comparison UnitTests/ServerIndex.cpp @ 207:7f74209ea0f8

RestApi
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 28 Nov 2012 16:23:11 +0100
parents 4453a010d0db
children de640de989b8
comparison
equal deleted inserted replaced
206:4453a010d0db 207:7f74209ea0f8
256 256
257 listener.Reset(); 257 listener.Reset();
258 index.DeleteResource(a[6]); 258 index.DeleteResource(a[6]);
259 ASSERT_EQ("", listener.ancestorId_); // No more ancestor 259 ASSERT_EQ("", listener.ancestorId_); // No more ancestor
260 } 260 }
261
262
263
264 #include "../Core/Toolbox.h"
265 #include "../Core/HttpServer/HttpOutput.h"
266 #include "../Core/HttpServer/HttpHandler.h"
267
268 namespace Orthanc
269 {
270 class RestApiPath
271 {
272 private:
273 UriComponents uri_;
274 bool hasTrailing_;
275 std::vector<std::string> components_;
276
277 public:
278 typedef std::map<std::string, std::string> Components;
279
280 RestApiPath(const std::string& uri)
281 {
282 Toolbox::SplitUriComponents(uri_, uri);
283
284 if (uri_.size() == 0)
285 {
286 return;
287 }
288
289 if (uri_.back() == "*")
290 {
291 hasTrailing_ = true;
292 uri_.pop_back();
293 }
294 else
295 {
296 hasTrailing_ = false;
297 }
298
299 components_.resize(uri_.size());
300 for (size_t i = 0; i < uri_.size(); i++)
301 {
302 size_t s = uri_[i].size();
303 assert(s > 0);
304
305 if (uri_[i][0] == '{' &&
306 uri_[i][s - 1] == '}')
307 {
308 components_[i] = uri_[i].substr(1, s - 2);
309 uri_[i] = "";
310 }
311 else
312 {
313 components_[i] = "";
314 }
315 }
316 }
317
318 // This version is slower
319 bool Match(Components& components,
320 UriComponents& trailing,
321 const std::string& uriRaw) const
322 {
323 UriComponents uri;
324 Toolbox::SplitUriComponents(uri, uriRaw);
325 return Match(components, trailing, uri);
326 }
327
328 bool Match(Components& components,
329 UriComponents& trailing,
330 const UriComponents& uri) const
331 {
332 if (uri.size() < uri_.size())
333 {
334 return false;
335 }
336
337 if (!hasTrailing_ && uri.size() > uri_.size())
338 {
339 return false;
340 }
341
342 components.clear();
343 trailing.clear();
344
345 assert(uri_.size() <= uri.size());
346 for (size_t i = 0; i < uri_.size(); i++)
347 {
348 if (components_[i].size() == 0)
349 {
350 // This URI component is not a free parameter
351 if (uri_[i] != uri[i])
352 {
353 return false;
354 }
355 }
356 else
357 {
358 // This URI component is a free parameter
359 components[components_[i]] = uri[i];
360 }
361 }
362
363 if (hasTrailing_)
364 {
365 trailing.assign(uri.begin() + uri_.size(), uri.end());
366 }
367
368 return true;
369 }
370
371 bool Match(const UriComponents& uri) const
372 {
373 Components components;
374 UriComponents trailing;
375 return Match(components, trailing, uri);
376 }
377 };
378
379
380 class HttpFileSender
381 {
382 private:
383 std::string contentType_;
384 std::string filename_;
385
386 void SendHeader(HttpOutput& output)
387 {
388 std::string header;
389 header += "Content-Length: " + boost::lexical_cast<std::string>(GetFileSize()) + "\r\n";
390
391 if (contentType_.size() > 0)
392 {
393 header += "Content-Type: " + contentType_ + "\r\n";
394 }
395
396 if (filename_.size() > 0)
397 {
398 header += "Content-Disposition: attachment; filename=\"" + filename_ + "\"\r\n";
399 }
400
401 output.SendCustomOkHeader(header);
402 }
403
404 protected:
405 virtual uint64_t GetFileSize() = 0;
406
407 virtual bool SendData(HttpOutput& output) = 0;
408
409 public:
410 virtual ~HttpFileSender()
411 {
412 }
413
414 void ResetContentType()
415 {
416 contentType_.clear();
417 }
418
419 void SetContentType(const std::string& contentType)
420 {
421 contentType_ = contentType;
422 }
423
424 const std::string& GetContentType() const
425 {
426 return contentType_;
427 }
428
429 void ResetFilename()
430 {
431 contentType_.clear();
432 }
433
434 void SetFilename(const std::string& filename)
435 {
436 filename_ = filename;
437 }
438
439 const std::string& GetFilename() const
440 {
441 return filename_;
442 }
443
444 void Send(HttpOutput& output)
445 {
446 SendHeader(output);
447
448 if (!SendData(output))
449 {
450 output.SendHeader(Orthanc_HttpStatus_500_InternalServerError);
451 }
452 }
453 };
454
455
456 class FilesystemHttpSender : public HttpFileSender
457 {
458 private:
459 std::string path_;
460
461 protected:
462 virtual uint64_t GetFileSize()
463 {
464 return Toolbox::GetFileSize(path_);
465 }
466
467 virtual bool SendData(HttpOutput& output)
468 {
469 FILE* fp = fopen(path_.c_str(), "rb");
470 if (!fp)
471 {
472 return false;
473 }
474
475 std::vector<uint8_t> buffer(1024 * 1024); // Chunks of 1MB
476
477 for (;;)
478 {
479 size_t nbytes = fread(&buffer[0], 1, buffer.size(), fp);
480 if (nbytes == 0)
481 {
482 break;
483 }
484 else
485 {
486 output.Send(&buffer[0], nbytes);
487 }
488 }
489
490 fclose(fp);
491
492 return true;
493 }
494
495 public:
496 FilesystemHttpSender(const char* path) : path_(path)
497 {
498 boost::filesystem::path p(path);
499 SetFilename(p.filename().string());
500 SetContentType(Toolbox::AutodetectMimeType(p.filename().string()));
501 }
502 };
503
504
505 class RestApiOutput
506 {
507 private:
508 HttpOutput& output_;
509
510 public:
511 RestApiOutput(HttpOutput& output) : output_(output)
512 {
513 }
514
515 void AnswerFile(HttpFileSender& sender)
516 {
517 sender.Send(output_);
518 }
519
520 void AnswerJson(const Json::Value& value)
521 {
522 Json::StyledWriter writer;
523 std::string s = writer.write(value);
524 output_.AnswerBufferWithContentType(s, "application/json");
525 }
526
527 void AnswerBuffer(const std::string& buffer,
528 const std::string& contentType)
529 {
530 output_.AnswerBufferWithContentType(buffer, contentType);
531 }
532
533 void Redirect(const char* path)
534 {
535 output_.Redirect(path);
536 }
537 };
538
539
540 class RestApiSharedCall
541 {
542 protected:
543 RestApiOutput* output_;
544 IDynamicObject* context_;
545 const HttpHandler::Arguments* httpHeaders_;
546 const RestApiPath::Components* uriComponents_;
547 const UriComponents* trailing_;
548
549 public:
550 RestApiOutput& GetOutput()
551 {
552 return *output_;
553 }
554
555 IDynamicObject* GetContext()
556 {
557 return context_;
558 }
559
560 const HttpHandler::Arguments& GetHttpHeaders() const
561 {
562 return *httpHeaders_;
563 }
564
565 const RestApiPath::Components& GetUriComponents() const
566 {
567 return *uriComponents_;
568 }
569
570 const UriComponents& GetTrailing() const
571 {
572 return *trailing_;
573 }
574
575 std::string GetUriComponent(const std::string& name,
576 const std::string& defaultValue)
577 {
578 return HttpHandler::GetArgument(*uriComponents_, name, defaultValue);
579 }
580 };
581
582
583 class RestApiPutCall : public RestApiSharedCall
584 {
585 friend class RestApi;
586
587 private:
588 const std::string* data_;
589
590 public:
591 const std::string& GetData()
592 {
593 return *data_;
594 }
595 };
596
597
598 class RestApiPostCall : public RestApiSharedCall
599 {
600 friend class RestApi;
601
602 private:
603 const std::string* data_;
604
605 public:
606 const std::string& GetData()
607 {
608 return *data_;
609 }
610 };
611
612
613
614 class RestApiDeleteCall : public RestApiSharedCall
615 {
616 friend class RestApi;
617 };
618
619
620
621
622 class RestApiGetCall : public RestApiSharedCall
623 {
624 friend class RestApi;
625
626 private:
627 const HttpHandler::Arguments* getArguments_;
628
629 public:
630 std::string GetArgument(const std::string& name,
631 const std::string& defaultValue)
632 {
633 return HttpHandler::GetArgument(*getArguments_, name, defaultValue);
634 }
635 };
636
637
638
639 class RestApi : public HttpHandler
640 {
641 public:
642 typedef void (*GetHandler) (RestApiGetCall& call);
643
644 typedef void (*DeleteHandler) (RestApiDeleteCall& call);
645
646 typedef void (*PutHandler) (RestApiPutCall& call);
647
648 typedef void (*PostHandler) (RestApiPostCall& call);
649
650 private:
651 typedef std::list< std::pair<RestApiPath*, GetHandler> > GetHandlers;
652 typedef std::list< std::pair<RestApiPath*, PutHandler> > PutHandlers;
653 typedef std::list< std::pair<RestApiPath*, PostHandler> > PostHandlers;
654 typedef std::list< std::pair<RestApiPath*, DeleteHandler> > DeleteHandlers;
655
656 // TODO MUTEX BETWEEN CONTEXTS !!!
657 std::auto_ptr<IDynamicObject> context_;
658
659 GetHandlers getHandlers_;
660 PutHandlers putHandlers_;
661 PostHandlers postHandlers_;
662 DeleteHandlers deleteHandlers_;
663
664 bool IsGetAccepted(const UriComponents& uri)
665 {
666 for (GetHandlers::const_iterator it = getHandlers_.begin();
667 it != getHandlers_.end(); it++)
668 {
669 if (it->first->Match(uri))
670 {
671 return true;
672 }
673 }
674
675 return false;
676 }
677
678 bool IsPutAccepted(const UriComponents& uri)
679 {
680 for (PutHandlers::const_iterator it = putHandlers_.begin();
681 it != putHandlers_.end(); it++)
682 {
683 if (it->first->Match(uri))
684 {
685 return true;
686 }
687 }
688
689 return false;
690 }
691
692 bool IsPostAccepted(const UriComponents& uri)
693 {
694 for (PostHandlers::const_iterator it = postHandlers_.begin();
695 it != postHandlers_.end(); it++)
696 {
697 if (it->first->Match(uri))
698 {
699 return true;
700 }
701 }
702
703 return false;
704 }
705
706 bool IsDeleteAccepted(const UriComponents& uri)
707 {
708 for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
709 it != deleteHandlers_.end(); it++)
710 {
711 if (it->first->Match(uri))
712 {
713 return true;
714 }
715 }
716
717 return false;
718 }
719
720 void AddMethod(std::string& target,
721 const std::string& method) const
722 {
723 if (target.size() > 0)
724 target += "," + method;
725 else
726 target = method;
727 }
728
729 std::string GetAcceptedMethods(const UriComponents& uri)
730 {
731 std::string s;
732
733 if (IsGetAccepted(uri))
734 AddMethod(s, "GET");
735
736 if (IsPutAccepted(uri))
737 AddMethod(s, "PUT");
738
739 if (IsPostAccepted(uri))
740 AddMethod(s, "POST");
741
742 if (IsDeleteAccepted(uri))
743 AddMethod(s, "DELETE");
744
745 return s;
746 }
747
748 public:
749 RestApi()
750 {
751 }
752
753 void SetContext(IDynamicObject* context) // This takes the ownership
754 {
755 context_.reset(context);
756 }
757
758 ~RestApi()
759 {
760 for (GetHandlers::iterator it = getHandlers_.begin();
761 it != getHandlers_.end(); it++)
762 {
763 delete it->first;
764 }
765
766 for (PutHandlers::iterator it = putHandlers_.begin();
767 it != putHandlers_.end(); it++)
768 {
769 delete it->first;
770 }
771
772 for (PostHandlers::iterator it = postHandlers_.begin();
773 it != postHandlers_.end(); it++)
774 {
775 delete it->first;
776 }
777
778 for (DeleteHandlers::iterator it = deleteHandlers_.begin();
779 it != deleteHandlers_.end(); it++)
780 {
781 delete it->first;
782 }
783 }
784
785 virtual bool IsServedUri(const UriComponents& uri)
786 {
787 return (IsGetAccepted(uri) ||
788 IsPutAccepted(uri) ||
789 IsPostAccepted(uri) ||
790 IsDeleteAccepted(uri));
791 }
792
793 virtual void Handle(HttpOutput& output,
794 const std::string& method,
795 const UriComponents& uri,
796 const Arguments& headers,
797 const Arguments& getArguments,
798 const std::string& postData)
799 {
800 bool ok = false;
801 RestApiOutput restOutput(output);
802 RestApiPath::Components components;
803 UriComponents trailing;
804
805 if (method == "GET")
806 {
807 for (GetHandlers::const_iterator it = getHandlers_.begin();
808 it != getHandlers_.end(); it++)
809 {
810 if (it->first->Match(components, trailing, uri))
811 {
812 ok = true;
813 RestApiGetCall call;
814 call.output_ = &restOutput;
815 call.context_ = context_.get();
816 call.httpHeaders_ = &headers;
817 call.uriComponents_ = &components;
818 call.trailing_ = &trailing;
819
820 call.getArguments_ = &getArguments;
821 it->second(call);
822 }
823 }
824 }
825 else if (method == "PUT")
826 {
827 for (PutHandlers::const_iterator it = putHandlers_.begin();
828 it != putHandlers_.end(); it++)
829 {
830 if (it->first->Match(components, trailing, uri))
831 {
832 ok = true;
833 RestApiPutCall call;
834 call.output_ = &restOutput;
835 call.context_ = context_.get();
836 call.httpHeaders_ = &headers;
837 call.uriComponents_ = &components;
838 call.trailing_ = &trailing;
839
840 call.data_ = &postData;
841 it->second(call);
842 }
843 }
844 }
845 else if (method == "POST")
846 {
847 for (PostHandlers::const_iterator it = postHandlers_.begin();
848 it != postHandlers_.end(); it++)
849 {
850 if (it->first->Match(components, trailing, uri))
851 {
852 ok = true;
853 RestApiPostCall call;
854 call.output_ = &restOutput;
855 call.context_ = context_.get();
856 call.httpHeaders_ = &headers;
857 call.uriComponents_ = &components;
858 call.trailing_ = &trailing;
859
860 call.data_ = &postData;
861 it->second(call);
862 }
863 }
864 }
865 else if (method == "DELETE")
866 {
867 for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
868 it != deleteHandlers_.end(); it++)
869 {
870 if (it->first->Match(components, trailing, uri))
871 {
872 ok = true;
873 RestApiDeleteCall call;
874 call.output_ = &restOutput;
875 call.context_ = context_.get();
876 call.httpHeaders_ = &headers;
877 call.uriComponents_ = &components;
878 call.trailing_ = &trailing;
879 it->second(call);
880 }
881 }
882 }
883
884 if (!ok)
885 {
886 output.SendMethodNotAllowedError(GetAcceptedMethods(uri));
887 }
888 }
889
890 void Register(const std::string& path,
891 GetHandler handler)
892 {
893 getHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
894 }
895
896
897 void Register(const std::string& path,
898 PutHandler handler)
899 {
900 putHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
901 }
902
903
904 void Register(const std::string& path,
905 PostHandler handler)
906 {
907 postHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
908 }
909
910
911 void Register(const std::string& path,
912 DeleteHandler handler)
913 {
914 deleteHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
915 }
916
917 };
918
919 }
920
921
922 TEST(RestApi, RestApiPath)
923 {
924 RestApiPath::Components args;
925 UriComponents trail;
926
927 {
928 RestApiPath uri("/coucou/{abc}/d/*");
929 ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
930 ASSERT_EQ(1u, args.size());
931 ASSERT_EQ(3u, trail.size());
932 ASSERT_EQ("moi", args["abc"]);
933 ASSERT_EQ("e", trail[0]);
934 ASSERT_EQ("f", trail[1]);
935 ASSERT_EQ("g", trail[2]);
936
937 ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f"));
938 ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/"));
939 ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d"));
940 ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi"));
941 }
942
943 {
944 RestApiPath uri("/coucou/{abc}/d");
945 ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
946 ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d"));
947 ASSERT_EQ(1u, args.size());
948 ASSERT_EQ(0u, trail.size());
949 ASSERT_EQ("moi", args["abc"]);
950 }
951
952 {
953 RestApiPath uri("/*");
954 ASSERT_TRUE(uri.Match(args, trail, "/a/b/c"));
955 ASSERT_EQ(0u, args.size());
956 ASSERT_EQ(3u, trail.size());
957 ASSERT_EQ("a", trail[0]);
958 ASSERT_EQ("b", trail[1]);
959 ASSERT_EQ("c", trail[2]);
960 }
961 }
962
963
964
965
966 #include "../Core/HttpServer/MongooseServer.h"
967
968 struct Tutu : public IDynamicObject
969 {
970 static void Toto(RestApiGetCall& call)
971 {
972 printf("DONE\n");
973 Json::Value a = Json::objectValue;
974 a["Tutu"] = "Toto";
975 a["Youpie"] = call.GetArgument("coucou", "nope");
976 a["Toto"] = call.GetUriComponent("test", "nope");
977 call.GetOutput().AnswerJson(a);
978 }
979 };
980
981
982
983 TEST(RestApi, Tutu)
984 {
985 MongooseServer httpServer;
986 httpServer.SetPortNumber(8042);
987 httpServer.Start();
988
989 RestApi* api = new RestApi;
990 httpServer.RegisterHandler(api);
991 api->Register("/coucou/{test}/a/*", Tutu::Toto);
992
993 httpServer.Start();
994 /*LOG(WARNING) << "REST has started";
995 Toolbox::ServerBarrier();*/
996 }
997
998
999 /**
1000
1001 output.AnswerBufferWithContentType(s, "application/json");
1002 output.AnswerFile(storage_, fileUuid, contentType, filename.c_str());
1003 output.Redirect("app/explorer.html");
1004 output.SendHeader(Orthanc_HttpStatus_415_UnsupportedMediaType);
1005 output.SendMethodNotAllowedError("GET");
1006
1007 **/