Mercurial > hg > orthanc
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 } |