0
|
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 }
|
8
|
284 assert(r <= length);
|
0
|
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;
|
10
|
342 typedef boost::iterator_range<char*> Range;
|
0
|
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 {
|
10
|
356 Range part(&last->back(), &it->front());
|
0
|
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 }
|
10
|
382 }
|
0
|
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 }
|