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 }