1 /** 2 * Orthanc - A Lightweight, RESTful DICOM Store 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics 4 * Department, University Hospital of Liege, Belgium 5 * Copyright (C) 2017-2021 Osimis S.A., Belgium 6 * 7 * This program is free software: you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public License 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this program. If not, see 19 * <http://www.gnu.org/licenses/>. 20 **/ 21 22 23 #include "../PrecompiledHeaders.h" 24 #include "RestApi.h" 25 26 #include "../HttpServer/StringHttpOutput.h" 27 #include "../Logging.h" 28 #include "../OrthancException.h" 29 30 #include <boost/algorithm/string/replace.hpp> 31 #include <boost/math/special_functions/round.hpp> 32 #include <stdlib.h> // To define "_exit()" under Windows 33 #include <stdio.h> 34 35 namespace Orthanc 36 { 37 namespace 38 { 39 // Anonymous namespace to avoid clashes between compilation modules 40 class HttpHandlerVisitor : public RestApiHierarchy::IVisitor 41 { 42 private: 43 RestApi& api_; 44 RestApiOutput& output_; 45 RequestOrigin origin_; 46 const char* remoteIp_; 47 const char* username_; 48 HttpMethod method_; 49 const HttpToolbox::Arguments& headers_; 50 const HttpToolbox::Arguments& getArguments_; 51 const void* bodyData_; 52 size_t bodySize_; 53 54 public: HttpHandlerVisitor(RestApi & api,RestApiOutput & output,RequestOrigin origin,const char * remoteIp,const char * username,HttpMethod method,const HttpToolbox::Arguments & headers,const HttpToolbox::Arguments & getArguments,const void * bodyData,size_t bodySize)55 HttpHandlerVisitor(RestApi& api, 56 RestApiOutput& output, 57 RequestOrigin origin, 58 const char* remoteIp, 59 const char* username, 60 HttpMethod method, 61 const HttpToolbox::Arguments& headers, 62 const HttpToolbox::Arguments& getArguments, 63 const void* bodyData, 64 size_t bodySize) : 65 api_(api), 66 output_(output), 67 origin_(origin), 68 remoteIp_(remoteIp), 69 username_(username), 70 method_(method), 71 headers_(headers), 72 getArguments_(getArguments), 73 bodyData_(bodyData), 74 bodySize_(bodySize) 75 { 76 } 77 Visit(const RestApiHierarchy::Resource & resource,const UriComponents & uri,bool hasTrailing,const HttpToolbox::Arguments & components,const UriComponents & trailing)78 virtual bool Visit(const RestApiHierarchy::Resource& resource, 79 const UriComponents& uri, 80 bool hasTrailing, 81 const HttpToolbox::Arguments& components, 82 const UriComponents& trailing) 83 { 84 if (resource.HasHandler(method_)) 85 { 86 switch (method_) 87 { 88 case HttpMethod_Get: 89 { 90 RestApiGetCall call(output_, api_, origin_, remoteIp_, username_, 91 headers_, components, trailing, uri, getArguments_); 92 resource.Handle(call); 93 return true; 94 } 95 96 case HttpMethod_Post: 97 { 98 RestApiPostCall call(output_, api_, origin_, remoteIp_, username_, 99 headers_, components, trailing, uri, bodyData_, bodySize_); 100 resource.Handle(call); 101 return true; 102 } 103 104 case HttpMethod_Delete: 105 { 106 RestApiDeleteCall call(output_, api_, origin_, remoteIp_, username_, 107 headers_, components, trailing, uri); 108 resource.Handle(call); 109 return true; 110 } 111 112 case HttpMethod_Put: 113 { 114 RestApiPutCall call(output_, api_, origin_, remoteIp_, username_, 115 headers_, components, trailing, uri, bodyData_, bodySize_); 116 resource.Handle(call); 117 return true; 118 } 119 120 default: 121 return false; 122 } 123 } 124 125 return false; 126 } 127 }; 128 129 130 131 class DocumentationVisitor : public RestApiHierarchy::IVisitor 132 { 133 private: 134 RestApi& restApi_; 135 size_t successPathsCount_; 136 size_t totalPathsCount_; 137 138 protected: 139 virtual bool HandleCall(RestApiCall& call, 140 const std::set<std::string>& uriArgumentsNames) = 0; 141 142 public: DocumentationVisitor(RestApi & restApi)143 explicit DocumentationVisitor(RestApi& restApi) : 144 restApi_(restApi), 145 successPathsCount_(0), 146 totalPathsCount_(0) 147 { 148 } 149 Visit(const RestApiHierarchy::Resource & resource,const UriComponents & uri,bool hasTrailing,const HttpToolbox::Arguments & components,const UriComponents & trailing)150 virtual bool Visit(const RestApiHierarchy::Resource& resource, 151 const UriComponents& uri, 152 bool hasTrailing, 153 const HttpToolbox::Arguments& components, 154 const UriComponents& trailing) 155 { 156 std::string path = Toolbox::FlattenUri(uri); 157 if (hasTrailing) 158 { 159 path += "/{...}"; 160 } 161 162 std::set<std::string> uriArgumentsNames; 163 HttpToolbox::Arguments uriArguments; 164 165 for (HttpToolbox::Arguments::const_iterator 166 it = components.begin(); it != components.end(); ++it) 167 { 168 assert(it->second.empty()); 169 uriArgumentsNames.insert(it->first.c_str()); 170 uriArguments[it->first] = ""; 171 } 172 173 if (hasTrailing) 174 { 175 uriArgumentsNames.insert("..."); 176 uriArguments["..."] = ""; 177 } 178 179 if (resource.HasHandler(HttpMethod_Get)) 180 { 181 totalPathsCount_ ++; 182 183 StringHttpOutput o1; 184 HttpOutput o2(o1, false); 185 RestApiOutput o3(o2, HttpMethod_Get); 186 RestApiGetCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */, 187 "" /* username */, HttpToolbox::Arguments() /* HTTP headers */, 188 uriArguments, UriComponents() /* trailing */, 189 uri, HttpToolbox::Arguments() /* GET arguments */); 190 191 bool ok = false; 192 193 try 194 { 195 ok = (resource.Handle(call) && 196 HandleCall(call, uriArgumentsNames)); 197 } 198 catch (OrthancException& e) 199 { 200 LOG(ERROR) << "Exception while documenting GET " << path << ": " << e.What(); 201 } 202 catch (boost::bad_lexical_cast&) 203 { 204 LOG(ERROR) << "Bad lexical cast while documenting GET " << path; 205 } 206 207 if (ok) 208 { 209 successPathsCount_ ++; 210 } 211 else 212 { 213 LOG(WARNING) << "Ignoring URI without API documentation: GET " << path; 214 } 215 } 216 217 if (resource.HasHandler(HttpMethod_Post)) 218 { 219 totalPathsCount_ ++; 220 221 StringHttpOutput o1; 222 HttpOutput o2(o1, false); 223 RestApiOutput o3(o2, HttpMethod_Post); 224 RestApiPostCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */, 225 "" /* username */, HttpToolbox::Arguments() /* HTTP headers */, 226 uriArguments, UriComponents() /* trailing */, 227 uri, NULL /* body */, 0 /* body size */); 228 229 bool ok = false; 230 231 try 232 { 233 ok = (resource.Handle(call) && 234 HandleCall(call, uriArgumentsNames)); 235 } 236 catch (OrthancException& e) 237 { 238 LOG(ERROR) << "Exception while documenting POST " << path << ": " << e.What(); 239 } 240 catch (boost::bad_lexical_cast&) 241 { 242 LOG(ERROR) << "Bad lexical cast while documenting POST " << path; 243 } 244 245 if (ok) 246 { 247 successPathsCount_ ++; 248 } 249 else 250 { 251 LOG(WARNING) << "Ignoring URI without API documentation: POST " << path; 252 } 253 } 254 255 if (resource.HasHandler(HttpMethod_Delete)) 256 { 257 totalPathsCount_ ++; 258 259 StringHttpOutput o1; 260 HttpOutput o2(o1, false); 261 RestApiOutput o3(o2, HttpMethod_Delete); 262 RestApiDeleteCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */, 263 "" /* username */, HttpToolbox::Arguments() /* HTTP headers */, 264 uriArguments, UriComponents() /* trailing */, uri); 265 266 bool ok = false; 267 268 try 269 { 270 ok = (resource.Handle(call) && 271 HandleCall(call, uriArgumentsNames)); 272 } 273 catch (OrthancException& e) 274 { 275 LOG(ERROR) << "Exception while documenting DELETE " << path << ": " << e.What(); 276 } 277 catch (boost::bad_lexical_cast&) 278 { 279 LOG(ERROR) << "Bad lexical cast while documenting DELETE " << path; 280 } 281 282 if (ok) 283 { 284 successPathsCount_ ++; 285 } 286 else 287 { 288 LOG(WARNING) << "Ignoring URI without API documentation: DELETE " << path; 289 } 290 } 291 292 if (resource.HasHandler(HttpMethod_Put)) 293 { 294 totalPathsCount_ ++; 295 296 StringHttpOutput o1; 297 HttpOutput o2(o1, false); 298 RestApiOutput o3(o2, HttpMethod_Put); 299 RestApiPutCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */, 300 "" /* username */, HttpToolbox::Arguments() /* HTTP headers */, 301 uriArguments, UriComponents() /* trailing */, uri, 302 NULL /* body */, 0 /* body size */); 303 304 bool ok = false; 305 306 try 307 { 308 ok = (resource.Handle(call) && 309 HandleCall(call, uriArgumentsNames)); 310 } 311 catch (OrthancException& e) 312 { 313 LOG(ERROR) << "Exception while documenting PUT " << path << ": " << e.What(); 314 } 315 catch (boost::bad_lexical_cast&) 316 { 317 LOG(ERROR) << "Bad lexical cast while documenting PUT " << path; 318 } 319 320 if (ok) 321 { 322 successPathsCount_ ++; 323 } 324 else 325 { 326 LOG(WARNING) << "Ignoring URI without API documentation: PUT " << path; 327 } 328 } 329 330 return true; 331 } 332 GetSuccessPathsCount() const333 size_t GetSuccessPathsCount() const 334 { 335 return successPathsCount_; 336 } 337 GetTotalPathsCount() const338 size_t GetTotalPathsCount() const 339 { 340 return totalPathsCount_; 341 } 342 LogStatistics() const343 void LogStatistics() const 344 { 345 assert(GetSuccessPathsCount() <= GetTotalPathsCount()); 346 size_t total = GetTotalPathsCount(); 347 if (total == 0) 348 { 349 total = 1; // Avoid division by zero 350 } 351 float coverage = (100.0f * static_cast<float>(GetSuccessPathsCount()) / 352 static_cast<float>(total)); 353 354 LOG(WARNING) << "The documentation of the REST API contains " << GetSuccessPathsCount() 355 << " paths over a total of " << GetTotalPathsCount() << " paths " 356 << "(coverage: " << static_cast<unsigned int>(boost::math::iround(coverage)) << "%)"; 357 } 358 }; 359 360 361 class OpenApiVisitor : public DocumentationVisitor 362 { 363 private: 364 Json::Value paths_; 365 366 protected: HandleCall(RestApiCall & call,const std::set<std::string> & uriArgumentsNames)367 virtual bool HandleCall(RestApiCall& call, 368 const std::set<std::string>& uriArgumentsNames) ORTHANC_OVERRIDE 369 { 370 const std::string path = Toolbox::FlattenUri(call.GetFullUri()); 371 372 Json::Value v; 373 if (call.GetDocumentation().FormatOpenApi(v, uriArgumentsNames, path)) 374 { 375 std::string method; 376 377 switch (call.GetMethod()) 378 { 379 case HttpMethod_Get: 380 method = "get"; 381 break; 382 383 case HttpMethod_Post: 384 method = "post"; 385 break; 386 387 case HttpMethod_Delete: 388 method = "delete"; 389 break; 390 391 case HttpMethod_Put: 392 method = "put"; 393 break; 394 395 default: 396 throw OrthancException(ErrorCode_ParameterOutOfRange); 397 } 398 399 if ((paths_.isMember(path) && 400 paths_[path].type() != Json::objectValue) || 401 paths_[path].isMember(method)) 402 { 403 throw OrthancException(ErrorCode_InternalError); 404 } 405 406 paths_[path][method] = v; 407 408 return true; 409 } 410 else 411 { 412 return false; 413 } 414 } 415 416 public: OpenApiVisitor(RestApi & restApi)417 explicit OpenApiVisitor(RestApi& restApi) : 418 DocumentationVisitor(restApi), 419 paths_(Json::objectValue) 420 { 421 } 422 GetPaths() const423 const Json::Value& GetPaths() const 424 { 425 return paths_; 426 } 427 }; 428 429 430 class ReStructuredTextCheatSheet : public DocumentationVisitor 431 { 432 private: 433 class Path 434 { 435 private: 436 bool hasGet_; 437 bool hasPost_; 438 bool hasDelete_; 439 bool hasPut_; 440 std::string getTag_; 441 std::string postTag_; 442 std::string deleteTag_; 443 std::string putTag_; 444 std::string summary_; 445 bool getDeprecated_; 446 bool postDeprecated_; 447 bool deleteDeprecated_; 448 bool putDeprecated_; 449 HttpMethod summaryOrigin_; 450 451 public: Path()452 Path() : 453 hasGet_(false), 454 hasPost_(false), 455 hasDelete_(false), 456 hasPut_(false), 457 getDeprecated_(false), 458 postDeprecated_(false), 459 deleteDeprecated_(false), 460 putDeprecated_(false), 461 summaryOrigin_(HttpMethod_Get) // Dummy initialization 462 { 463 } 464 AddMethod(HttpMethod method,const std::string & tag,bool deprecated)465 void AddMethod(HttpMethod method, 466 const std::string& tag, 467 bool deprecated) 468 { 469 switch (method) 470 { 471 case HttpMethod_Get: 472 if (hasGet_) 473 { 474 throw OrthancException(ErrorCode_InternalError); 475 } 476 477 hasGet_ = true; 478 getTag_ = tag; 479 getDeprecated_ = deprecated; 480 break; 481 482 case HttpMethod_Post: 483 if (hasPost_) 484 { 485 throw OrthancException(ErrorCode_InternalError); 486 } 487 488 hasPost_ = true; 489 postTag_ = tag; 490 postDeprecated_ = deprecated; 491 break; 492 493 case HttpMethod_Delete: 494 if (hasDelete_) 495 { 496 throw OrthancException(ErrorCode_InternalError); 497 } 498 499 hasDelete_ = true; 500 deleteTag_ = tag; 501 deleteDeprecated_ = deprecated; 502 break; 503 504 case HttpMethod_Put: 505 if (hasPut_) 506 { 507 throw OrthancException(ErrorCode_InternalError); 508 } 509 510 hasPut_ = true; 511 putTag_ = tag; 512 putDeprecated_ = deprecated; 513 break; 514 515 default: 516 throw OrthancException(ErrorCode_ParameterOutOfRange); 517 } 518 } 519 SetSummary(const std::string & summary,HttpMethod newOrigin)520 void SetSummary(const std::string& summary, 521 HttpMethod newOrigin) 522 { 523 if (!summary.empty()) 524 { 525 bool replace; 526 527 if (summary_.empty()) 528 { 529 // We don't have a summary so far 530 replace = true; 531 } 532 else 533 { 534 // We already have a summary. Replace it if the new 535 // summary is associated with a HTTP method of higher 536 // weight (GET > POST > DELETE > PUT) 537 switch (summaryOrigin_) 538 { 539 case HttpMethod_Get: 540 replace = false; 541 break; 542 543 case HttpMethod_Post: 544 replace = (newOrigin == HttpMethod_Get); 545 break; 546 547 case HttpMethod_Delete: 548 replace = (newOrigin == HttpMethod_Get || 549 newOrigin == HttpMethod_Post); 550 break; 551 552 case HttpMethod_Put: 553 replace = (newOrigin == HttpMethod_Get || 554 newOrigin == HttpMethod_Post || 555 newOrigin == HttpMethod_Delete); 556 break; 557 558 default: 559 throw OrthancException(ErrorCode_ParameterOutOfRange); 560 } 561 } 562 563 if (replace) 564 { 565 summary_ = summary; 566 summaryOrigin_ = newOrigin; 567 } 568 } 569 } 570 GetSummary() const571 const std::string& GetSummary() const 572 { 573 return summary_; 574 } 575 FormatTag(const std::string & tag)576 static std::string FormatTag(const std::string& tag) 577 { 578 if (tag.empty()) 579 { 580 return tag; 581 } 582 else 583 { 584 std::string s; 585 s.reserve(tag.size()); 586 s.push_back(tag[0]); 587 588 for (size_t i = 1; i < tag.size(); i++) 589 { 590 if (tag[i] == ' ') 591 { 592 s.push_back('-'); 593 } 594 else if (isupper(tag[i]) && 595 tag[i - 1] == ' ') 596 { 597 s.push_back(tolower(tag[i])); 598 } 599 else 600 { 601 s.push_back(tag[i]); 602 } 603 } 604 605 return s; 606 } 607 } 608 Format(const std::string & openApiUrl,HttpMethod method,const std::string & uri) const609 std::string Format(const std::string& openApiUrl, 610 HttpMethod method, 611 const std::string& uri) const 612 { 613 std::string p = uri; 614 boost::replace_all(p, "/", "~1"); 615 616 std::string verb; 617 std::string url; 618 619 switch (method) 620 { 621 case HttpMethod_Get: 622 if (hasGet_) 623 { 624 verb = (getDeprecated_ ? "(get)" : "GET"); 625 url = openApiUrl + "#tag/" + FormatTag(getTag_) + "/paths/" + p + "/get"; 626 } 627 break; 628 629 case HttpMethod_Post: 630 if (hasPost_) 631 { 632 verb = (postDeprecated_ ? "(post)" : "POST"); 633 url = openApiUrl + "#tag/" + FormatTag(postTag_) + "/paths/" + p + "/post"; 634 } 635 break; 636 637 case HttpMethod_Delete: 638 if (hasDelete_) 639 { 640 verb = (deleteDeprecated_ ? "(delete)" : "DELETE"); 641 url = openApiUrl + "#tag/" + FormatTag(deleteTag_) + "/paths/" + p + "/delete"; 642 } 643 break; 644 645 case HttpMethod_Put: 646 if (hasPut_) 647 { 648 verb = (putDeprecated_ ? "(put)" : "PUT"); 649 url = openApiUrl + "#tag/" + FormatTag(putTag_) + "/paths/" + p + "/put"; 650 } 651 break; 652 653 default: 654 throw OrthancException(ErrorCode_InternalError); 655 } 656 657 if (verb.empty()) 658 { 659 return ""; 660 } 661 else if (openApiUrl.empty()) 662 { 663 return verb; 664 } 665 else 666 { 667 return "`" + verb + " <" + url + ">`__"; 668 } 669 } 670 HasDeprecated() const671 bool HasDeprecated() const 672 { 673 return ((hasGet_ && getDeprecated_) || 674 (hasPost_ && postDeprecated_) || 675 (hasDelete_ && deleteDeprecated_) || 676 (hasPut_ && putDeprecated_)); 677 } 678 }; 679 680 typedef std::map<std::string, Path> Paths; 681 682 Paths paths_; 683 684 protected: HandleCall(RestApiCall & call,const std::set<std::string> & uriArgumentsNames)685 virtual bool HandleCall(RestApiCall& call, 686 const std::set<std::string>& uriArgumentsNames) ORTHANC_OVERRIDE 687 { 688 Path& path = paths_[ Toolbox::FlattenUri(call.GetFullUri()) ]; 689 690 path.AddMethod(call.GetMethod(), call.GetDocumentation().GetTag(), call.GetDocumentation().IsDeprecated()); 691 692 if (call.GetDocumentation().HasSummary()) 693 { 694 path.SetSummary(call.GetDocumentation().GetSummary(), call.GetMethod()); 695 } 696 697 return true; 698 } 699 700 public: ReStructuredTextCheatSheet(RestApi & restApi)701 explicit ReStructuredTextCheatSheet(RestApi& restApi) : 702 DocumentationVisitor(restApi) 703 { 704 } 705 Format(std::string & target,const std::string & openApiUrl) const706 void Format(std::string& target, 707 const std::string& openApiUrl) const 708 { 709 target += "Path,GET,POST,DELETE,PUT,Summary\n"; 710 for (Paths::const_iterator it = paths_.begin(); it != paths_.end(); ++it) 711 { 712 target += "``" + it->first + "``,"; 713 target += it->second.Format(openApiUrl, HttpMethod_Get, it->first) + ","; 714 target += it->second.Format(openApiUrl, HttpMethod_Post, it->first) + ","; 715 target += it->second.Format(openApiUrl, HttpMethod_Delete, it->first) + ","; 716 target += it->second.Format(openApiUrl, HttpMethod_Put, it->first) + ","; 717 718 if (it->second.HasDeprecated()) 719 { 720 target += "*(deprecated)* "; 721 } 722 723 target += it->second.GetSummary() + "\n"; 724 } 725 } 726 }; 727 } 728 729 730 AddMethod(std::string & target,const std::string & method)731 static void AddMethod(std::string& target, 732 const std::string& method) 733 { 734 if (target.size() > 0) 735 target += "," + method; 736 else 737 target = method; 738 } 739 MethodsToString(const std::set<HttpMethod> & methods)740 static std::string MethodsToString(const std::set<HttpMethod>& methods) 741 { 742 std::string s; 743 744 if (methods.find(HttpMethod_Get) != methods.end()) 745 { 746 AddMethod(s, "GET"); 747 } 748 749 if (methods.find(HttpMethod_Post) != methods.end()) 750 { 751 AddMethod(s, "POST"); 752 } 753 754 if (methods.find(HttpMethod_Put) != methods.end()) 755 { 756 AddMethod(s, "PUT"); 757 } 758 759 if (methods.find(HttpMethod_Delete) != methods.end()) 760 { 761 AddMethod(s, "DELETE"); 762 } 763 764 return s; 765 } 766 767 768 CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader> & target,RequestOrigin origin,const char * remoteIp,const char * username,HttpMethod method,const UriComponents & uri,const HttpToolbox::Arguments & headers)769 bool RestApi::CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target, 770 RequestOrigin origin, 771 const char* remoteIp, 772 const char* username, 773 HttpMethod method, 774 const UriComponents& uri, 775 const HttpToolbox::Arguments& headers) 776 { 777 return false; 778 } 779 780 Handle(HttpOutput & output,RequestOrigin origin,const char * remoteIp,const char * username,HttpMethod method,const UriComponents & uri,const HttpToolbox::Arguments & headers,const HttpToolbox::GetArguments & getArguments,const void * bodyData,size_t bodySize)781 bool RestApi::Handle(HttpOutput& output, 782 RequestOrigin origin, 783 const char* remoteIp, 784 const char* username, 785 HttpMethod method, 786 const UriComponents& uri, 787 const HttpToolbox::Arguments& headers, 788 const HttpToolbox::GetArguments& getArguments, 789 const void* bodyData, 790 size_t bodySize) 791 { 792 RestApiOutput wrappedOutput(output, method); 793 794 #if ORTHANC_ENABLE_PUGIXML == 1 795 { 796 // Look if the client wishes XML answers instead of JSON 797 // http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html#z3 798 HttpToolbox::Arguments::const_iterator it = headers.find("accept"); 799 if (it != headers.end()) 800 { 801 std::vector<std::string> accepted; 802 Toolbox::TokenizeString(accepted, it->second, ';'); 803 for (size_t i = 0; i < accepted.size(); i++) 804 { 805 if (accepted[i] == MIME_XML) 806 { 807 wrappedOutput.SetConvertJsonToXml(true); 808 } 809 810 if (accepted[i] == MIME_JSON) 811 { 812 wrappedOutput.SetConvertJsonToXml(false); 813 } 814 } 815 } 816 } 817 #endif 818 819 HttpToolbox::Arguments compiled; 820 HttpToolbox::CompileGetArguments(compiled, getArguments); 821 822 HttpHandlerVisitor visitor(*this, wrappedOutput, origin, remoteIp, username, 823 method, headers, compiled, bodyData, bodySize); 824 825 if (root_.LookupResource(uri, visitor)) 826 { 827 wrappedOutput.Finalize(); 828 return true; 829 } 830 831 std::set<HttpMethod> methods; 832 root_.GetAcceptedMethods(methods, uri); 833 834 if (methods.empty()) 835 { 836 return false; // This URI is not served by this REST API 837 } 838 else 839 { 840 LOG(INFO) << "REST method " << EnumerationToString(method) 841 << " not allowed on: " << Toolbox::FlattenUri(uri); 842 843 output.SendMethodNotAllowed(MethodsToString(methods)); 844 845 return true; 846 } 847 } 848 Register(const std::string & path,RestApiGetCall::Handler handler)849 void RestApi::Register(const std::string& path, 850 RestApiGetCall::Handler handler) 851 { 852 root_.Register(path, handler); 853 } 854 Register(const std::string & path,RestApiPutCall::Handler handler)855 void RestApi::Register(const std::string& path, 856 RestApiPutCall::Handler handler) 857 { 858 root_.Register(path, handler); 859 } 860 Register(const std::string & path,RestApiPostCall::Handler handler)861 void RestApi::Register(const std::string& path, 862 RestApiPostCall::Handler handler) 863 { 864 root_.Register(path, handler); 865 } 866 Register(const std::string & path,RestApiDeleteCall::Handler handler)867 void RestApi::Register(const std::string& path, 868 RestApiDeleteCall::Handler handler) 869 { 870 root_.Register(path, handler); 871 } 872 AutoListChildren(RestApiGetCall & call)873 void RestApi::AutoListChildren(RestApiGetCall& call) 874 { 875 call.GetDocumentation() 876 .SetTag("Other") 877 .SetSummary("List operations") 878 .SetDescription("List the available operations under URI `" + call.FlattenUri() + "`") 879 .AddAnswerType(MimeType_Json, "List of the available operations"); 880 881 RestApi& context = call.GetContext(); 882 883 Json::Value directory; 884 if (context.root_.GetDirectory(directory, call.GetFullUri())) 885 { 886 if (call.IsDocumentation()) 887 { 888 call.GetDocumentation().SetSample(directory); 889 890 std::set<std::string> c; 891 call.GetUriComponentsNames(c); 892 for (std::set<std::string>::const_iterator it = c.begin(); it != c.end(); ++it) 893 { 894 call.GetDocumentation().SetUriArgument(*it, RestApiCallDocumentation::Type_String, ""); 895 } 896 } 897 else 898 { 899 call.GetOutput().AnswerJson(directory); 900 } 901 } 902 } 903 904 GenerateOpenApiDocumentation(Json::Value & target)905 void RestApi::GenerateOpenApiDocumentation(Json::Value& target) 906 { 907 OpenApiVisitor visitor(*this); 908 909 UriComponents root; 910 std::set<std::string> uriArgumentsNames; 911 root_.ExploreAllResources(visitor, root, uriArgumentsNames); 912 913 target = Json::objectValue; 914 915 target["info"] = Json::objectValue; 916 target["openapi"] = "3.0.0"; 917 target["servers"] = Json::arrayValue; 918 target["paths"] = visitor.GetPaths(); 919 920 visitor.LogStatistics(); 921 } 922 923 GenerateReStructuredTextCheatSheet(std::string & target,const std::string & openApiUrl)924 void RestApi::GenerateReStructuredTextCheatSheet(std::string& target, 925 const std::string& openApiUrl) 926 { 927 ReStructuredTextCheatSheet visitor(*this); 928 929 UriComponents root; 930 std::set<std::string> uriArgumentsNames; 931 root_.ExploreAllResources(visitor, root, uriArgumentsNames); 932 933 visitor.Format(target, openApiUrl); 934 935 visitor.LogStatistics(); 936 } 937 } 938