Mercurial > hg > orthanc
comparison Core/HttpServer/MongooseServer.cpp @ 0:3959d33612cc
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 19 Jul 2012 14:32:22 +0200 |
parents | |
children | 3a584803783e |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:3959d33612cc |
---|---|
1 /** | |
2 * Palantir - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012 Medical Physics Department, CHU of Liege, | |
4 * Belgium | |
5 * | |
6 * This program is free software: you can redistribute it and/or | |
7 * modify it under the terms of the GNU General Public License as | |
8 * published by the Free Software Foundation, either version 3 of the | |
9 * License, or (at your option) any later version. | |
10 * | |
11 * This program is distributed in the hope that it will be useful, but | |
12 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 * General Public License for more details. | |
15 * | |
16 * You should have received a copy of the GNU General Public License | |
17 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 **/ | |
19 | |
20 | |
21 // http://en.highscore.de/cpp/boost/stringhandling.html | |
22 | |
23 #include "MongooseServer.h" | |
24 | |
25 #include <algorithm> | |
26 #include <string.h> | |
27 #include <boost/lexical_cast.hpp> | |
28 #include <boost/algorithm/string.hpp> | |
29 #include <iostream> | |
30 #include <string.h> | |
31 #include <stdio.h> | |
32 #include <boost/thread.hpp> | |
33 | |
34 #include "../PalantirException.h" | |
35 #include "../ChunkedBuffer.h" | |
36 #include "mongoose.h" | |
37 | |
38 | |
39 namespace Palantir | |
40 { | |
41 static const char multipart[] = "multipart/form-data; boundary="; | |
42 static unsigned int multipartLength = sizeof(multipart) / sizeof(char) - 1; | |
43 | |
44 | |
45 namespace | |
46 { | |
47 // Anonymous namespace to avoid clashes between compilation modules | |
48 class MongooseOutput : public HttpOutput | |
49 { | |
50 private: | |
51 struct mg_connection* connection_; | |
52 | |
53 public: | |
54 MongooseOutput(struct mg_connection* connection) : connection_(connection) | |
55 { | |
56 } | |
57 | |
58 virtual void Send(const void* buffer, size_t length) | |
59 { | |
60 mg_write(connection_, buffer, length); | |
61 } | |
62 }; | |
63 | |
64 | |
65 enum PostDataStatus | |
66 { | |
67 PostDataStatus_Success, | |
68 PostDataStatus_NoLength, | |
69 PostDataStatus_Pending, | |
70 PostDataStatus_Failure | |
71 }; | |
72 } | |
73 | |
74 | |
75 // TODO Move this to external file | |
76 | |
77 | |
78 class ChunkedFile : public ChunkedBuffer | |
79 { | |
80 private: | |
81 std::string filename_; | |
82 | |
83 public: | |
84 ChunkedFile(const std::string& filename) : | |
85 filename_(filename) | |
86 { | |
87 } | |
88 | |
89 const std::string& GetFilename() const | |
90 { | |
91 return filename_; | |
92 } | |
93 }; | |
94 | |
95 | |
96 | |
97 class ChunkStore | |
98 { | |
99 private: | |
100 typedef std::list<ChunkedFile*> Content; | |
101 Content content_; | |
102 unsigned int numPlaces_; | |
103 | |
104 boost::mutex mutex_; | |
105 std::set<std::string> discardedFiles_; | |
106 | |
107 void Clear() | |
108 { | |
109 for (Content::iterator it = content_.begin(); | |
110 it != content_.end(); it++) | |
111 { | |
112 delete *it; | |
113 } | |
114 } | |
115 | |
116 Content::iterator Find(const std::string& filename) | |
117 { | |
118 for (Content::iterator it = content_.begin(); | |
119 it != content_.end(); it++) | |
120 { | |
121 if ((*it)->GetFilename() == filename) | |
122 { | |
123 return it; | |
124 } | |
125 } | |
126 | |
127 return content_.end(); | |
128 } | |
129 | |
130 void Remove(const std::string& filename) | |
131 { | |
132 Content::iterator it = Find(filename); | |
133 if (it != content_.end()) | |
134 { | |
135 delete *it; | |
136 content_.erase(it); | |
137 } | |
138 } | |
139 | |
140 public: | |
141 ChunkStore() | |
142 { | |
143 numPlaces_ = 10; | |
144 } | |
145 | |
146 ~ChunkStore() | |
147 { | |
148 Clear(); | |
149 } | |
150 | |
151 PostDataStatus Store(std::string& completed, | |
152 const char* chunkData, | |
153 size_t chunkSize, | |
154 const std::string& filename, | |
155 size_t filesize) | |
156 { | |
157 boost::mutex::scoped_lock lock(mutex_); | |
158 | |
159 std::set<std::string>::iterator wasDiscarded = discardedFiles_.find(filename); | |
160 if (wasDiscarded != discardedFiles_.end()) | |
161 { | |
162 discardedFiles_.erase(wasDiscarded); | |
163 return PostDataStatus_Failure; | |
164 } | |
165 | |
166 ChunkedFile* f; | |
167 Content::iterator it = Find(filename); | |
168 if (it == content_.end()) | |
169 { | |
170 f = new ChunkedFile(filename); | |
171 | |
172 // Make some room | |
173 if (content_.size() >= numPlaces_) | |
174 { | |
175 discardedFiles_.insert(content_.front()->GetFilename()); | |
176 delete content_.front(); | |
177 content_.pop_front(); | |
178 } | |
179 | |
180 content_.push_back(f); | |
181 } | |
182 else | |
183 { | |
184 f = *it; | |
185 } | |
186 | |
187 f->AddChunk(chunkData, chunkSize); | |
188 | |
189 if (f->GetNumBytes() > filesize) | |
190 { | |
191 Remove(filename); | |
192 } | |
193 else if (f->GetNumBytes() == filesize) | |
194 { | |
195 f->Flatten(completed); | |
196 Remove(filename); | |
197 return PostDataStatus_Success; | |
198 } | |
199 | |
200 return PostDataStatus_Pending; | |
201 } | |
202 | |
203 /*void Print() | |
204 { | |
205 boost::mutex::scoped_lock lock(mutex_); | |
206 | |
207 printf("ChunkStore status:\n"); | |
208 for (Content::const_iterator i = content_.begin(); | |
209 i != content_.end(); i++) | |
210 { | |
211 printf(" [%s]: %d\n", (*i)->GetFilename().c_str(), (*i)->GetNumBytes()); | |
212 } | |
213 printf("-----\n"); | |
214 }*/ | |
215 }; | |
216 | |
217 | |
218 struct MongooseServer::PImpl | |
219 { | |
220 struct mg_context *context_; | |
221 ChunkStore chunkStore_; | |
222 }; | |
223 | |
224 | |
225 ChunkStore& MongooseServer::GetChunkStore() | |
226 { | |
227 return pimpl_->chunkStore_; | |
228 } | |
229 | |
230 | |
231 | |
232 HttpHandler* MongooseServer::FindHandler(const UriComponents& forUri) const | |
233 { | |
234 for (Handlers::const_iterator it = | |
235 handlers_.begin(); it != handlers_.end(); it++) | |
236 { | |
237 if ((*it)->IsServedUri(forUri)) | |
238 { | |
239 return *it; | |
240 } | |
241 } | |
242 | |
243 return NULL; | |
244 } | |
245 | |
246 | |
247 | |
248 | |
249 static PostDataStatus ReadPostData(std::string& postData, | |
250 struct mg_connection *connection, | |
251 const HttpHandler::Arguments& headers) | |
252 { | |
253 HttpHandler::Arguments::const_iterator cs = headers.find("content-length"); | |
254 if (cs == headers.end()) | |
255 { | |
256 return PostDataStatus_NoLength; | |
257 } | |
258 | |
259 int length; | |
260 try | |
261 { | |
262 length = boost::lexical_cast<int>(cs->second); | |
263 } | |
264 catch (boost::bad_lexical_cast) | |
265 { | |
266 return PostDataStatus_NoLength; | |
267 } | |
268 | |
269 if (length < 0) | |
270 { | |
271 length = 0; | |
272 } | |
273 | |
274 postData.resize(length); | |
275 | |
276 size_t pos = 0; | |
277 while (length > 0) | |
278 { | |
279 int r = mg_read(connection, &postData[pos], length); | |
280 if (r <= 0) | |
281 { | |
282 return PostDataStatus_Failure; | |
283 } | |
284 assert((unsigned int) r <= length); | |
285 length -= r; | |
286 pos += r; | |
287 } | |
288 | |
289 return PostDataStatus_Success; | |
290 } | |
291 | |
292 | |
293 | |
294 static PostDataStatus ParseMultipartPost(std::string &completedFile, | |
295 struct mg_connection *connection, | |
296 const HttpHandler::Arguments& headers, | |
297 const std::string& contentType, | |
298 ChunkStore& chunkStore) | |
299 { | |
300 std::string boundary = "--" + contentType.substr(multipartLength); | |
301 | |
302 std::string postData; | |
303 PostDataStatus status = ReadPostData(postData, connection, headers); | |
304 | |
305 if (status != PostDataStatus_Success) | |
306 { | |
307 return status; | |
308 } | |
309 | |
310 /*for (HttpHandler::Arguments::const_iterator i = headers.begin(); i != headers.end(); i++) | |
311 { | |
312 std::cout << "Header [" << i->first << "] = " << i->second << "\n"; | |
313 } | |
314 printf("CHUNK\n");*/ | |
315 | |
316 typedef HttpHandler::Arguments::const_iterator ArgumentIterator; | |
317 | |
318 ArgumentIterator requestedWith = headers.find("x-requested-with"); | |
319 ArgumentIterator fileName = headers.find("x-file-name"); | |
320 ArgumentIterator fileSizeStr = headers.find("x-file-size"); | |
321 | |
322 if (requestedWith == headers.end() || | |
323 requestedWith->second != "XMLHttpRequest") | |
324 { | |
325 return PostDataStatus_Failure; | |
326 } | |
327 | |
328 size_t fileSize = 0; | |
329 if (fileSizeStr != headers.end()) | |
330 { | |
331 try | |
332 { | |
333 fileSize = boost::lexical_cast<size_t>(fileSizeStr->second); | |
334 } | |
335 catch (boost::bad_lexical_cast) | |
336 { | |
337 return PostDataStatus_Failure; | |
338 } | |
339 } | |
340 | |
341 typedef boost::find_iterator<std::string::iterator> FindIterator; | |
342 typedef boost::iterator_range<std::string::iterator> Range; | |
343 | |
344 //chunkStore.Print(); | |
345 | |
346 try | |
347 { | |
348 FindIterator last; | |
349 for (FindIterator it = | |
350 make_find_iterator(postData, boost::first_finder(boundary)); | |
351 it!=FindIterator(); | |
352 ++it) | |
353 { | |
354 if (last != FindIterator()) | |
355 { | |
356 Range part(&last->back(), &it->front()); | |
357 Range content = boost::find_first(part, "\r\n\r\n"); | |
358 if (content != Range()) | |
359 { | |
360 Range c(&content.back() + 1, &it->front() - 2); | |
361 size_t chunkSize = c.size(); | |
362 | |
363 if (chunkSize > 0) | |
364 { | |
365 const char* chunkData = &c.front(); | |
366 | |
367 if (fileName == headers.end()) | |
368 { | |
369 // This file is stored in a single chunk | |
370 completedFile.resize(chunkSize); | |
371 if (chunkSize > 0) | |
372 { | |
373 memcpy(&completedFile[0], chunkData, chunkSize); | |
374 } | |
375 return PostDataStatus_Success; | |
376 } | |
377 else | |
378 { | |
379 return chunkStore.Store(completedFile, chunkData, chunkSize, fileName->second, fileSize); | |
380 } | |
381 } | |
382 } | |
383 } | |
384 | |
385 last = it; | |
386 } | |
387 } | |
388 catch (std::length_error) | |
389 { | |
390 return PostDataStatus_Failure; | |
391 } | |
392 | |
393 return PostDataStatus_Pending; | |
394 } | |
395 | |
396 | |
397 | |
398 static void* Callback(enum mg_event event, | |
399 struct mg_connection *connection, | |
400 const struct mg_request_info *request) | |
401 { | |
402 if (event == MG_NEW_REQUEST) | |
403 { | |
404 MongooseServer* that = (MongooseServer*) (request->user_data); | |
405 | |
406 HttpHandler::Arguments arguments, headers; | |
407 MongooseOutput c(connection); | |
408 | |
409 for (int i = 0; i < request->num_headers; i++) | |
410 { | |
411 std::string name = request->http_headers[i].name; | |
412 std::transform(name.begin(), name.end(), name.begin(), ::tolower); | |
413 headers.insert(std::make_pair(name, request->http_headers[i].value)); | |
414 } | |
415 | |
416 std::string postData; | |
417 | |
418 if (!strcmp(request->request_method, "GET")) | |
419 { | |
420 HttpHandler::ParseGetQuery(arguments, request->query_string); | |
421 } | |
422 else if (!strcmp(request->request_method, "POST")) | |
423 { | |
424 HttpHandler::Arguments::const_iterator ct = headers.find("content-type"); | |
425 if (ct == headers.end()) | |
426 { | |
427 c.SendHeader(HttpStatus_400_BadRequest); | |
428 return (void*) ""; | |
429 } | |
430 | |
431 PostDataStatus status; | |
432 | |
433 std::string contentType = ct->second; | |
434 if (contentType.size() >= multipartLength && | |
435 !memcmp(contentType.c_str(), multipart, multipartLength)) | |
436 { | |
437 status = ParseMultipartPost(postData, connection, headers, contentType, that->GetChunkStore()); | |
438 } | |
439 else | |
440 { | |
441 status = ReadPostData(postData, connection, headers); | |
442 } | |
443 | |
444 switch (status) | |
445 { | |
446 case PostDataStatus_NoLength: | |
447 c.SendHeader(HttpStatus_411_LengthRequired); | |
448 return (void*) ""; | |
449 | |
450 case PostDataStatus_Failure: | |
451 c.SendHeader(HttpStatus_400_BadRequest); | |
452 return (void*) ""; | |
453 | |
454 case PostDataStatus_Pending: | |
455 c.AnswerBuffer(""); | |
456 return (void*) ""; | |
457 | |
458 default: | |
459 break; | |
460 } | |
461 } | |
462 | |
463 UriComponents uri; | |
464 Toolbox::SplitUriComponents(uri, request->uri); | |
465 | |
466 HttpHandler* handler = that->FindHandler(uri); | |
467 if (handler) | |
468 { | |
469 try | |
470 { | |
471 handler->Handle(c, std::string(request->request_method), | |
472 uri, headers, arguments, postData); | |
473 } | |
474 catch (PalantirException& e) | |
475 { | |
476 std::cerr << "MongooseServer Exception [" << e.What() << "]" << std::endl; | |
477 c.SendHeader(HttpStatus_500_InternalServerError); | |
478 } | |
479 } | |
480 else | |
481 { | |
482 c.SendHeader(HttpStatus_404_NotFound); | |
483 } | |
484 | |
485 // Mark as processed | |
486 return (void*) ""; | |
487 } | |
488 else | |
489 { | |
490 return NULL; | |
491 } | |
492 } | |
493 | |
494 | |
495 bool MongooseServer::IsRunning() const | |
496 { | |
497 return (pimpl_->context_ != NULL); | |
498 } | |
499 | |
500 | |
501 MongooseServer::MongooseServer() : pimpl_(new PImpl) | |
502 { | |
503 pimpl_->context_ = NULL; | |
504 port_ = 8000; | |
505 } | |
506 | |
507 | |
508 MongooseServer::~MongooseServer() | |
509 { | |
510 Stop(); | |
511 ClearHandlers(); | |
512 } | |
513 | |
514 | |
515 void MongooseServer::SetPort(uint16_t port) | |
516 { | |
517 Stop(); | |
518 port_ = port; | |
519 } | |
520 | |
521 void MongooseServer::Start() | |
522 { | |
523 if (!IsRunning()) | |
524 { | |
525 std::string port = boost::lexical_cast<std::string>(port_); | |
526 | |
527 const char *options[] = { | |
528 "listening_ports", port.c_str(), | |
529 NULL | |
530 }; | |
531 | |
532 pimpl_->context_ = mg_start(&Callback, this, options); | |
533 if (!pimpl_->context_) | |
534 { | |
535 throw PalantirException("Unable to launch the Mongoose server"); | |
536 } | |
537 } | |
538 } | |
539 | |
540 void MongooseServer::Stop() | |
541 { | |
542 if (IsRunning()) | |
543 { | |
544 mg_stop(pimpl_->context_); | |
545 pimpl_->context_ = NULL; | |
546 } | |
547 } | |
548 | |
549 | |
550 void MongooseServer::RegisterHandler(HttpHandler* handler) | |
551 { | |
552 Stop(); | |
553 | |
554 handlers_.push_back(handler); | |
555 } | |
556 | |
557 | |
558 void MongooseServer::ClearHandlers() | |
559 { | |
560 Stop(); | |
561 | |
562 for (Handlers::iterator it = | |
563 handlers_.begin(); it != handlers_.end(); it++) | |
564 { | |
565 delete *it; | |
566 } | |
567 } | |
568 | |
569 } |