Mercurial > hg > orthanc
comparison UnitTestsSources/RestApiTests.cpp @ 1782:9f2df9bb2cdf
fix
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 18 Nov 2015 09:05:43 +0100 |
parents | 5ad4e4d92ecb |
children | dbb07eb1a2f3 |
comparison
equal
deleted
inserted
replaced
1781:5ad4e4d92ecb | 1782:9f2df9bb2cdf |
---|---|
341 | 341 |
342 | 342 |
343 | 343 |
344 namespace Orthanc | 344 namespace Orthanc |
345 { | 345 { |
346 class AcceptMediaDispatcher : public boost::noncopyable | 346 class HttpContentNegociation : public boost::noncopyable |
347 { | 347 { |
348 public: | 348 public: |
349 typedef std::map<std::string, std::string> HttpHeaders; | 349 typedef std::map<std::string, std::string> HttpHeaders; |
350 | 350 |
351 class IHandler : public boost::noncopyable | 351 class IHandler : public boost::noncopyable |
354 virtual ~IHandler() | 354 virtual ~IHandler() |
355 { | 355 { |
356 } | 356 } |
357 | 357 |
358 virtual void Handle(const std::string& type, | 358 virtual void Handle(const std::string& type, |
359 const std::string& subtype, | 359 const std::string& subtype) = 0; |
360 float quality /* between 0 and 1 */) = 0; | |
361 }; | 360 }; |
362 | 361 |
363 private: | 362 private: |
364 struct Handler | 363 struct Handler |
365 { | 364 { |
389 return true; | 388 return true; |
390 } | 389 } |
391 | 390 |
392 return type == type_ && subtype == subtype_; | 391 return type == type_ && subtype == subtype_; |
393 } | 392 } |
393 | |
394 void Call() const | |
395 { | |
396 handler_.Handle(type_, subtype_); | |
397 } | |
394 }; | 398 }; |
395 | 399 |
396 | 400 |
397 struct Reference : public boost::noncopyable | 401 struct Reference : public boost::noncopyable |
398 { | 402 { |
399 const Handler& handler_; | 403 const Handler& handler_; |
400 uint8_t level_; | 404 uint8_t level_; |
401 float quality_; | 405 float quality_; |
402 size_t specificity_; // Number of arguments | |
403 | 406 |
404 Reference(const Handler& handler, | 407 Reference(const Handler& handler, |
405 const std::string& type, | 408 const std::string& type, |
406 const std::string& subtype, | 409 const std::string& subtype, |
407 float quality, | 410 float quality) : |
408 size_t specificity) : | |
409 handler_(handler), | 411 handler_(handler), |
410 quality_(quality), | 412 quality_(quality) |
411 specificity_(specificity) | |
412 { | 413 { |
413 if (type == "*" && subtype == "*") | 414 if (type == "*" && subtype == "*") |
414 { | 415 { |
415 level_ = 0; | 416 level_ = 0; |
416 } | 417 } |
434 if (level_ > other.level_) | 435 if (level_ > other.level_) |
435 { | 436 { |
436 return false; | 437 return false; |
437 } | 438 } |
438 | 439 |
439 return specificity_ < other.specificity_; | 440 return quality_ < other.quality_; |
440 } | |
441 | |
442 void Call() const | |
443 { | |
444 handler_.handler_.Handle(handler_.type_, handler_.subtype_, quality_); | |
445 } | 441 } |
446 }; | 442 }; |
447 | 443 |
448 | 444 |
449 typedef std::vector<std::string> Tokens; | 445 typedef std::vector<std::string> Tokens; |
470 return true; | 466 return true; |
471 } | 467 } |
472 } | 468 } |
473 | 469 |
474 | 470 |
475 static void GetQualityAndSpecificity(float& quality /* out */, | 471 static float GetQuality(const Tokens& parameters) |
476 size_t& specificity /* out */, | 472 { |
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++) | 473 for (size_t i = 1; i < parameters.size(); i++) |
485 { | 474 { |
486 std::string key, value; | 475 std::string key, value; |
487 if (SplitPair(key, value, parameters[i], '=') && | 476 if (SplitPair(key, value, parameters[i], '=') && |
488 key == "q") | 477 key == "q") |
489 { | 478 { |
479 float quality; | |
490 bool ok = false; | 480 bool ok = false; |
491 | 481 |
492 try | 482 try |
493 { | 483 { |
494 quality = boost::lexical_cast<float>(value); | 484 quality = boost::lexical_cast<float>(value); |
498 { | 488 { |
499 } | 489 } |
500 | 490 |
501 if (ok) | 491 if (ok) |
502 { | 492 { |
503 assert(parameters.size() >= 2); | 493 return quality; |
504 specificity = parameters.size() - 2; | |
505 return; | |
506 } | 494 } |
507 else | 495 else |
508 { | 496 { |
509 LOG(ERROR) << "Quality parameter out of range in a HTTP request (must be between 0 and 1): " << value; | 497 LOG(ERROR) << "Quality parameter out of range in a HTTP request (must be between 0 and 1): " << value; |
510 throw OrthancException(ErrorCode_ParameterOutOfRange); | 498 throw OrthancException(ErrorCode_BadRequest); |
511 } | 499 } |
512 } | 500 } |
513 } | 501 } |
502 | |
503 return 1.0f; // Default quality | |
514 } | 504 } |
515 | 505 |
516 | 506 |
517 static void SelectBestMatch(std::auto_ptr<Reference>& best, | 507 static void SelectBestMatch(std::auto_ptr<Reference>& best, |
518 const Handler& handler, | 508 const Handler& handler, |
519 const std::string& type, | 509 const std::string& type, |
520 const std::string& subtype, | 510 const std::string& subtype, |
521 float quality, | 511 float quality) |
522 size_t specificity) | 512 { |
523 { | 513 std::auto_ptr<Reference> match(new Reference(handler, type, subtype, quality)); |
524 std::auto_ptr<Reference> match(new Reference(handler, type, subtype, quality, specificity)); | |
525 | 514 |
526 if (best.get() == NULL || | 515 if (best.get() == NULL || |
527 *best < *match) | 516 *best < *match) |
528 { | 517 { |
529 best = match; | 518 best = match; |
565 | 554 |
566 | 555 |
567 bool Apply(const std::string& accept) | 556 bool Apply(const std::string& accept) |
568 { | 557 { |
569 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 | 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 | |
570 | 561 |
571 Tokens mediaRanges; | 562 Tokens mediaRanges; |
572 Toolbox::TokenizeString(mediaRanges, accept, ','); | 563 Toolbox::TokenizeString(mediaRanges, accept, ','); |
573 | 564 |
574 std::auto_ptr<Reference> bestMatch; | 565 std::auto_ptr<Reference> bestMatch; |
575 | 566 |
576 for (Tokens::const_reverse_iterator it = mediaRanges.rbegin(); | 567 for (Tokens::const_iterator it = mediaRanges.begin(); |
577 it != mediaRanges.rend(); ++it) | 568 it != mediaRanges.end(); ++it) |
578 { | 569 { |
579 Tokens parameters; | 570 Tokens parameters; |
580 Toolbox::TokenizeString(parameters, *it, ';'); | 571 Toolbox::TokenizeString(parameters, *it, ';'); |
581 | 572 |
582 if (parameters.size() > 0) | 573 if (parameters.size() > 0) |
583 { | 574 { |
584 float quality; | 575 float quality = GetQuality(parameters); |
585 size_t specificity; | |
586 GetQualityAndSpecificity(quality, specificity, parameters); | |
587 | 576 |
588 std::string type, subtype; | 577 std::string type, subtype; |
589 if (SplitPair(type, subtype, parameters[0], '/')) | 578 if (SplitPair(type, subtype, parameters[0], '/')) |
590 { | 579 { |
591 for (Handlers::const_iterator it2 = handlers_.begin(); | 580 for (Handlers::const_iterator it2 = handlers_.begin(); |
592 it2 != handlers_.end(); ++it2) | 581 it2 != handlers_.end(); ++it2) |
593 { | 582 { |
594 if (it2->IsMatch(type, subtype)) | 583 if (it2->IsMatch(type, subtype)) |
595 { | 584 { |
596 SelectBestMatch(bestMatch, *it2, type, subtype, quality, specificity); | 585 SelectBestMatch(bestMatch, *it2, type, subtype, quality); |
597 } | 586 } |
598 } | 587 } |
599 } | 588 } |
600 } | 589 } |
601 } | 590 } |
604 { | 593 { |
605 return false; | 594 return false; |
606 } | 595 } |
607 else | 596 else |
608 { | 597 { |
609 bestMatch->Call(); | 598 bestMatch->handler_.Call(); |
610 return true; | 599 return true; |
611 } | 600 } |
612 } | 601 } |
613 }; | 602 }; |
614 } | 603 } |
615 | 604 |
616 | 605 |
617 | 606 |
618 namespace | 607 namespace |
619 { | 608 { |
620 class AcceptHandler : public Orthanc::AcceptMediaDispatcher::IHandler | 609 class AcceptHandler : public Orthanc::HttpContentNegociation::IHandler |
621 { | 610 { |
622 private: | 611 private: |
623 std::string type_; | 612 std::string type_; |
624 std::string subtype_; | 613 std::string subtype_; |
625 float quality_; | |
626 | 614 |
627 public: | 615 public: |
628 AcceptHandler() | 616 AcceptHandler() |
629 { | 617 { |
630 Reset(); | 618 Reset(); |
631 } | 619 } |
632 | 620 |
633 void Reset() | 621 void Reset() |
634 { | 622 { |
635 Handle("nope", "nope", 0.0f); | 623 Handle("nope", "nope"); |
636 } | 624 } |
637 | 625 |
638 const std::string& GetType() const | 626 const std::string& GetType() const |
639 { | 627 { |
640 return type_; | 628 return type_; |
643 const std::string& GetSubType() const | 631 const std::string& GetSubType() const |
644 { | 632 { |
645 return subtype_; | 633 return subtype_; |
646 } | 634 } |
647 | 635 |
648 float GetQuality() const | |
649 { | |
650 return quality_; | |
651 } | |
652 | |
653 virtual void Handle(const std::string& type, | 636 virtual void Handle(const std::string& type, |
654 const std::string& subtype, | 637 const std::string& subtype) |
655 float quality /* between 0 and 1 */) | |
656 { | 638 { |
657 type_ = type; | 639 type_ = type; |
658 subtype_ = subtype; | 640 subtype_ = subtype; |
659 quality_ = quality; | |
660 } | 641 } |
661 }; | 642 }; |
662 } | 643 } |
663 | 644 |
664 | 645 |
665 TEST(RestApi, AcceptMediaDispatcher) | 646 TEST(RestApi, HttpContentNegociation) |
666 { | 647 { |
667 // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 | 648 // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 |
668 | 649 |
669 AcceptHandler h; | 650 AcceptHandler h; |
670 | 651 |
671 { | 652 { |
672 Orthanc::AcceptMediaDispatcher d; | 653 Orthanc::HttpContentNegociation d; |
673 d.Register("audio/mp3", h); | 654 d.Register("audio/mp3", h); |
674 d.Register("audio/basic", h); | 655 d.Register("audio/basic", h); |
675 | 656 |
676 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic")); | 657 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic")); |
677 ASSERT_EQ("audio", h.GetType()); | 658 ASSERT_EQ("audio", h.GetType()); |
678 ASSERT_EQ("basic", h.GetSubType()); | 659 ASSERT_EQ("basic", h.GetSubType()); |
679 ASSERT_FLOAT_EQ(1.0f, h.GetQuality()); | |
680 | 660 |
681 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/nope")); | 661 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/nope")); |
682 ASSERT_EQ("audio", h.GetType()); | 662 ASSERT_EQ("audio", h.GetType()); |
683 ASSERT_EQ("mp3", h.GetSubType()); | 663 ASSERT_EQ("mp3", h.GetSubType()); |
684 ASSERT_FLOAT_EQ(0.2f, h.GetQuality()); | |
685 | 664 |
686 ASSERT_FALSE(d.Apply("application/*; q=0.2, application/pdf")); | 665 ASSERT_FALSE(d.Apply("application/*; q=0.2, application/pdf")); |
687 | 666 |
688 ASSERT_TRUE(d.Apply("*/*; application/*; q=0.2, application/pdf")); | 667 ASSERT_TRUE(d.Apply("*/*; application/*; q=0.2, application/pdf")); |
689 ASSERT_EQ("audio", h.GetType()); | 668 ASSERT_EQ("audio", h.GetType()); |
694 // text/x-dvi entity, and if that does not exist, send the | 673 // text/x-dvi entity, and if that does not exist, send the |
695 // text/plain entity."" | 674 // text/plain entity."" |
696 const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"; | 675 const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"; |
697 | 676 |
698 { | 677 { |
699 Orthanc::AcceptMediaDispatcher d; | 678 Orthanc::HttpContentNegociation d; |
700 d.Register("text/plain", h); | 679 d.Register("text/plain", h); |
701 //d.Register("text/x-dvi", h); | |
702 d.Register("text/html", h); | 680 d.Register("text/html", h); |
681 d.Register("text/x-dvi", h); | |
703 ASSERT_TRUE(d.Apply(T1)); | 682 ASSERT_TRUE(d.Apply(T1)); |
704 ASSERT_EQ("text", h.GetType()); | 683 ASSERT_EQ("text", h.GetType()); |
705 ASSERT_EQ("html", h.GetSubType()); | 684 ASSERT_EQ("html", h.GetSubType()); |
706 ASSERT_EQ(1.0f, h.GetQuality()); | |
707 } | 685 } |
708 | 686 |
709 { | 687 { |
710 Orthanc::AcceptMediaDispatcher d; | 688 Orthanc::HttpContentNegociation d; |
711 d.Register("text/plain", h); | 689 d.Register("text/plain", h); |
712 //d.Register("text/x-dvi", h); | 690 d.Register("text/x-dvi", h); |
713 d.Register("text/x-c", h); | 691 d.Register("text/x-c", h); |
714 ASSERT_TRUE(d.Apply(T1)); | 692 ASSERT_TRUE(d.Apply(T1)); |
715 ASSERT_EQ("text", h.GetType()); | 693 ASSERT_EQ("text", h.GetType()); |
716 ASSERT_EQ("x-c", h.GetSubType()); | 694 ASSERT_EQ("x-c", h.GetSubType()); |
717 ASSERT_EQ(1.0f, h.GetQuality()); | |
718 } | 695 } |
719 | 696 |
720 { | 697 { |
721 Orthanc::AcceptMediaDispatcher d; | 698 Orthanc::HttpContentNegociation d; |
699 d.Register("text/plain", h); | |
700 d.Register("text/x-dvi", h); | |
701 d.Register("text/x-c", h); | |
702 d.Register("text/html", h); | |
703 ASSERT_TRUE(d.Apply(T1)); | |
704 ASSERT_EQ("text", h.GetType()); | |
705 ASSERT_TRUE(h.GetSubType() == "x-c" || h.GetSubType() == "html"); | |
706 } | |
707 | |
708 { | |
709 Orthanc::HttpContentNegociation d; | |
722 d.Register("text/plain", h); | 710 d.Register("text/plain", h); |
723 d.Register("text/x-dvi", h); | 711 d.Register("text/x-dvi", h); |
724 ASSERT_TRUE(d.Apply(T1)); | 712 ASSERT_TRUE(d.Apply(T1)); |
725 ASSERT_EQ("text", h.GetType()); | 713 ASSERT_EQ("text", h.GetType()); |
726 ASSERT_EQ("x-dvi", h.GetSubType()); | 714 ASSERT_EQ("x-dvi", h.GetSubType()); |
727 ASSERT_EQ(0.8f, h.GetQuality()); | |
728 } | 715 } |
729 | 716 |
730 { | 717 { |
731 Orthanc::AcceptMediaDispatcher d; | 718 Orthanc::HttpContentNegociation d; |
732 d.Register("text/plain", h); | 719 d.Register("text/plain", h); |
733 ASSERT_TRUE(d.Apply(T1)); | 720 ASSERT_TRUE(d.Apply(T1)); |
734 ASSERT_EQ("text", h.GetType()); | 721 ASSERT_EQ("text", h.GetType()); |
735 ASSERT_EQ("plain", h.GetSubType()); | 722 ASSERT_EQ("plain", h.GetSubType()); |
736 ASSERT_EQ(0.5f, h.GetQuality()); | 723 } |
737 } | 724 } |
738 } |