Mercurial > hg > orthanc
comparison UnitTestsSources/RestApiTests.cpp @ 1783:dbb07eb1a2f3
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 18 Nov 2015 09:15:30 +0100 |
parents | 9f2df9bb2cdf |
children | b1291df2f780 |
comparison
equal
deleted
inserted
replaced
1782:9f2df9bb2cdf | 1783:dbb07eb1a2f3 |
---|---|
43 #include "../Core/RestApi/RestApi.h" | 43 #include "../Core/RestApi/RestApi.h" |
44 #include "../Core/Uuid.h" | 44 #include "../Core/Uuid.h" |
45 #include "../Core/OrthancException.h" | 45 #include "../Core/OrthancException.h" |
46 #include "../Core/Compression/ZlibCompressor.h" | 46 #include "../Core/Compression/ZlibCompressor.h" |
47 #include "../Core/RestApi/RestApiHierarchy.h" | 47 #include "../Core/RestApi/RestApiHierarchy.h" |
48 #include "../Core/HttpServer/HttpContentNegociation.h" | |
48 | 49 |
49 using namespace Orthanc; | 50 using namespace Orthanc; |
50 | 51 |
51 #if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS) | 52 #if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS) |
52 #error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS" | 53 #error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS" |
339 } | 340 } |
340 | 341 |
341 | 342 |
342 | 343 |
343 | 344 |
344 namespace Orthanc | |
345 { | |
346 class HttpContentNegociation : public boost::noncopyable | |
347 { | |
348 public: | |
349 typedef std::map<std::string, std::string> HttpHeaders; | |
350 | |
351 class IHandler : public boost::noncopyable | |
352 { | |
353 public: | |
354 virtual ~IHandler() | |
355 { | |
356 } | |
357 | |
358 virtual void Handle(const std::string& type, | |
359 const std::string& subtype) = 0; | |
360 }; | |
361 | |
362 private: | |
363 struct Handler | |
364 { | |
365 std::string type_; | |
366 std::string subtype_; | |
367 IHandler& handler_; | |
368 | |
369 Handler(const std::string& type, | |
370 const std::string& subtype, | |
371 IHandler& handler) : | |
372 type_(type), | |
373 subtype_(subtype), | |
374 handler_(handler) | |
375 { | |
376 } | |
377 | |
378 bool IsMatch(const std::string& type, | |
379 const std::string& subtype) const | |
380 { | |
381 if (type == "*" && subtype == "*") | |
382 { | |
383 return true; | |
384 } | |
385 | |
386 if (subtype == "*" && type == type_) | |
387 { | |
388 return true; | |
389 } | |
390 | |
391 return type == type_ && subtype == subtype_; | |
392 } | |
393 | |
394 void Call() const | |
395 { | |
396 handler_.Handle(type_, subtype_); | |
397 } | |
398 }; | |
399 | |
400 | |
401 struct Reference : public boost::noncopyable | |
402 { | |
403 const Handler& handler_; | |
404 uint8_t level_; | |
405 float quality_; | |
406 | |
407 Reference(const Handler& handler, | |
408 const std::string& type, | |
409 const std::string& subtype, | |
410 float quality) : | |
411 handler_(handler), | |
412 quality_(quality) | |
413 { | |
414 if (type == "*" && subtype == "*") | |
415 { | |
416 level_ = 0; | |
417 } | |
418 else if (subtype == "*") | |
419 { | |
420 level_ = 1; | |
421 } | |
422 else | |
423 { | |
424 level_ = 2; | |
425 } | |
426 } | |
427 | |
428 bool operator< (const Reference& other) const | |
429 { | |
430 if (level_ < other.level_) | |
431 { | |
432 return true; | |
433 } | |
434 | |
435 if (level_ > other.level_) | |
436 { | |
437 return false; | |
438 } | |
439 | |
440 return quality_ < other.quality_; | |
441 } | |
442 }; | |
443 | |
444 | |
445 typedef std::vector<std::string> Tokens; | |
446 typedef std::list<Handler> Handlers; | |
447 | |
448 Handlers handlers_; | |
449 | |
450 | |
451 static bool SplitPair(std::string& first /* out */, | |
452 std::string& second /* out */, | |
453 const std::string& source, | |
454 char separator) | |
455 { | |
456 size_t pos = source.find(separator); | |
457 | |
458 if (pos == std::string::npos) | |
459 { | |
460 return false; | |
461 } | |
462 else | |
463 { | |
464 first = Toolbox::StripSpaces(source.substr(0, pos)); | |
465 second = Toolbox::StripSpaces(source.substr(pos + 1)); | |
466 return true; | |
467 } | |
468 } | |
469 | |
470 | |
471 static float GetQuality(const Tokens& parameters) | |
472 { | |
473 for (size_t i = 1; i < parameters.size(); i++) | |
474 { | |
475 std::string key, value; | |
476 if (SplitPair(key, value, parameters[i], '=') && | |
477 key == "q") | |
478 { | |
479 float quality; | |
480 bool ok = false; | |
481 | |
482 try | |
483 { | |
484 quality = boost::lexical_cast<float>(value); | |
485 ok = (quality >= 0.0f && quality <= 1.0f); | |
486 } | |
487 catch (boost::bad_lexical_cast&) | |
488 { | |
489 } | |
490 | |
491 if (ok) | |
492 { | |
493 return quality; | |
494 } | |
495 else | |
496 { | |
497 LOG(ERROR) << "Quality parameter out of range in a HTTP request (must be between 0 and 1): " << value; | |
498 throw OrthancException(ErrorCode_BadRequest); | |
499 } | |
500 } | |
501 } | |
502 | |
503 return 1.0f; // Default quality | |
504 } | |
505 | |
506 | |
507 static void SelectBestMatch(std::auto_ptr<Reference>& best, | |
508 const Handler& handler, | |
509 const std::string& type, | |
510 const std::string& subtype, | |
511 float quality) | |
512 { | |
513 std::auto_ptr<Reference> match(new Reference(handler, type, subtype, quality)); | |
514 | |
515 if (best.get() == NULL || | |
516 *best < *match) | |
517 { | |
518 best = match; | |
519 } | |
520 } | |
521 | |
522 | |
523 public: | |
524 void Register(const std::string& mime, | |
525 IHandler& handler) | |
526 { | |
527 std::string type, subtype; | |
528 | |
529 if (SplitPair(type, subtype, mime, '/') && | |
530 type != "*" && | |
531 subtype != "*") | |
532 { | |
533 handlers_.push_back(Handler(type, subtype, handler)); | |
534 } | |
535 else | |
536 { | |
537 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
538 } | |
539 } | |
540 | |
541 | |
542 bool Apply(const HttpHeaders& headers) | |
543 { | |
544 HttpHeaders::const_iterator accept = headers.find("accept"); | |
545 if (accept != headers.end()) | |
546 { | |
547 return Apply(accept->second); | |
548 } | |
549 else | |
550 { | |
551 return Apply("*/*"); | |
552 } | |
553 } | |
554 | |
555 | |
556 bool Apply(const std::string& accept) | |
557 { | |
558 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 | |
559 // https://en.wikipedia.org/wiki/Content_negotiation | |
560 // http://www.newmediacampaigns.com/blog/browser-rest-http-accept-headers | |
561 | |
562 Tokens mediaRanges; | |
563 Toolbox::TokenizeString(mediaRanges, accept, ','); | |
564 | |
565 std::auto_ptr<Reference> bestMatch; | |
566 | |
567 for (Tokens::const_iterator it = mediaRanges.begin(); | |
568 it != mediaRanges.end(); ++it) | |
569 { | |
570 Tokens parameters; | |
571 Toolbox::TokenizeString(parameters, *it, ';'); | |
572 | |
573 if (parameters.size() > 0) | |
574 { | |
575 float quality = GetQuality(parameters); | |
576 | |
577 std::string type, subtype; | |
578 if (SplitPair(type, subtype, parameters[0], '/')) | |
579 { | |
580 for (Handlers::const_iterator it2 = handlers_.begin(); | |
581 it2 != handlers_.end(); ++it2) | |
582 { | |
583 if (it2->IsMatch(type, subtype)) | |
584 { | |
585 SelectBestMatch(bestMatch, *it2, type, subtype, quality); | |
586 } | |
587 } | |
588 } | |
589 } | |
590 } | |
591 | |
592 if (bestMatch.get() == NULL) // No match was found | |
593 { | |
594 return false; | |
595 } | |
596 else | |
597 { | |
598 bestMatch->handler_.Call(); | |
599 return true; | |
600 } | |
601 } | |
602 }; | |
603 } | |
604 | |
605 | |
606 | 345 |
607 namespace | 346 namespace |
608 { | 347 { |
609 class AcceptHandler : public Orthanc::HttpContentNegociation::IHandler | 348 class AcceptHandler : public Orthanc::HttpContentNegociation::IHandler |
610 { | 349 { |