Mercurial > hg > orthanc
comparison Core/HttpServer/HttpServer.cpp @ 3395:0ce9b4f5fdf5
new abstraction: IHttpHandler::CreateStreamHandler()
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 06 Jun 2019 18:54:27 +0200 |
parents | c0aa5f1cf2f5 |
children | e280ced38a4c |
comparison
equal
deleted
inserted
replaced
3394:2f82ef41bf5a | 3395:0ce9b4f5fdf5 |
---|---|
77 #define ORTHANC_REALM "Orthanc Secure Area" | 77 #define ORTHANC_REALM "Orthanc Secure Area" |
78 | 78 |
79 | 79 |
80 namespace Orthanc | 80 namespace Orthanc |
81 { | 81 { |
82 static const char multipart[] = "multipart/form-data; boundary="; | 82 static const char MULTIPART_FORM[] = "multipart/form-data; boundary="; |
83 static unsigned int multipartLength = sizeof(multipart) / sizeof(char) - 1; | 83 static unsigned int MULTIPART_FORM_LENGTH = sizeof(MULTIPART_FORM) / sizeof(char) - 1; |
84 | 84 |
85 | 85 |
86 namespace | 86 namespace |
87 { | 87 { |
88 // Anonymous namespace to avoid clashes between compilation modules | 88 // Anonymous namespace to avoid clashes between compilation modules |
300 { | 300 { |
301 return pimpl_->chunkStore_; | 301 return pimpl_->chunkStore_; |
302 } | 302 } |
303 | 303 |
304 | 304 |
305 | 305 static PostDataStatus ReadBodyWithContentLength(std::string& body, |
306 static PostDataStatus ReadBody(std::string& postData, | 306 struct mg_connection *connection, |
307 struct mg_connection *connection, | 307 const std::string& contentLength) |
308 const IHttpHandler::Arguments& headers) | 308 { |
309 { | 309 int length; |
310 IHttpHandler::Arguments::const_iterator cs = headers.find("content-length"); | 310 try |
311 if (cs == headers.end()) | 311 { |
312 { | 312 length = boost::lexical_cast<int>(contentLength); |
313 // Store all the individual chunks within a temporary file, then | 313 } |
314 // read it back into the memory buffer "postData" | 314 catch (boost::bad_lexical_cast&) |
315 { | |
316 return PostDataStatus_NoLength; | |
317 } | |
318 | |
319 if (length < 0) | |
320 { | |
321 length = 0; | |
322 } | |
323 | |
324 body.resize(length); | |
325 | |
326 size_t pos = 0; | |
327 while (length > 0) | |
328 { | |
329 int r = mg_read(connection, &body[pos], length); | |
330 if (r <= 0) | |
331 { | |
332 return PostDataStatus_Failure; | |
333 } | |
334 | |
335 assert(r <= length); | |
336 length -= r; | |
337 pos += r; | |
338 } | |
339 | |
340 return PostDataStatus_Success; | |
341 } | |
342 | |
343 | |
344 static PostDataStatus ReadBodyToString(std::string& body, | |
345 struct mg_connection *connection, | |
346 const IHttpHandler::Arguments& headers) | |
347 { | |
348 IHttpHandler::Arguments::const_iterator contentLength = headers.find("content-length"); | |
349 | |
350 if (contentLength != headers.end()) | |
351 { | |
352 // "Content-Length" is available | |
353 return ReadBodyWithContentLength(body, connection, contentLength->second); | |
354 } | |
355 else | |
356 { | |
357 // No Content-Length. Store the individual chunks in a temporary | |
358 // file, then read it back into the memory buffer "body" | |
315 FileBuffer buffer; | 359 FileBuffer buffer; |
316 | 360 |
317 std::string tmp(1024 * 1024, 0); | 361 std::string tmp(1024 * 1024, 0); |
318 | 362 |
319 for (;;) | 363 for (;;) |
331 { | 375 { |
332 buffer.Append(tmp.c_str(), r); | 376 buffer.Append(tmp.c_str(), r); |
333 } | 377 } |
334 } | 378 } |
335 | 379 |
336 buffer.Read(postData); | 380 buffer.Read(body); |
337 | 381 |
338 return PostDataStatus_Success; | 382 return PostDataStatus_Success; |
339 } | 383 } |
384 } | |
385 | |
386 | |
387 static PostDataStatus ReadBodyToStream(IHttpHandler::IStream& stream, | |
388 struct mg_connection *connection, | |
389 const IHttpHandler::Arguments& headers) | |
390 { | |
391 IHttpHandler::Arguments::const_iterator contentLength = headers.find("content-length"); | |
392 | |
393 if (contentLength != headers.end()) | |
394 { | |
395 // "Content-Length" is available | |
396 | |
397 std::string body; | |
398 PostDataStatus status = ReadBodyWithContentLength(body, connection, contentLength->second); | |
399 | |
400 if (status == PostDataStatus_Success && | |
401 !body.empty()) | |
402 { | |
403 stream.AddBodyChunk(body.c_str(), body.size()); | |
404 } | |
405 | |
406 return status; | |
407 } | |
340 else | 408 else |
341 { | 409 { |
342 // "Content-Length" is available | 410 // No Content-Length. Stream the HTTP connection. |
343 int length; | 411 std::string tmp(1024 * 1024, 0); |
344 try | 412 |
345 { | 413 for (;;) |
346 length = boost::lexical_cast<int>(cs->second); | 414 { |
347 } | 415 int r = mg_read(connection, &tmp[0], tmp.size()); |
348 catch (boost::bad_lexical_cast&) | 416 if (r < 0) |
349 { | |
350 return PostDataStatus_NoLength; | |
351 } | |
352 | |
353 if (length < 0) | |
354 { | |
355 length = 0; | |
356 } | |
357 | |
358 postData.resize(length); | |
359 | |
360 size_t pos = 0; | |
361 while (length > 0) | |
362 { | |
363 int r = mg_read(connection, &postData[pos], length); | |
364 if (r <= 0) | |
365 { | 417 { |
366 return PostDataStatus_Failure; | 418 return PostDataStatus_Failure; |
367 } | 419 } |
368 | 420 else if (r == 0) |
369 assert(r <= length); | 421 { |
370 length -= r; | 422 break; |
371 pos += r; | 423 } |
424 else | |
425 { | |
426 stream.AddBodyChunk(tmp.c_str(), r); | |
427 } | |
372 } | 428 } |
373 | 429 |
374 return PostDataStatus_Success; | 430 return PostDataStatus_Success; |
375 } | 431 } |
376 } | 432 } |
377 | 433 |
378 | 434 |
379 | 435 static PostDataStatus ParseMultipartForm(std::string &completedFile, |
380 static PostDataStatus ParseMultipartPost(std::string &completedFile, | |
381 struct mg_connection *connection, | 436 struct mg_connection *connection, |
382 const IHttpHandler::Arguments& headers, | 437 const IHttpHandler::Arguments& headers, |
383 const std::string& contentType, | 438 const std::string& contentType, |
384 ChunkStore& chunkStore) | 439 ChunkStore& chunkStore) |
385 { | 440 { |
386 std::string boundary = "--" + contentType.substr(multipartLength); | 441 std::string boundary = "--" + contentType.substr(MULTIPART_FORM_LENGTH); |
387 | 442 |
388 std::string postData; | 443 std::string body; |
389 PostDataStatus status = ReadBody(postData, connection, headers); | 444 PostDataStatus status = ReadBodyToString(body, connection, headers); |
390 | 445 |
391 if (status != PostDataStatus_Success) | 446 if (status != PostDataStatus_Success) |
392 { | 447 { |
393 return status; | 448 return status; |
394 } | 449 } |
431 | 486 |
432 try | 487 try |
433 { | 488 { |
434 FindIterator last; | 489 FindIterator last; |
435 for (FindIterator it = | 490 for (FindIterator it = |
436 make_find_iterator(postData, boost::first_finder(boundary)); | 491 make_find_iterator(body, boost::first_finder(boundary)); |
437 it!=FindIterator(); | 492 it!=FindIterator(); |
438 ++it) | 493 ++it) |
439 { | 494 { |
440 if (last != FindIterator()) | 495 if (last != FindIterator()) |
441 { | 496 { |
750 return; | 805 return; |
751 } | 806 } |
752 } | 807 } |
753 | 808 |
754 | 809 |
755 // Extract the body of the request for PUT and POST | 810 // Decompose the URI into its components |
811 UriComponents uri; | |
812 try | |
813 { | |
814 Toolbox::SplitUriComponents(uri, requestUri); | |
815 } | |
816 catch (OrthancException&) | |
817 { | |
818 output.SendStatus(HttpStatus_400_BadRequest); | |
819 return; | |
820 } | |
821 | |
822 LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); | |
823 | |
824 | |
825 bool found = false; | |
826 | |
827 // Extract the body of the request for PUT and POST, or process | |
828 // the body as a stream | |
756 | 829 |
757 // TODO Avoid unneccessary memcopy of the body | 830 // TODO Avoid unneccessary memcopy of the body |
758 | 831 |
759 std::string body; | 832 std::string body; |
760 if (method == HttpMethod_Post || | 833 if (method == HttpMethod_Post || |
761 method == HttpMethod_Put) | 834 method == HttpMethod_Put) |
762 { | 835 { |
763 PostDataStatus status; | 836 PostDataStatus status; |
764 | 837 |
838 bool isMultipartForm = false; | |
839 | |
765 IHttpHandler::Arguments::const_iterator ct = headers.find("content-type"); | 840 IHttpHandler::Arguments::const_iterator ct = headers.find("content-type"); |
766 if (ct == headers.end()) | 841 if (ct != headers.end() && |
767 { | 842 ct->second.size() >= MULTIPART_FORM_LENGTH && |
768 // No content-type specified. Assume no multi-part content occurs at this point. | 843 !memcmp(ct->second.c_str(), MULTIPART_FORM, MULTIPART_FORM_LENGTH)) |
769 status = ReadBody(body, connection, headers); | 844 { |
770 } | 845 status = ParseMultipartForm(body, connection, headers, ct->second, server.GetChunkStore()); |
771 else | 846 isMultipartForm = true; |
772 { | 847 } |
773 std::string contentType = ct->second; | 848 |
774 if (contentType.size() >= multipartLength && | 849 if (!isMultipartForm) |
775 !memcmp(contentType.c_str(), multipart, multipartLength)) | 850 { |
776 { | 851 std::auto_ptr<IHttpHandler::IStream> stream; |
777 status = ParseMultipartPost(body, connection, headers, contentType, server.GetChunkStore()); | 852 |
853 if (server.HasHandler()) | |
854 { | |
855 stream.reset(server.GetHandler().CreateStreamHandler | |
856 (RequestOrigin_RestApi, remoteIp, username.c_str(), method, uri, headers)); | |
857 } | |
858 | |
859 if (stream.get() != NULL) | |
860 { | |
861 status = ReadBodyToStream(*stream, connection, headers); | |
862 | |
863 if (status == PostDataStatus_Success) | |
864 { | |
865 stream->Execute(output); | |
866 } | |
778 } | 867 } |
779 else | 868 else |
780 { | 869 { |
781 status = ReadBody(body, connection, headers); | 870 status = ReadBodyToString(body, connection, headers); |
782 } | 871 } |
783 } | 872 } |
784 | 873 |
785 switch (status) | 874 switch (status) |
786 { | 875 { |
794 | 883 |
795 case PostDataStatus_Pending: | 884 case PostDataStatus_Pending: |
796 output.AnswerEmpty(); | 885 output.AnswerEmpty(); |
797 return; | 886 return; |
798 | 887 |
888 case PostDataStatus_Success: | |
889 break; | |
890 | |
799 default: | 891 default: |
800 break; | 892 throw OrthancException(ErrorCode_InternalError); |
801 } | 893 } |
802 } | 894 } |
803 | 895 |
804 | 896 |
805 // Decompose the URI into its components | 897 if (!found && |
806 UriComponents uri; | 898 server.HasHandler()) |
807 try | |
808 { | |
809 Toolbox::SplitUriComponents(uri, requestUri); | |
810 } | |
811 catch (OrthancException&) | |
812 { | |
813 output.SendStatus(HttpStatus_400_BadRequest); | |
814 return; | |
815 } | |
816 | |
817 | |
818 LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); | |
819 | |
820 bool found = false; | |
821 | |
822 if (server.HasHandler()) | |
823 { | 899 { |
824 found = server.GetHandler().Handle(output, RequestOrigin_RestApi, remoteIp, username.c_str(), | 900 found = server.GetHandler().Handle(output, RequestOrigin_RestApi, remoteIp, username.c_str(), |
825 method, uri, headers, argumentsGET, body.c_str(), body.size()); | 901 method, uri, headers, argumentsGET, body.c_str(), body.size()); |
826 } | 902 } |
827 | 903 |