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 }