Mercurial > hg > orthanc
comparison Core/HttpServer/HttpOutput.cpp @ 1113:ba5c0908600c
Refactoring of HttpOutput ("Content-Length" header is now always sent)
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 02 Sep 2014 15:51:20 +0200 |
parents | 8d1845feb277 |
children | da56a7916e8a |
comparison
equal
deleted
inserted
replaced
1112:a119f9ae3640 | 1113:ba5c0908600c |
---|---|
41 #include "../OrthancException.h" | 41 #include "../OrthancException.h" |
42 #include "../Toolbox.h" | 42 #include "../Toolbox.h" |
43 | 43 |
44 namespace Orthanc | 44 namespace Orthanc |
45 { | 45 { |
46 void HttpOutput::StateMachine::SendHttpStatus(HttpStatus status) | 46 HttpOutput::StateMachine::StateMachine(IHttpOutputStream& stream) : |
47 { | 47 stream_(stream), |
48 if (state_ != State_WaitingHttpStatus) | 48 state_(State_WritingHeader), |
49 { | 49 status_(HttpStatus_200_Ok), |
50 LOG(ERROR) << "Sending twice an HTTP status"; | 50 hasContentLength_(false), |
51 return; | 51 contentPosition_(0) |
52 } | 52 { |
53 | 53 } |
54 stream_.OnHttpStatusReceived(status); | 54 |
55 state_ = State_WritingHeader; | 55 HttpOutput::StateMachine::~StateMachine() |
56 | 56 { |
57 std::string s = "HTTP/1.1 " + | 57 if (state_ != State_Done) |
58 boost::lexical_cast<std::string>(status) + | 58 { |
59 " " + std::string(EnumerationToString(status)) + | 59 //asm volatile ("int3;"); |
60 "\r\n"; | 60 LOG(ERROR) << "This HTTP answer does not contain any body"; |
61 | 61 } |
62 stream_.Send(true, &s[0], s.size()); | 62 |
63 } | 63 if (hasContentLength_ && contentPosition_ != contentLength_) |
64 | 64 { |
65 void HttpOutput::StateMachine::SendHeaderData(const void* buffer, size_t length) | 65 LOG(ERROR) << "This HTTP answer has not sent the proper number of bytes in its body"; |
66 { | 66 } |
67 if (state_ != State_WritingHeader) | 67 } |
68 { | 68 |
69 throw OrthancException(ErrorCode_BadSequenceOfCalls); | 69 |
70 } | 70 void HttpOutput::StateMachine::SetHttpStatus(HttpStatus status) |
71 | 71 { |
72 stream_.Send(true, buffer, length); | 72 if (state_ != State_WritingHeader) |
73 } | 73 { |
74 | 74 throw OrthancException(ErrorCode_BadSequenceOfCalls); |
75 void HttpOutput::StateMachine::SendHeaderString(const std::string& str) | 75 } |
76 { | 76 |
77 if (str.size() > 0) | 77 status_ = status; |
78 { | 78 } |
79 SendHeaderData(&str[0], str.size()); | 79 |
80 } | 80 |
81 } | 81 void HttpOutput::StateMachine::SetContentLength(uint64_t length) |
82 | 82 { |
83 void HttpOutput::StateMachine::SendBodyData(const void* buffer, size_t length) | 83 if (state_ != State_WritingHeader) |
84 { | 84 { |
85 if (state_ == State_WaitingHttpStatus) | 85 throw OrthancException(ErrorCode_BadSequenceOfCalls); |
86 { | 86 } |
87 throw OrthancException(ErrorCode_BadSequenceOfCalls); | 87 |
88 hasContentLength_ = true; | |
89 contentLength_ = length; | |
90 } | |
91 | |
92 void HttpOutput::StateMachine::SetContentType(const char* contentType) | |
93 { | |
94 AddHeader("Content-Type", contentType); | |
95 } | |
96 | |
97 void HttpOutput::StateMachine::SetContentFilename(const char* filename) | |
98 { | |
99 // TODO Escape double quotes | |
100 AddHeader("Content-Disposition", "filename=\"" + std::string(filename) + "\""); | |
101 } | |
102 | |
103 void HttpOutput::StateMachine::SetCookie(const std::string& cookie, | |
104 const std::string& value) | |
105 { | |
106 if (state_ != State_WritingHeader) | |
107 { | |
108 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
109 } | |
110 | |
111 // TODO Escape "=" characters | |
112 AddHeader("Set-Cookie", cookie + "=" + value); | |
113 } | |
114 | |
115 | |
116 void HttpOutput::StateMachine::AddHeader(const std::string& header, | |
117 const std::string& value) | |
118 { | |
119 if (state_ != State_WritingHeader) | |
120 { | |
121 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
122 } | |
123 | |
124 headers_.push_back(header + ": " + value + "\r\n"); | |
125 } | |
126 | |
127 void HttpOutput::StateMachine::ClearHeaders() | |
128 { | |
129 if (state_ != State_WritingHeader) | |
130 { | |
131 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
132 } | |
133 | |
134 headers_.clear(); | |
135 } | |
136 | |
137 void HttpOutput::StateMachine::SendBody(const void* buffer, size_t length) | |
138 { | |
139 if (state_ == State_Done) | |
140 { | |
141 if (length == 0) | |
142 { | |
143 return; | |
144 } | |
145 else | |
146 { | |
147 LOG(ERROR) << "Because of keep-alive connections, the entire body must be sent at once or Content-Length must be given"; | |
148 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
149 } | |
88 } | 150 } |
89 | 151 |
90 if (state_ == State_WritingHeader) | 152 if (state_ == State_WritingHeader) |
91 { | 153 { |
92 // Close the HTTP header before writing the body | 154 // Send the HTTP header before writing the body |
93 stream_.Send(true, "\r\n", 2); | 155 |
156 stream_.OnHttpStatusReceived(status_); | |
157 | |
158 std::string s = "HTTP/1.1 " + | |
159 boost::lexical_cast<std::string>(status_) + | |
160 " " + std::string(EnumerationToString(status_)) + | |
161 "\r\n"; | |
162 | |
163 if (status_ != HttpStatus_200_Ok) | |
164 { | |
165 hasContentLength_ = false; | |
166 } | |
167 | |
168 for (std::list<std::string>::const_iterator | |
169 it = headers_.begin(); it != headers_.end(); ++it) | |
170 { | |
171 s += *it; | |
172 } | |
173 | |
174 uint64_t contentLength = (hasContentLength_ ? contentLength_ : length); | |
175 s += "Content-Length: " + boost::lexical_cast<std::string>(contentLength) + "\r\n\r\n"; | |
176 | |
177 stream_.Send(true, s.c_str(), s.size()); | |
94 state_ = State_WritingBody; | 178 state_ = State_WritingBody; |
95 } | 179 } |
96 | 180 |
181 if (hasContentLength_ && | |
182 contentPosition_ + length > contentLength_) | |
183 { | |
184 LOG(ERROR) << "The body size exceeds what was declared with SetContentSize()"; | |
185 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
186 } | |
187 | |
97 if (length > 0) | 188 if (length > 0) |
98 { | 189 { |
99 stream_.Send(false, buffer, length); | 190 stream_.Send(false, buffer, length); |
100 } | 191 contentPosition_ += length; |
101 } | 192 } |
102 | 193 |
103 void HttpOutput::StateMachine::SendBodyString(const std::string& str) | 194 if (!hasContentLength_ || |
104 { | 195 contentPosition_ == contentLength_) |
105 if (str.size() > 0) | 196 { |
106 { | 197 state_ = State_Done; |
107 SendBodyData(&str[0], str.size()); | 198 } |
108 } | |
109 } | |
110 | |
111 | |
112 void HttpOutput::PrepareOkHeader(Header& header, | |
113 const char* contentType, | |
114 bool hasContentLength, | |
115 uint64_t contentLength, | |
116 const char* contentFilename) | |
117 { | |
118 header.clear(); | |
119 | |
120 if (contentType && contentType[0] != '\0') | |
121 { | |
122 header.push_back(std::make_pair("Content-Type", std::string(contentType))); | |
123 } | |
124 | |
125 if (hasContentLength) | |
126 { | |
127 header.push_back(std::make_pair("Content-Length", boost::lexical_cast<std::string>(contentLength))); | |
128 } | |
129 | |
130 if (contentFilename && contentFilename[0] != '\0') | |
131 { | |
132 std::string attachment = "attachment; filename=\"" + std::string(contentFilename) + "\""; | |
133 header.push_back(std::make_pair("Content-Disposition", attachment)); | |
134 } | |
135 } | |
136 | |
137 void HttpOutput::SendOkHeader(const char* contentType, | |
138 bool hasContentLength, | |
139 uint64_t contentLength, | |
140 const char* contentFilename) | |
141 { | |
142 Header header; | |
143 PrepareOkHeader(header, contentType, hasContentLength, contentLength, contentFilename); | |
144 SendOkHeader(header); | |
145 } | |
146 | |
147 void HttpOutput::SendOkHeader(const Header& header) | |
148 { | |
149 stateMachine_.SendHttpStatus(HttpStatus_200_Ok); | |
150 | |
151 std::string s; | |
152 for (Header::const_iterator | |
153 it = header.begin(); it != header.end(); ++it) | |
154 { | |
155 s += it->first + ": " + it->second + "\r\n"; | |
156 } | |
157 | |
158 for (HttpHandler::Arguments::const_iterator | |
159 it = cookies_.begin(); it != cookies_.end(); ++it) | |
160 { | |
161 s += "Set-Cookie: " + it->first + "=" + it->second + "\r\n"; | |
162 } | |
163 | |
164 stateMachine_.SendHeaderString(s); | |
165 } | 199 } |
166 | 200 |
167 | 201 |
168 void HttpOutput::SendMethodNotAllowed(const std::string& allowed) | 202 void HttpOutput::SendMethodNotAllowed(const std::string& allowed) |
169 { | 203 { |
170 stateMachine_.SendHttpStatus(HttpStatus_405_MethodNotAllowed); | 204 stateMachine_.ClearHeaders(); |
171 stateMachine_.SendHeaderString("Allow: " + allowed + "\r\n"); | 205 stateMachine_.SetHttpStatus(HttpStatus_405_MethodNotAllowed); |
172 } | 206 stateMachine_.AddHeader("Allow", allowed); |
173 | 207 stateMachine_.SendBody(NULL, 0); |
174 | 208 } |
175 void HttpOutput::SendHeader(HttpStatus status) | 209 |
210 | |
211 void HttpOutput::SendStatus(HttpStatus status) | |
176 { | 212 { |
177 if (status == HttpStatus_200_Ok || | 213 if (status == HttpStatus_200_Ok || |
178 status == HttpStatus_301_MovedPermanently || | 214 status == HttpStatus_301_MovedPermanently || |
179 status == HttpStatus_401_Unauthorized || | 215 status == HttpStatus_401_Unauthorized || |
180 status == HttpStatus_405_MethodNotAllowed) | 216 status == HttpStatus_405_MethodNotAllowed) |
181 { | 217 { |
182 throw OrthancException("Please use the dedicated methods to this HTTP status code in HttpOutput"); | 218 LOG(ERROR) << "Please use the dedicated methods to this HTTP status code in HttpOutput"; |
219 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
183 } | 220 } |
184 | 221 |
185 stateMachine_.SendHttpStatus(status); | 222 stateMachine_.ClearHeaders(); |
186 } | 223 stateMachine_.SetHttpStatus(status); |
187 | 224 stateMachine_.SendBody(NULL, 0); |
188 | |
189 void HttpOutput::AnswerBufferWithContentType(const std::string& buffer, | |
190 const std::string& contentType) | |
191 { | |
192 Header header; | |
193 PrepareOkHeader(header, contentType.c_str(), true, buffer.size(), NULL); | |
194 SendOkHeader(header); | |
195 SendBodyString(buffer); | |
196 } | |
197 | |
198 | |
199 void HttpOutput::AnswerBufferWithContentType(const void* buffer, | |
200 size_t size, | |
201 const std::string& contentType) | |
202 { | |
203 Header header; | |
204 PrepareOkHeader(header, contentType.c_str(), true, size, NULL); | |
205 SendOkHeader(header); | |
206 SendBodyData(buffer, size); | |
207 } | 225 } |
208 | 226 |
209 | 227 |
210 void HttpOutput::Redirect(const std::string& path) | 228 void HttpOutput::Redirect(const std::string& path) |
211 { | 229 { |
212 stateMachine_.SendHttpStatus(HttpStatus_301_MovedPermanently); | 230 stateMachine_.ClearHeaders(); |
213 stateMachine_.SendHeaderString("Location: " + path + "\r\n"); | 231 stateMachine_.SetHttpStatus(HttpStatus_301_MovedPermanently); |
232 stateMachine_.AddHeader("Location", path); | |
233 stateMachine_.SendBody(NULL, 0); | |
214 } | 234 } |
215 | 235 |
216 | 236 |
217 void HttpOutput::SendUnauthorized(const std::string& realm) | 237 void HttpOutput::SendUnauthorized(const std::string& realm) |
218 { | 238 { |
219 stateMachine_.SendHttpStatus(HttpStatus_401_Unauthorized); | 239 stateMachine_.ClearHeaders(); |
220 stateMachine_.SendHeaderString("WWW-Authenticate: Basic realm=\"" + realm + "\"\r\n"); | 240 stateMachine_.SetHttpStatus(HttpStatus_401_Unauthorized); |
221 } | 241 stateMachine_.AddHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\""); |
222 | 242 stateMachine_.SendBody(NULL, 0); |
243 } | |
244 | |
245 void HttpOutput::SendBody(const void* buffer, size_t length) | |
246 { | |
247 stateMachine_.SendBody(buffer, length); | |
248 } | |
249 | |
250 void HttpOutput::SendBody(const std::string& str) | |
251 { | |
252 if (str.size() == 0) | |
253 { | |
254 stateMachine_.SendBody(NULL, 0); | |
255 } | |
256 else | |
257 { | |
258 stateMachine_.SendBody(str.c_str(), str.size()); | |
259 } | |
260 } | |
261 | |
262 void HttpOutput::SendBody() | |
263 { | |
264 stateMachine_.SendBody(NULL, 0); | |
265 } | |
223 } | 266 } |