Mercurial > hg > orthanc
comparison OrthancFramework/Sources/HttpServer/HttpOutput.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | Core/HttpServer/HttpOutput.cpp@94f4a18a79cc |
children | bf7b9edf6b81 |
comparison
equal
deleted
inserted
replaced
4043:6c6239aec462 | 4044:d25f4c0fa160 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #include "../PrecompiledHeaders.h" | |
35 #include "HttpOutput.h" | |
36 | |
37 #include "../ChunkedBuffer.h" | |
38 #include "../Compression/GzipCompressor.h" | |
39 #include "../Compression/ZlibCompressor.h" | |
40 #include "../Logging.h" | |
41 #include "../OrthancException.h" | |
42 #include "../Toolbox.h" | |
43 | |
44 #include <iostream> | |
45 #include <vector> | |
46 #include <stdio.h> | |
47 #include <boost/lexical_cast.hpp> | |
48 | |
49 | |
50 #if ORTHANC_ENABLE_CIVETWEB == 1 | |
51 # if !defined(CIVETWEB_HAS_DISABLE_KEEP_ALIVE) | |
52 # error Macro CIVETWEB_HAS_DISABLE_KEEP_ALIVE must be defined | |
53 # endif | |
54 #endif | |
55 | |
56 | |
57 namespace Orthanc | |
58 { | |
59 HttpOutput::StateMachine::StateMachine(IHttpOutputStream& stream, | |
60 bool isKeepAlive) : | |
61 stream_(stream), | |
62 state_(State_WritingHeader), | |
63 status_(HttpStatus_200_Ok), | |
64 hasContentLength_(false), | |
65 contentPosition_(0), | |
66 keepAlive_(isKeepAlive) | |
67 { | |
68 } | |
69 | |
70 HttpOutput::StateMachine::~StateMachine() | |
71 { | |
72 if (state_ != State_Done) | |
73 { | |
74 //asm volatile ("int3;"); | |
75 //LOG(ERROR) << "This HTTP answer does not contain any body"; | |
76 } | |
77 | |
78 if (hasContentLength_ && contentPosition_ != contentLength_) | |
79 { | |
80 LOG(ERROR) << "This HTTP answer has not sent the proper number of bytes in its body"; | |
81 } | |
82 } | |
83 | |
84 | |
85 void HttpOutput::StateMachine::SetHttpStatus(HttpStatus status) | |
86 { | |
87 if (state_ != State_WritingHeader) | |
88 { | |
89 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
90 } | |
91 | |
92 status_ = status; | |
93 } | |
94 | |
95 | |
96 void HttpOutput::StateMachine::SetContentLength(uint64_t length) | |
97 { | |
98 if (state_ != State_WritingHeader) | |
99 { | |
100 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
101 } | |
102 | |
103 hasContentLength_ = true; | |
104 contentLength_ = length; | |
105 } | |
106 | |
107 void HttpOutput::StateMachine::SetContentType(const char* contentType) | |
108 { | |
109 AddHeader("Content-Type", contentType); | |
110 } | |
111 | |
112 void HttpOutput::StateMachine::SetContentFilename(const char* filename) | |
113 { | |
114 // TODO Escape double quotes | |
115 AddHeader("Content-Disposition", "filename=\"" + std::string(filename) + "\""); | |
116 } | |
117 | |
118 void HttpOutput::StateMachine::SetCookie(const std::string& cookie, | |
119 const std::string& value) | |
120 { | |
121 if (state_ != State_WritingHeader) | |
122 { | |
123 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
124 } | |
125 | |
126 // TODO Escape "=" characters | |
127 AddHeader("Set-Cookie", cookie + "=" + value); | |
128 } | |
129 | |
130 | |
131 void HttpOutput::StateMachine::AddHeader(const std::string& header, | |
132 const std::string& value) | |
133 { | |
134 if (state_ != State_WritingHeader) | |
135 { | |
136 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
137 } | |
138 | |
139 headers_.push_back(header + ": " + value + "\r\n"); | |
140 } | |
141 | |
142 void HttpOutput::StateMachine::ClearHeaders() | |
143 { | |
144 if (state_ != State_WritingHeader) | |
145 { | |
146 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
147 } | |
148 | |
149 headers_.clear(); | |
150 } | |
151 | |
152 void HttpOutput::StateMachine::SendBody(const void* buffer, size_t length) | |
153 { | |
154 if (state_ == State_Done) | |
155 { | |
156 if (length == 0) | |
157 { | |
158 return; | |
159 } | |
160 else | |
161 { | |
162 throw OrthancException(ErrorCode_BadSequenceOfCalls, | |
163 "Because of keep-alive connections, the entire body must " | |
164 "be sent at once or Content-Length must be given"); | |
165 } | |
166 } | |
167 | |
168 if (state_ == State_WritingMultipart) | |
169 { | |
170 throw OrthancException(ErrorCode_InternalError); | |
171 } | |
172 | |
173 if (state_ == State_WritingHeader) | |
174 { | |
175 // Send the HTTP header before writing the body | |
176 | |
177 stream_.OnHttpStatusReceived(status_); | |
178 | |
179 std::string s = "HTTP/1.1 " + | |
180 boost::lexical_cast<std::string>(status_) + | |
181 " " + std::string(EnumerationToString(status_)) + | |
182 "\r\n"; | |
183 | |
184 if (keepAlive_) | |
185 { | |
186 s += "Connection: keep-alive\r\n"; | |
187 } | |
188 else | |
189 { | |
190 s += "Connection: close\r\n"; | |
191 } | |
192 | |
193 for (std::list<std::string>::const_iterator | |
194 it = headers_.begin(); it != headers_.end(); ++it) | |
195 { | |
196 s += *it; | |
197 } | |
198 | |
199 if (status_ != HttpStatus_200_Ok) | |
200 { | |
201 hasContentLength_ = false; | |
202 } | |
203 | |
204 uint64_t contentLength = (hasContentLength_ ? contentLength_ : length); | |
205 s += "Content-Length: " + boost::lexical_cast<std::string>(contentLength) + "\r\n\r\n"; | |
206 | |
207 stream_.Send(true, s.c_str(), s.size()); | |
208 state_ = State_WritingBody; | |
209 } | |
210 | |
211 if (hasContentLength_ && | |
212 contentPosition_ + length > contentLength_) | |
213 { | |
214 throw OrthancException(ErrorCode_BadSequenceOfCalls, | |
215 "The body size exceeds what was declared with SetContentSize()"); | |
216 } | |
217 | |
218 if (length > 0) | |
219 { | |
220 stream_.Send(false, buffer, length); | |
221 contentPosition_ += length; | |
222 } | |
223 | |
224 if (!hasContentLength_ || | |
225 contentPosition_ == contentLength_) | |
226 { | |
227 state_ = State_Done; | |
228 } | |
229 } | |
230 | |
231 | |
232 void HttpOutput::StateMachine::CloseBody() | |
233 { | |
234 switch (state_) | |
235 { | |
236 case State_WritingHeader: | |
237 SetContentLength(0); | |
238 SendBody(NULL, 0); | |
239 break; | |
240 | |
241 case State_WritingBody: | |
242 if (!hasContentLength_ || | |
243 contentPosition_ == contentLength_) | |
244 { | |
245 state_ = State_Done; | |
246 } | |
247 else | |
248 { | |
249 throw OrthancException(ErrorCode_BadSequenceOfCalls, | |
250 "The body size has not reached what was declared with SetContentSize()"); | |
251 } | |
252 | |
253 break; | |
254 | |
255 case State_WritingMultipart: | |
256 throw OrthancException(ErrorCode_BadSequenceOfCalls, | |
257 "Cannot invoke CloseBody() with multipart outputs"); | |
258 | |
259 case State_Done: | |
260 return; // Ignore | |
261 | |
262 default: | |
263 throw OrthancException(ErrorCode_InternalError); | |
264 } | |
265 } | |
266 | |
267 | |
268 HttpCompression HttpOutput::GetPreferredCompression(size_t bodySize) const | |
269 { | |
270 #if 0 | |
271 // TODO Do not compress small files? | |
272 if (bodySize < 512) | |
273 { | |
274 return HttpCompression_None; | |
275 } | |
276 #endif | |
277 | |
278 // Prefer "gzip" over "deflate" if the choice is offered | |
279 | |
280 if (isGzipAllowed_) | |
281 { | |
282 return HttpCompression_Gzip; | |
283 } | |
284 else if (isDeflateAllowed_) | |
285 { | |
286 return HttpCompression_Deflate; | |
287 } | |
288 else | |
289 { | |
290 return HttpCompression_None; | |
291 } | |
292 } | |
293 | |
294 | |
295 void HttpOutput::SendMethodNotAllowed(const std::string& allowed) | |
296 { | |
297 stateMachine_.ClearHeaders(); | |
298 stateMachine_.SetHttpStatus(HttpStatus_405_MethodNotAllowed); | |
299 stateMachine_.AddHeader("Allow", allowed); | |
300 stateMachine_.SendBody(NULL, 0); | |
301 } | |
302 | |
303 | |
304 void HttpOutput::SendStatus(HttpStatus status, | |
305 const char* message, | |
306 size_t messageSize) | |
307 { | |
308 if (status == HttpStatus_301_MovedPermanently || | |
309 //status == HttpStatus_401_Unauthorized || | |
310 status == HttpStatus_405_MethodNotAllowed) | |
311 { | |
312 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
313 "Please use the dedicated methods to this HTTP status code in HttpOutput"); | |
314 } | |
315 | |
316 stateMachine_.SetHttpStatus(status); | |
317 stateMachine_.SendBody(message, messageSize); | |
318 } | |
319 | |
320 | |
321 void HttpOutput::Redirect(const std::string& path) | |
322 { | |
323 stateMachine_.ClearHeaders(); | |
324 stateMachine_.SetHttpStatus(HttpStatus_301_MovedPermanently); | |
325 stateMachine_.AddHeader("Location", path); | |
326 stateMachine_.SendBody(NULL, 0); | |
327 } | |
328 | |
329 | |
330 void HttpOutput::SendUnauthorized(const std::string& realm) | |
331 { | |
332 stateMachine_.ClearHeaders(); | |
333 stateMachine_.SetHttpStatus(HttpStatus_401_Unauthorized); | |
334 stateMachine_.AddHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\""); | |
335 stateMachine_.SendBody(NULL, 0); | |
336 } | |
337 | |
338 | |
339 void HttpOutput::Answer(const void* buffer, | |
340 size_t length) | |
341 { | |
342 if (length == 0) | |
343 { | |
344 AnswerEmpty(); | |
345 return; | |
346 } | |
347 | |
348 HttpCompression compression = GetPreferredCompression(length); | |
349 | |
350 if (compression == HttpCompression_None) | |
351 { | |
352 stateMachine_.SetContentLength(length); | |
353 stateMachine_.SendBody(buffer, length); | |
354 return; | |
355 } | |
356 | |
357 std::string compressed, encoding; | |
358 | |
359 switch (compression) | |
360 { | |
361 case HttpCompression_Deflate: | |
362 { | |
363 encoding = "deflate"; | |
364 ZlibCompressor compressor; | |
365 // Do not prefix the buffer with its uncompressed size, to be compatible with "deflate" | |
366 compressor.SetPrefixWithUncompressedSize(false); | |
367 compressor.Compress(compressed, buffer, length); | |
368 break; | |
369 } | |
370 | |
371 case HttpCompression_Gzip: | |
372 { | |
373 encoding = "gzip"; | |
374 GzipCompressor compressor; | |
375 compressor.Compress(compressed, buffer, length); | |
376 break; | |
377 } | |
378 | |
379 default: | |
380 throw OrthancException(ErrorCode_InternalError); | |
381 } | |
382 | |
383 LOG(TRACE) << "Compressing a HTTP answer using " << encoding; | |
384 | |
385 // The body is empty, do not use HTTP compression | |
386 if (compressed.size() == 0) | |
387 { | |
388 AnswerEmpty(); | |
389 } | |
390 else | |
391 { | |
392 stateMachine_.AddHeader("Content-Encoding", encoding); | |
393 stateMachine_.SetContentLength(compressed.size()); | |
394 stateMachine_.SendBody(compressed.c_str(), compressed.size()); | |
395 } | |
396 | |
397 stateMachine_.CloseBody(); | |
398 } | |
399 | |
400 | |
401 void HttpOutput::Answer(const std::string& str) | |
402 { | |
403 Answer(str.size() == 0 ? NULL : str.c_str(), str.size()); | |
404 } | |
405 | |
406 | |
407 void HttpOutput::AnswerEmpty() | |
408 { | |
409 stateMachine_.CloseBody(); | |
410 } | |
411 | |
412 | |
413 void HttpOutput::StateMachine::CheckHeadersCompatibilityWithMultipart() const | |
414 { | |
415 for (std::list<std::string>::const_iterator | |
416 it = headers_.begin(); it != headers_.end(); ++it) | |
417 { | |
418 if (!Toolbox::StartsWith(*it, "Set-Cookie: ")) | |
419 { | |
420 throw OrthancException(ErrorCode_BadSequenceOfCalls, | |
421 "The only headers that can be set in multipart answers " | |
422 "are Set-Cookie (here: " + *it + " is set)"); | |
423 } | |
424 } | |
425 } | |
426 | |
427 | |
428 static void PrepareMultipartMainHeader(std::string& boundary, | |
429 std::string& contentTypeHeader, | |
430 const std::string& subType, | |
431 const std::string& contentType) | |
432 { | |
433 if (subType != "mixed" && | |
434 subType != "related") | |
435 { | |
436 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
437 } | |
438 | |
439 /** | |
440 * Fix for issue 54 ("Decide what to do wrt. quoting of multipart | |
441 * answers"). The "type" parameter in the "Content-Type" HTTP | |
442 * header must be quoted if it contains a forward slash "/". This | |
443 * is necessary for DICOMweb compatibility with OsiriX, but breaks | |
444 * compatibility with old releases of the client in the Orthanc | |
445 * DICOMweb plugin <= 0.3 (releases >= 0.4 work fine). | |
446 * | |
447 * Full history is available at the following locations: | |
448 * - In changeset 2248:69b0f4e8a49b: | |
449 * # hg history -v -r 2248 | |
450 * - https://bitbucket.org/sjodogne/orthanc/issues/54/ | |
451 * - https://groups.google.com/d/msg/orthanc-users/65zhIM5xbKI/TU5Q1_LhAwAJ | |
452 **/ | |
453 std::string tmp; | |
454 if (contentType.find('/') == std::string::npos) | |
455 { | |
456 // No forward slash in the content type | |
457 tmp = contentType; | |
458 } | |
459 else | |
460 { | |
461 // Quote the content type because of the forward slash | |
462 tmp = "\"" + contentType + "\""; | |
463 } | |
464 | |
465 boundary = Toolbox::GenerateUuid() + "-" + Toolbox::GenerateUuid(); | |
466 | |
467 /** | |
468 * Fix for issue #165: "Encapsulation boundaries must not appear | |
469 * within the encapsulations, and must be no longer than 70 | |
470 * characters, not counting the two leading hyphens." | |
471 * https://tools.ietf.org/html/rfc1521 | |
472 * https://bitbucket.org/sjodogne/orthanc/issues/165/ | |
473 **/ | |
474 if (boundary.size() != 36 + 1 + 36) // one UUID contains 36 characters | |
475 { | |
476 throw OrthancException(ErrorCode_InternalError); | |
477 } | |
478 | |
479 boundary = boundary.substr(0, 70); | |
480 | |
481 contentTypeHeader = ("multipart/" + subType + "; type=" + tmp + "; boundary=" + boundary); | |
482 } | |
483 | |
484 | |
485 void HttpOutput::StateMachine::StartMultipart(const std::string& subType, | |
486 const std::string& contentType) | |
487 { | |
488 if (state_ != State_WritingHeader) | |
489 { | |
490 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
491 } | |
492 | |
493 if (status_ != HttpStatus_200_Ok) | |
494 { | |
495 SendBody(NULL, 0); | |
496 return; | |
497 } | |
498 | |
499 stream_.OnHttpStatusReceived(status_); | |
500 | |
501 std::string header = "HTTP/1.1 200 OK\r\n"; | |
502 | |
503 if (keepAlive_) | |
504 { | |
505 #if ORTHANC_ENABLE_MONGOOSE == 1 | |
506 throw OrthancException(ErrorCode_NotImplemented, | |
507 "Multipart answers are not implemented together " | |
508 "with keep-alive connections if using Mongoose"); | |
509 | |
510 #elif ORTHANC_ENABLE_CIVETWEB == 1 | |
511 # if CIVETWEB_HAS_DISABLE_KEEP_ALIVE == 1 | |
512 // Turn off Keep-Alive for multipart answers | |
513 // https://github.com/civetweb/civetweb/issues/727 | |
514 stream_.DisableKeepAlive(); | |
515 header += "Connection: close\r\n"; | |
516 # else | |
517 // The function "mg_disable_keep_alive()" is not available, | |
518 // let's continue with Keep-Alive. Performance of WADO-RS will | |
519 // decrease. | |
520 header += "Connection: keep-alive\r\n"; | |
521 # endif | |
522 | |
523 #else | |
524 # error Please support your embedded Web server here | |
525 #endif | |
526 } | |
527 else | |
528 { | |
529 header += "Connection: close\r\n"; | |
530 } | |
531 | |
532 // Possibly add the cookies | |
533 CheckHeadersCompatibilityWithMultipart(); | |
534 | |
535 for (std::list<std::string>::const_iterator | |
536 it = headers_.begin(); it != headers_.end(); ++it) | |
537 { | |
538 header += *it; | |
539 } | |
540 | |
541 std::string contentTypeHeader; | |
542 PrepareMultipartMainHeader(multipartBoundary_, contentTypeHeader, subType, contentType); | |
543 multipartContentType_ = contentType; | |
544 header += ("Content-Type: " + contentTypeHeader + "\r\n\r\n"); | |
545 | |
546 stream_.Send(true, header.c_str(), header.size()); | |
547 state_ = State_WritingMultipart; | |
548 } | |
549 | |
550 | |
551 static void PrepareMultipartItemHeader(std::string& target, | |
552 size_t length, | |
553 const std::map<std::string, std::string>& headers, | |
554 const std::string& boundary, | |
555 const std::string& contentType) | |
556 { | |
557 target = "--" + boundary + "\r\n"; | |
558 | |
559 bool hasContentType = false; | |
560 bool hasContentLength = false; | |
561 bool hasMimeVersion = false; | |
562 | |
563 for (std::map<std::string, std::string>::const_iterator | |
564 it = headers.begin(); it != headers.end(); ++it) | |
565 { | |
566 target += it->first + ": " + it->second + "\r\n"; | |
567 | |
568 std::string tmp; | |
569 Toolbox::ToLowerCase(tmp, it->first); | |
570 | |
571 if (tmp == "content-type") | |
572 { | |
573 hasContentType = true; | |
574 } | |
575 | |
576 if (tmp == "content-length") | |
577 { | |
578 hasContentLength = true; | |
579 } | |
580 | |
581 if (tmp == "mime-version") | |
582 { | |
583 hasMimeVersion = true; | |
584 } | |
585 } | |
586 | |
587 if (!hasContentType) | |
588 { | |
589 target += "Content-Type: " + contentType + "\r\n"; | |
590 } | |
591 | |
592 if (!hasContentLength) | |
593 { | |
594 target += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\r\n"; | |
595 } | |
596 | |
597 if (!hasMimeVersion) | |
598 { | |
599 target += "MIME-Version: 1.0\r\n\r\n"; | |
600 } | |
601 } | |
602 | |
603 | |
604 void HttpOutput::StateMachine::SendMultipartItem(const void* item, | |
605 size_t length, | |
606 const std::map<std::string, std::string>& headers) | |
607 { | |
608 if (state_ != State_WritingMultipart) | |
609 { | |
610 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
611 } | |
612 | |
613 std::string header; | |
614 PrepareMultipartItemHeader(header, length, headers, multipartBoundary_, multipartContentType_); | |
615 stream_.Send(false, header.c_str(), header.size()); | |
616 | |
617 if (length > 0) | |
618 { | |
619 stream_.Send(false, item, length); | |
620 } | |
621 | |
622 stream_.Send(false, "\r\n", 2); | |
623 } | |
624 | |
625 | |
626 void HttpOutput::StateMachine::CloseMultipart() | |
627 { | |
628 if (state_ != State_WritingMultipart) | |
629 { | |
630 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
631 } | |
632 | |
633 // The two lines below might throw an exception, if the client has | |
634 // closed the connection. Such an error is ignored. | |
635 try | |
636 { | |
637 std::string header = "--" + multipartBoundary_ + "--\r\n"; | |
638 stream_.Send(false, header.c_str(), header.size()); | |
639 } | |
640 catch (OrthancException&) | |
641 { | |
642 } | |
643 | |
644 state_ = State_Done; | |
645 } | |
646 | |
647 | |
648 static void AnswerStreamAsBuffer(HttpOutput& output, | |
649 IHttpStreamAnswer& stream) | |
650 { | |
651 ChunkedBuffer buffer; | |
652 | |
653 while (stream.ReadNextChunk()) | |
654 { | |
655 if (stream.GetChunkSize() > 0) | |
656 { | |
657 buffer.AddChunk(stream.GetChunkContent(), stream.GetChunkSize()); | |
658 } | |
659 } | |
660 | |
661 std::string s; | |
662 buffer.Flatten(s); | |
663 | |
664 output.SetContentType(stream.GetContentType()); | |
665 | |
666 std::string filename; | |
667 if (stream.HasContentFilename(filename)) | |
668 { | |
669 output.SetContentFilename(filename.c_str()); | |
670 } | |
671 | |
672 output.Answer(s); | |
673 } | |
674 | |
675 | |
676 void HttpOutput::Answer(IHttpStreamAnswer& stream) | |
677 { | |
678 HttpCompression compression = stream.SetupHttpCompression(isGzipAllowed_, isDeflateAllowed_); | |
679 | |
680 switch (compression) | |
681 { | |
682 case HttpCompression_None: | |
683 { | |
684 if (isGzipAllowed_ || isDeflateAllowed_) | |
685 { | |
686 // New in Orthanc 1.5.7: Compress streams without built-in | |
687 // compression, if requested by the "Accept-Encoding" HTTP | |
688 // header | |
689 AnswerStreamAsBuffer(*this, stream); | |
690 return; | |
691 } | |
692 | |
693 break; | |
694 } | |
695 | |
696 case HttpCompression_Gzip: | |
697 stateMachine_.AddHeader("Content-Encoding", "gzip"); | |
698 break; | |
699 | |
700 case HttpCompression_Deflate: | |
701 stateMachine_.AddHeader("Content-Encoding", "deflate"); | |
702 break; | |
703 | |
704 default: | |
705 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
706 } | |
707 | |
708 stateMachine_.SetContentLength(stream.GetContentLength()); | |
709 | |
710 std::string contentType = stream.GetContentType(); | |
711 if (contentType.empty()) | |
712 { | |
713 contentType = MIME_BINARY; | |
714 } | |
715 | |
716 stateMachine_.SetContentType(contentType.c_str()); | |
717 | |
718 std::string filename; | |
719 if (stream.HasContentFilename(filename)) | |
720 { | |
721 SetContentFilename(filename.c_str()); | |
722 } | |
723 | |
724 while (stream.ReadNextChunk()) | |
725 { | |
726 stateMachine_.SendBody(stream.GetChunkContent(), | |
727 stream.GetChunkSize()); | |
728 } | |
729 | |
730 stateMachine_.CloseBody(); | |
731 } | |
732 | |
733 | |
734 void HttpOutput::AnswerMultipartWithoutChunkedTransfer( | |
735 const std::string& subType, | |
736 const std::string& contentType, | |
737 const std::vector<const void*>& parts, | |
738 const std::vector<size_t>& sizes, | |
739 const std::vector<const std::map<std::string, std::string>*>& headers) | |
740 { | |
741 if (parts.size() != sizes.size()) | |
742 { | |
743 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
744 } | |
745 | |
746 stateMachine_.CheckHeadersCompatibilityWithMultipart(); | |
747 | |
748 std::string boundary, contentTypeHeader; | |
749 PrepareMultipartMainHeader(boundary, contentTypeHeader, subType, contentType); | |
750 SetContentType(contentTypeHeader); | |
751 | |
752 std::map<std::string, std::string> empty; | |
753 | |
754 ChunkedBuffer chunked; | |
755 for (size_t i = 0; i < parts.size(); i++) | |
756 { | |
757 std::string partHeader; | |
758 PrepareMultipartItemHeader(partHeader, sizes[i], headers[i] == NULL ? empty : *headers[i], | |
759 boundary, contentType); | |
760 | |
761 chunked.AddChunk(partHeader); | |
762 chunked.AddChunk(parts[i], sizes[i]); | |
763 chunked.AddChunk("\r\n"); | |
764 } | |
765 | |
766 chunked.AddChunk("--" + boundary + "--\r\n"); | |
767 | |
768 std::string body; | |
769 chunked.Flatten(body); | |
770 Answer(body); | |
771 } | |
772 } |