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 }