comparison Plugins/Samples/Common/OrthancPluginCppWrapper.cpp @ 3393:2cd0369a156f

support of chunked answers in HttpClient and in SDK
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 06 Jun 2019 16:12:55 +0200
parents ad434967a68c
children 2f82ef41bf5a
comparison
equal deleted inserted replaced
3392:ad434967a68c 3393:2cd0369a156f
2057 #endif 2057 #endif
2058 2058
2059 2059
2060 2060
2061 #if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 2061 #if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
2062 #if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1 2062 class HttpClient::RequestBodyWrapper : public boost::noncopyable
2063 class HttpClient::RequestChunkedBody : public boost::noncopyable
2064 { 2063 {
2065 private: 2064 private:
2066 static RequestChunkedBody& GetObject(void* body) 2065 static RequestBodyWrapper& GetObject(void* body)
2067 { 2066 {
2068 assert(body != NULL); 2067 assert(body != NULL);
2069 return *reinterpret_cast<RequestChunkedBody*>(body); 2068 return *reinterpret_cast<RequestBodyWrapper*>(body);
2070 } 2069 }
2071 2070
2072 IRequestChunkedBody& body_; 2071 IRequestBody& body_;
2073 bool done_; 2072 bool done_;
2074 std::string chunk_; 2073 std::string chunk_;
2075 2074
2076 public: 2075 public:
2077 RequestChunkedBody(IRequestChunkedBody& body) : 2076 RequestBodyWrapper(IRequestBody& body) :
2078 body_(body), 2077 body_(body),
2079 done_(false) 2078 done_(false)
2080 { 2079 {
2081 } 2080 }
2082 2081
2095 return static_cast<uint32_t>(GetObject(body).chunk_.size()); 2094 return static_cast<uint32_t>(GetObject(body).chunk_.size());
2096 } 2095 }
2097 2096
2098 static OrthancPluginErrorCode Next(void* body) 2097 static OrthancPluginErrorCode Next(void* body)
2099 { 2098 {
2100 RequestChunkedBody& that = GetObject(body); 2099 RequestBodyWrapper& that = GetObject(body);
2101 2100
2102 if (that.done_) 2101 if (that.done_)
2103 { 2102 {
2104 return OrthancPluginErrorCode_BadSequenceOfCalls; 2103 return OrthancPluginErrorCode_BadSequenceOfCalls;
2105 } 2104 }
2119 return OrthancPluginErrorCode_InternalError; 2118 return OrthancPluginErrorCode_InternalError;
2120 } 2119 }
2121 } 2120 }
2122 } 2121 }
2123 }; 2122 };
2123
2124
2125 #if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
2126 static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer,
2127 const char* key,
2128 const char* value)
2129 {
2130 assert(answer != NULL && key != NULL && value != NULL);
2131
2132 try
2133 {
2134 reinterpret_cast<HttpClient::IAnswer*>(answer)->AddHeader(key, value);
2135 return OrthancPluginErrorCode_Success;
2136 }
2137 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
2138 {
2139 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
2140 }
2141 catch (...)
2142 {
2143 return OrthancPluginErrorCode_Plugin;
2144 }
2145 }
2124 #endif 2146 #endif
2147
2148
2149 #if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
2150 static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer,
2151 const void* data,
2152 uint32_t size)
2153 {
2154 assert(answer != NULL);
2155
2156 try
2157 {
2158 reinterpret_cast<HttpClient::IAnswer*>(answer)->AddChunk(data, size);
2159 return OrthancPluginErrorCode_Success;
2160 }
2161 catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
2162 {
2163 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
2164 }
2165 catch (...)
2166 {
2167 return OrthancPluginErrorCode_Plugin;
2168 }
2169 }
2170 #endif
2171
2125 2172
2126 HttpClient::HttpClient() : 2173 HttpClient::HttpClient() :
2127 httpStatus_(0), 2174 httpStatus_(0),
2128 method_(OrthancPluginHttpMethod_Get), 2175 method_(OrthancPluginHttpMethod_Get),
2129 timeout_(0), 2176 timeout_(0),
2130 pkcs11_(false), 2177 pkcs11_(false),
2131 chunkedBody_(NULL) 2178 streamingBody_(NULL)
2132 { 2179 {
2133 } 2180 }
2134 2181
2135 2182
2136 void HttpClient::SetCredentials(const std::string& username, 2183 void HttpClient::SetCredentials(const std::string& username,
2167 2214
2168 2215
2169 void HttpClient::ClearBody() 2216 void HttpClient::ClearBody()
2170 { 2217 {
2171 body_.clear(); 2218 body_.clear();
2172 chunkedBody_ = NULL; 2219 streamingBody_ = NULL;
2173 } 2220 }
2174 2221
2175 2222
2176 void HttpClient::SwapBody(std::string& body) 2223 void HttpClient::SwapBody(std::string& body)
2177 { 2224 {
2178 body_.swap(body); 2225 body_.swap(body);
2179 chunkedBody_ = NULL; 2226 streamingBody_ = NULL;
2180 } 2227 }
2181 2228
2182 2229
2183 void HttpClient::SetBody(const std::string& body) 2230 void HttpClient::SetBody(const std::string& body)
2184 { 2231 {
2185 body_ = body; 2232 body_ = body;
2186 chunkedBody_ = NULL; 2233 streamingBody_ = NULL;
2187 } 2234 }
2188 2235
2189 2236
2190 #if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1 2237 void HttpClient::SetBody(IRequestBody& body)
2191 void HttpClient::SetBody(IRequestChunkedBody& body)
2192 { 2238 {
2193 body_.clear(); 2239 body_.clear();
2194 chunkedBody_ = &body; 2240 streamingBody_ = &body;
2195 } 2241 }
2242
2243
2244 namespace
2245 {
2246 class HeadersWrapper : public boost::noncopyable
2247 {
2248 private:
2249 std::vector<const char*> headersKeys_;
2250 std::vector<const char*> headersValues_;
2251
2252 public:
2253 HeadersWrapper(const HttpClient::HttpHeaders& headers)
2254 {
2255 headersKeys_.reserve(headers.size());
2256 headersValues_.reserve(headers.size());
2257
2258 for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
2259 {
2260 headersKeys_.push_back(it->first.c_str());
2261 headersValues_.push_back(it->second.c_str());
2262 }
2263 }
2264
2265 uint32_t GetCount() const
2266 {
2267 return headersKeys_.size();
2268 }
2269
2270 const char* const* GetKeys() const
2271 {
2272 return headersKeys_.empty() ? NULL : &headersKeys_[0];
2273 }
2274
2275 const char* const* GetValues() const
2276 {
2277 return headersValues_.empty() ? NULL : &headersValues_[0];
2278 }
2279 };
2280
2281
2282 class MemoryRequestBody : public HttpClient::IRequestBody
2283 {
2284 private:
2285 std::string body_;
2286
2287 public:
2288 MemoryRequestBody(const std::string& body) :
2289 body_(body)
2290 {
2291 }
2292
2293 virtual bool ReadNextChunk(std::string& chunk)
2294 {
2295 chunk.swap(body_);
2296 return true;
2297 }
2298 };
2299
2300
2301 // This class mimics Orthanc::ChunkedBuffer
2302 class ChunkedBuffer : public boost::noncopyable
2303 {
2304 private:
2305 typedef std::list<std::string*> Content;
2306
2307 Content content_;
2308 size_t size_;
2309
2310 public:
2311 ChunkedBuffer() :
2312 size_(0)
2313 {
2314 }
2315
2316 ~ChunkedBuffer()
2317 {
2318 Clear();
2319 }
2320
2321 void Clear()
2322 {
2323 for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
2324 {
2325 assert(*it != NULL);
2326 delete *it;
2327 }
2328
2329 content_.clear();
2330 }
2331
2332 void Flatten(std::string& target) const
2333 {
2334 target.resize(size_);
2335
2336 size_t pos = 0;
2337
2338 for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
2339 {
2340 assert(*it != NULL);
2341 size_t s = (*it)->size();
2342
2343 if (s != 0)
2344 {
2345 memcpy(&target[pos], (*it)->c_str(), s);
2346 pos += s;
2347 }
2348 }
2349
2350 assert(size_ == 0 ||
2351 pos == target.size());
2352 }
2353
2354 void AddChunk(const void* data,
2355 size_t size)
2356 {
2357 content_.push_back(new std::string(reinterpret_cast<const char*>(data), size));
2358 size_ += size;
2359 }
2360
2361 void AddChunk(const std::string& chunk)
2362 {
2363 content_.push_back(new std::string(chunk));
2364 size_ += chunk.size();
2365 }
2366 };
2367
2368
2369 #if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
2370 class MemoryAnswer : public HttpClient::IAnswer
2371 {
2372 private:
2373 HttpClient::HttpHeaders headers_;
2374 ChunkedBuffer body_;
2375
2376 public:
2377 const HttpClient::HttpHeaders& GetHeaders() const
2378 {
2379 return headers_;
2380 }
2381
2382 const ChunkedBuffer& GetBody() const
2383 {
2384 return body_;
2385 }
2386
2387 virtual void AddHeader(const std::string& key,
2388 const std::string& value)
2389 {
2390 headers_[key] = value;
2391 }
2392
2393 virtual void AddChunk(const void* data,
2394 size_t size)
2395 {
2396 body_.AddChunk(data, size);
2397 }
2398 };
2196 #endif 2399 #endif
2197 2400 }
2198 2401
2199 void HttpClient::Execute() 2402
2200 { 2403 #if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
2201 std::vector<const char*> headersKeys; 2404 void HttpClient::ExecuteWithStream(uint16_t& httpStatus,
2202 std::vector<const char*> headersValues; 2405 IAnswer& answer,
2203 2406 IRequestBody& body) const
2204 headersKeys.reserve(headers_.size()); 2407 {
2205 headersValues.reserve(headers_.size()); 2408 std::auto_ptr<HeadersWrapper> h;
2206 2409
2207 for (HttpHeaders::const_iterator it = headers_.begin(); 2410 // Automatically set the "Transfer-Encoding" header if absent
2208 it != headers_.end(); ++it) 2411 if (headers_.find("Transfer-Encoding") == headers_.end())
2209 { 2412 {
2210 headersKeys.push_back(it->first.c_str()); 2413 HttpHeaders tmp = headers_;
2211 headersValues.push_back(it->second.c_str()); 2414 tmp["Transfer-Encoding"] = "chunked";
2212 } 2415 h.reset(new HeadersWrapper(tmp));
2213 2416 }
2214 OrthancPluginErrorCode error; 2417 else
2418 {
2419 h.reset(new HeadersWrapper(headers_));
2420 }
2421
2422 RequestBodyWrapper request(body);
2423
2424 OrthancPluginErrorCode error = OrthancPluginStreamingHttpClient(
2425 GetGlobalContext(),
2426 &answer,
2427 AnswerAddChunkCallback,
2428 AnswerAddHeaderCallback,
2429 &httpStatus,
2430 method_,
2431 url_.c_str(),
2432 h->GetCount(),
2433 h->GetKeys(),
2434 h->GetValues(),
2435 &request,
2436 RequestBodyWrapper::IsDone,
2437 RequestBodyWrapper::GetChunkData,
2438 RequestBodyWrapper::GetChunkSize,
2439 RequestBodyWrapper::Next,
2440 username_.empty() ? NULL : username_.c_str(),
2441 password_.empty() ? NULL : password_.c_str(),
2442 timeout_,
2443 certificateFile_.empty() ? NULL : certificateFile_.c_str(),
2444 certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
2445 certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
2446 pkcs11_ ? 1 : 0);
2447
2448 if (error != OrthancPluginErrorCode_Success)
2449 {
2450 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
2451 }
2452 }
2453 #endif
2454
2455
2456 void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
2457 HttpHeaders& answerHeaders,
2458 std::string& answerBody,
2459 const std::string& body) const
2460 {
2461 HeadersWrapper headers(headers_);
2462
2463 MemoryBuffer answerBodyBuffer, answerHeadersBuffer;
2464
2465 OrthancPluginErrorCode error = OrthancPluginHttpClient(
2466 GetGlobalContext(),
2467 *answerBodyBuffer,
2468 *answerHeadersBuffer,
2469 &httpStatus,
2470 method_,
2471 url_.c_str(),
2472 headers.GetCount(),
2473 headers.GetKeys(),
2474 headers.GetValues(),
2475 body.empty() ? NULL : body.c_str(),
2476 body.size(),
2477 username_.empty() ? NULL : username_.c_str(),
2478 password_.empty() ? NULL : password_.c_str(),
2479 timeout_,
2480 certificateFile_.empty() ? NULL : certificateFile_.c_str(),
2481 certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
2482 certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
2483 pkcs11_ ? 1 : 0);
2484
2485 if (error != OrthancPluginErrorCode_Success)
2486 {
2487 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
2488 }
2489
2490 Json::Value v;
2491 answerHeadersBuffer.ToJson(v);
2492
2493 if (v.type() != Json::objectValue)
2494 {
2495 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
2496 }
2497
2498 Json::Value::Members members = v.getMemberNames();
2499 answerHeaders.clear();
2500
2501 for (size_t i = 0; i < members.size(); i++)
2502 {
2503 const Json::Value& h = v[members[i]];
2504 if (h.type() != Json::stringValue)
2505 {
2506 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
2507 }
2508 else
2509 {
2510 answerHeaders[members[i]] = h.asString();
2511 }
2512 }
2513
2514 answerBodyBuffer.ToString(answerBody);
2515 }
2516
2517
2518 #if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
2519 void HttpClient::Execute(IAnswer& answer)
2520 {
2521 if (streamingBody_ != NULL)
2522 {
2523 ExecuteWithStream(httpStatus_, answer, *streamingBody_);
2524 }
2525 else
2526 {
2527 MemoryRequestBody wrapper(body_);
2528 ExecuteWithStream(httpStatus_, answer, wrapper);
2529 }
2530 }
2531 #endif
2532
2533
2534 void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
2535 std::string& answerBody /* out */)
2536 {
2537 #if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
2538 MemoryAnswer answer;
2539 Execute(answer);
2540 answerHeaders = answer.GetHeaders();
2541 answer.GetBody().Flatten(answerBody);
2542
2543 #else
2544 // Compatibility mode for Orthanc SDK <= 1.5.6. This results in
2545 // higher memory usage (all chunks from the body request are sent
2546 // at once)
2547
2548 if (streamingBody_ != NULL)
2549 {
2550 ChunkedBuffer buffer;
2215 2551
2216 if (chunkedBody_ == NULL) 2552 std::string chunk;
2217 { 2553 while (streamingBody_->ReadNextChunk(chunk))
2218 error = OrthancPluginHttpClient( 2554 {
2219 GetGlobalContext(), 2555 buffer.AddChunk(chunk);
2220 *answerBody_, 2556 }
2221 *answerHeaders_, 2557
2222 &httpStatus_, 2558 std::string body;
2223 method_, 2559 buffer.Flatten(body);
2224 url_.c_str(), 2560
2225 headersKeys.size(), 2561 ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body);
2226 headersKeys.empty() ? NULL : &headersKeys[0], 2562 }
2227 headersValues.empty() ? NULL : &headersValues[0], 2563 else
2228 body_.empty() ? NULL : body_.c_str(), 2564 {
2229 body_.size(), 2565 ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body_);
2230 username_.empty() ? NULL : username_.c_str(), 2566 }
2231 password_.empty() ? NULL : password_.c_str(),
2232 timeout_,
2233 certificateFile_.empty() ? NULL : certificateFile_.c_str(),
2234 certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
2235 certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
2236 pkcs11_ ? 1 : 0);
2237 }
2238 else
2239 {
2240 #if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY != 1
2241 error = OrthancPluginErrorCode_InternalError;
2242 #else
2243 RequestChunkedBody wrapper(*chunkedBody_);
2244
2245 error = OrthancPluginHttpClientChunkedBody(
2246 GetGlobalContext(),
2247 *answerBody_,
2248 *answerHeaders_,
2249 &httpStatus_,
2250 method_,
2251 url_.c_str(),
2252 headersKeys.size(),
2253 headersKeys.empty() ? NULL : &headersKeys[0],
2254 headersValues.empty() ? NULL : &headersValues[0],
2255 username_.empty() ? NULL : username_.c_str(),
2256 password_.empty() ? NULL : password_.c_str(),
2257 timeout_,
2258 certificateFile_.empty() ? NULL : certificateFile_.c_str(),
2259 certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
2260 certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
2261 pkcs11_ ? 1 : 0,
2262 &wrapper,
2263 RequestChunkedBody::IsDone,
2264 RequestChunkedBody::GetChunkData,
2265 RequestChunkedBody::GetChunkSize,
2266 RequestChunkedBody::Next);
2267 #endif 2567 #endif
2268 } 2568 }
2269 2569
2270 if (error != OrthancPluginErrorCode_Success)
2271 {
2272 ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
2273 }
2274 }
2275 #endif /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */ 2570 #endif /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */
2276 } 2571 }