Mercurial > hg > orthanc
comparison UnitTestsSources/RestApiTests.cpp @ 1781:5ad4e4d92ecb
AcceptMediaDispatcher bootstrap
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 17 Nov 2015 17:46:32 +0100 |
parents | 33d34bc4ac15 |
children | 9f2df9bb2cdf |
comparison
equal
deleted
inserted
replaced
1780:94990da8710e | 1781:5ad4e4d92ecb |
---|---|
32 | 32 |
33 #include "PrecompiledHeadersUnitTests.h" | 33 #include "PrecompiledHeadersUnitTests.h" |
34 #include "gtest/gtest.h" | 34 #include "gtest/gtest.h" |
35 | 35 |
36 #include <ctype.h> | 36 #include <ctype.h> |
37 #include <boost/lexical_cast.hpp> | |
38 #include <algorithm> | |
37 | 39 |
38 #include "../Core/ChunkedBuffer.h" | 40 #include "../Core/ChunkedBuffer.h" |
39 #include "../Core/HttpClient.h" | 41 #include "../Core/HttpClient.h" |
40 #include "../Core/Logging.h" | 42 #include "../Core/Logging.h" |
41 #include "../Core/RestApi/RestApi.h" | 43 #include "../Core/RestApi/RestApi.h" |
333 ASSERT_FALSE(HandleGet(root, "/hello/b/test3/test")); | 335 ASSERT_FALSE(HandleGet(root, "/hello/b/test3/test")); |
334 ASSERT_EQ(testValue, 3); | 336 ASSERT_EQ(testValue, 3); |
335 ASSERT_TRUE(HandleGet(root, "/hello2/a/b")); | 337 ASSERT_TRUE(HandleGet(root, "/hello2/a/b")); |
336 ASSERT_EQ(testValue, 4); | 338 ASSERT_EQ(testValue, 4); |
337 } | 339 } |
340 | |
341 | |
342 | |
343 | |
344 namespace Orthanc | |
345 { | |
346 class AcceptMediaDispatcher : 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, | |
360 float quality /* between 0 and 1 */) = 0; | |
361 }; | |
362 | |
363 private: | |
364 struct Handler | |
365 { | |
366 std::string type_; | |
367 std::string subtype_; | |
368 IHandler& handler_; | |
369 | |
370 Handler(const std::string& type, | |
371 const std::string& subtype, | |
372 IHandler& handler) : | |
373 type_(type), | |
374 subtype_(subtype), | |
375 handler_(handler) | |
376 { | |
377 } | |
378 | |
379 bool IsMatch(const std::string& type, | |
380 const std::string& subtype) const | |
381 { | |
382 if (type == "*" && subtype == "*") | |
383 { | |
384 return true; | |
385 } | |
386 | |
387 if (subtype == "*" && type == type_) | |
388 { | |
389 return true; | |
390 } | |
391 | |
392 return type == type_ && subtype == subtype_; | |
393 } | |
394 }; | |
395 | |
396 | |
397 struct Reference : public boost::noncopyable | |
398 { | |
399 const Handler& handler_; | |
400 uint8_t level_; | |
401 float quality_; | |
402 size_t specificity_; // Number of arguments | |
403 | |
404 Reference(const Handler& handler, | |
405 const std::string& type, | |
406 const std::string& subtype, | |
407 float quality, | |
408 size_t specificity) : | |
409 handler_(handler), | |
410 quality_(quality), | |
411 specificity_(specificity) | |
412 { | |
413 if (type == "*" && subtype == "*") | |
414 { | |
415 level_ = 0; | |
416 } | |
417 else if (subtype == "*") | |
418 { | |
419 level_ = 1; | |
420 } | |
421 else | |
422 { | |
423 level_ = 2; | |
424 } | |
425 } | |
426 | |
427 bool operator< (const Reference& other) const | |
428 { | |
429 if (level_ < other.level_) | |
430 { | |
431 return true; | |
432 } | |
433 | |
434 if (level_ > other.level_) | |
435 { | |
436 return false; | |
437 } | |
438 | |
439 return specificity_ < other.specificity_; | |
440 } | |
441 | |
442 void Call() const | |
443 { | |
444 handler_.handler_.Handle(handler_.type_, handler_.subtype_, quality_); | |
445 } | |
446 }; | |
447 | |
448 | |
449 typedef std::vector<std::string> Tokens; | |
450 typedef std::list<Handler> Handlers; | |
451 | |
452 Handlers handlers_; | |
453 | |
454 | |
455 static bool SplitPair(std::string& first /* out */, | |
456 std::string& second /* out */, | |
457 const std::string& source, | |
458 char separator) | |
459 { | |
460 size_t pos = source.find(separator); | |
461 | |
462 if (pos == std::string::npos) | |
463 { | |
464 return false; | |
465 } | |
466 else | |
467 { | |
468 first = Toolbox::StripSpaces(source.substr(0, pos)); | |
469 second = Toolbox::StripSpaces(source.substr(pos + 1)); | |
470 return true; | |
471 } | |
472 } | |
473 | |
474 | |
475 static void GetQualityAndSpecificity(float& quality /* out */, | |
476 size_t& specificity /* out */, | |
477 const Tokens& parameters) | |
478 { | |
479 assert(!parameters.empty()); | |
480 | |
481 quality = 1.0f; | |
482 specificity = parameters.size() - 1; | |
483 | |
484 for (size_t i = 1; i < parameters.size(); i++) | |
485 { | |
486 std::string key, value; | |
487 if (SplitPair(key, value, parameters[i], '=') && | |
488 key == "q") | |
489 { | |
490 bool ok = false; | |
491 | |
492 try | |
493 { | |
494 quality = boost::lexical_cast<float>(value); | |
495 ok = (quality >= 0.0f && quality <= 1.0f); | |
496 } | |
497 catch (boost::bad_lexical_cast&) | |
498 { | |
499 } | |
500 | |
501 if (ok) | |
502 { | |
503 assert(parameters.size() >= 2); | |
504 specificity = parameters.size() - 2; | |
505 return; | |
506 } | |
507 else | |
508 { | |
509 LOG(ERROR) << "Quality parameter out of range in a HTTP request (must be between 0 and 1): " << value; | |
510 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
511 } | |
512 } | |
513 } | |
514 } | |
515 | |
516 | |
517 static void SelectBestMatch(std::auto_ptr<Reference>& best, | |
518 const Handler& handler, | |
519 const std::string& type, | |
520 const std::string& subtype, | |
521 float quality, | |
522 size_t specificity) | |
523 { | |
524 std::auto_ptr<Reference> match(new Reference(handler, type, subtype, quality, specificity)); | |
525 | |
526 if (best.get() == NULL || | |
527 *best < *match) | |
528 { | |
529 best = match; | |
530 } | |
531 } | |
532 | |
533 | |
534 public: | |
535 void Register(const std::string& mime, | |
536 IHandler& handler) | |
537 { | |
538 std::string type, subtype; | |
539 | |
540 if (SplitPair(type, subtype, mime, '/') && | |
541 type != "*" && | |
542 subtype != "*") | |
543 { | |
544 handlers_.push_back(Handler(type, subtype, handler)); | |
545 } | |
546 else | |
547 { | |
548 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
549 } | |
550 } | |
551 | |
552 | |
553 bool Apply(const HttpHeaders& headers) | |
554 { | |
555 HttpHeaders::const_iterator accept = headers.find("accept"); | |
556 if (accept != headers.end()) | |
557 { | |
558 return Apply(accept->second); | |
559 } | |
560 else | |
561 { | |
562 return Apply("*/*"); | |
563 } | |
564 } | |
565 | |
566 | |
567 bool Apply(const std::string& accept) | |
568 { | |
569 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 | |
570 | |
571 Tokens mediaRanges; | |
572 Toolbox::TokenizeString(mediaRanges, accept, ','); | |
573 | |
574 std::auto_ptr<Reference> bestMatch; | |
575 | |
576 for (Tokens::const_reverse_iterator it = mediaRanges.rbegin(); | |
577 it != mediaRanges.rend(); ++it) | |
578 { | |
579 Tokens parameters; | |
580 Toolbox::TokenizeString(parameters, *it, ';'); | |
581 | |
582 if (parameters.size() > 0) | |
583 { | |
584 float quality; | |
585 size_t specificity; | |
586 GetQualityAndSpecificity(quality, specificity, parameters); | |
587 | |
588 std::string type, subtype; | |
589 if (SplitPair(type, subtype, parameters[0], '/')) | |
590 { | |
591 for (Handlers::const_iterator it2 = handlers_.begin(); | |
592 it2 != handlers_.end(); ++it2) | |
593 { | |
594 if (it2->IsMatch(type, subtype)) | |
595 { | |
596 SelectBestMatch(bestMatch, *it2, type, subtype, quality, specificity); | |
597 } | |
598 } | |
599 } | |
600 } | |
601 } | |
602 | |
603 if (bestMatch.get() == NULL) // No match was found | |
604 { | |
605 return false; | |
606 } | |
607 else | |
608 { | |
609 bestMatch->Call(); | |
610 return true; | |
611 } | |
612 } | |
613 }; | |
614 } | |
615 | |
616 | |
617 | |
618 namespace | |
619 { | |
620 class AcceptHandler : public Orthanc::AcceptMediaDispatcher::IHandler | |
621 { | |
622 private: | |
623 std::string type_; | |
624 std::string subtype_; | |
625 float quality_; | |
626 | |
627 public: | |
628 AcceptHandler() | |
629 { | |
630 Reset(); | |
631 } | |
632 | |
633 void Reset() | |
634 { | |
635 Handle("nope", "nope", 0.0f); | |
636 } | |
637 | |
638 const std::string& GetType() const | |
639 { | |
640 return type_; | |
641 } | |
642 | |
643 const std::string& GetSubType() const | |
644 { | |
645 return subtype_; | |
646 } | |
647 | |
648 float GetQuality() const | |
649 { | |
650 return quality_; | |
651 } | |
652 | |
653 virtual void Handle(const std::string& type, | |
654 const std::string& subtype, | |
655 float quality /* between 0 and 1 */) | |
656 { | |
657 type_ = type; | |
658 subtype_ = subtype; | |
659 quality_ = quality; | |
660 } | |
661 }; | |
662 } | |
663 | |
664 | |
665 TEST(RestApi, AcceptMediaDispatcher) | |
666 { | |
667 // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 | |
668 | |
669 AcceptHandler h; | |
670 | |
671 { | |
672 Orthanc::AcceptMediaDispatcher d; | |
673 d.Register("audio/mp3", h); | |
674 d.Register("audio/basic", h); | |
675 | |
676 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic")); | |
677 ASSERT_EQ("audio", h.GetType()); | |
678 ASSERT_EQ("basic", h.GetSubType()); | |
679 ASSERT_FLOAT_EQ(1.0f, h.GetQuality()); | |
680 | |
681 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/nope")); | |
682 ASSERT_EQ("audio", h.GetType()); | |
683 ASSERT_EQ("mp3", h.GetSubType()); | |
684 ASSERT_FLOAT_EQ(0.2f, h.GetQuality()); | |
685 | |
686 ASSERT_FALSE(d.Apply("application/*; q=0.2, application/pdf")); | |
687 | |
688 ASSERT_TRUE(d.Apply("*/*; application/*; q=0.2, application/pdf")); | |
689 ASSERT_EQ("audio", h.GetType()); | |
690 } | |
691 | |
692 // "This would be interpreted as "text/html and text/x-c are the | |
693 // preferred media types, but if they do not exist, then send the | |
694 // text/x-dvi entity, and if that does not exist, send the | |
695 // text/plain entity."" | |
696 const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"; | |
697 | |
698 { | |
699 Orthanc::AcceptMediaDispatcher d; | |
700 d.Register("text/plain", h); | |
701 //d.Register("text/x-dvi", h); | |
702 d.Register("text/html", h); | |
703 ASSERT_TRUE(d.Apply(T1)); | |
704 ASSERT_EQ("text", h.GetType()); | |
705 ASSERT_EQ("html", h.GetSubType()); | |
706 ASSERT_EQ(1.0f, h.GetQuality()); | |
707 } | |
708 | |
709 { | |
710 Orthanc::AcceptMediaDispatcher d; | |
711 d.Register("text/plain", h); | |
712 //d.Register("text/x-dvi", h); | |
713 d.Register("text/x-c", h); | |
714 ASSERT_TRUE(d.Apply(T1)); | |
715 ASSERT_EQ("text", h.GetType()); | |
716 ASSERT_EQ("x-c", h.GetSubType()); | |
717 ASSERT_EQ(1.0f, h.GetQuality()); | |
718 } | |
719 | |
720 { | |
721 Orthanc::AcceptMediaDispatcher d; | |
722 d.Register("text/plain", h); | |
723 d.Register("text/x-dvi", h); | |
724 ASSERT_TRUE(d.Apply(T1)); | |
725 ASSERT_EQ("text", h.GetType()); | |
726 ASSERT_EQ("x-dvi", h.GetSubType()); | |
727 ASSERT_EQ(0.8f, h.GetQuality()); | |
728 } | |
729 | |
730 { | |
731 Orthanc::AcceptMediaDispatcher d; | |
732 d.Register("text/plain", h); | |
733 ASSERT_TRUE(d.Apply(T1)); | |
734 ASSERT_EQ("text", h.GetType()); | |
735 ASSERT_EQ("plain", h.GetSubType()); | |
736 ASSERT_EQ(0.5f, h.GetQuality()); | |
737 } | |
738 } |