1//===--- HTTPServer.cpp - HTTP server library -----------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// \file
10///
11/// This file defines the methods of the HTTPServer class and the streamFile
12/// function.
13///
14//===----------------------------------------------------------------------===//
15
16#include "llvm/Support/HTTP/HTTPServer.h"
17
18#include "llvm/ADT/StringExtras.h"
19#include "llvm/ADT/StringRef.h"
20#include "llvm/Support/Errc.h"
21#include "llvm/Support/Error.h"
22#include "llvm/Support/FileSystem.h"
23#include "llvm/Support/MemoryBuffer.h"
24#include "llvm/Support/Regex.h"
25
26#ifdef LLVM_ENABLE_HTTPLIB
27#include "httplib.h"
28#endif
29
30using namespace llvm;
31
32char HTTPServerError::ID = 0;
33
34HTTPServerError::HTTPServerError(const Twine &Msg) : Msg(Msg.str()) {}
35
36void HTTPServerError::log(raw_ostream &OS) const { OS << Msg; }
37
38bool llvm::streamFile(HTTPServerRequest &Request, StringRef FilePath) {
39 Expected<sys::fs::file_t> FDOrErr = sys::fs::openNativeFileForRead(Name: FilePath);
40 if (Error Err = FDOrErr.takeError()) {
41 consumeError(Err: std::move(Err));
42 Request.setResponse({.Code: 404u, .ContentType: "text/plain", .Body: "Could not open file to read.\n"});
43 return false;
44 }
45 ErrorOr<std::unique_ptr<MemoryBuffer>> MBOrErr =
46 MemoryBuffer::getOpenFile(FD: *FDOrErr, Filename: FilePath,
47 /*FileSize=*/-1,
48 /*RequiresNullTerminator=*/false);
49 sys::fs::closeFile(F&: *FDOrErr);
50 if (Error Err = errorCodeToError(EC: MBOrErr.getError())) {
51 consumeError(Err: std::move(Err));
52 Request.setResponse({.Code: 404u, .ContentType: "text/plain", .Body: "Could not memory-map file.\n"});
53 return false;
54 }
55 // Lambdas are copied on conversion to std::function, preventing use of
56 // smart pointers.
57 MemoryBuffer *MB = MBOrErr->release();
58 Request.setResponse({.Code: 200u, .ContentType: "application/octet-stream", .ContentLength: MB->getBufferSize(),
59 .Provider: [=](size_t Offset, size_t Length) -> StringRef {
60 return MB->getBuffer().substr(Start: Offset, N: Length);
61 },
62 .CompletionHandler: [=](bool Success) { delete MB; }});
63 return true;
64}
65
66#ifdef LLVM_ENABLE_HTTPLIB
67
68bool HTTPServer::isAvailable() { return true; }
69
70HTTPServer::HTTPServer() { Server = std::make_unique<httplib::Server>(); }
71
72HTTPServer::~HTTPServer() { stop(); }
73
74static void expandUrlPathMatches(const std::smatch &Matches,
75 HTTPServerRequest &Request) {
76 bool UrlPathSet = false;
77 for (const auto &it : Matches) {
78 if (UrlPathSet)
79 Request.UrlPathMatches.push_back(it);
80 else {
81 Request.UrlPath = it;
82 UrlPathSet = true;
83 }
84 }
85}
86
87HTTPServerRequest::HTTPServerRequest(const httplib::Request &HTTPLibRequest,
88 httplib::Response &HTTPLibResponse)
89 : HTTPLibResponse(HTTPLibResponse) {
90 expandUrlPathMatches(HTTPLibRequest.matches, *this);
91}
92
93void HTTPServerRequest::setResponse(HTTPResponse Response) {
94 HTTPLibResponse.set_content(Response.Body.begin(), Response.Body.size(),
95 Response.ContentType);
96 HTTPLibResponse.status = Response.Code;
97}
98
99void HTTPServerRequest::setResponse(StreamingHTTPResponse Response) {
100 HTTPLibResponse.set_content_provider(
101 Response.ContentLength, Response.ContentType,
102 [=](size_t Offset, size_t Length, httplib::DataSink &Sink) {
103 if (Offset < Response.ContentLength) {
104 StringRef Chunk = Response.Provider(Offset, Length);
105 Sink.write(Chunk.begin(), Chunk.size());
106 }
107 return true;
108 },
109 [=](bool Success) { Response.CompletionHandler(Success); });
110
111 HTTPLibResponse.status = Response.Code;
112}
113
114Error HTTPServer::get(StringRef UrlPathPattern, HTTPRequestHandler Handler) {
115 std::string ErrorMessage;
116 if (!Regex(UrlPathPattern).isValid(ErrorMessage))
117 return createStringError(errc::argument_out_of_domain, ErrorMessage);
118 Server->Get(std::string(UrlPathPattern),
119 [Handler](const httplib::Request &HTTPLibRequest,
120 httplib::Response &HTTPLibResponse) {
121 HTTPServerRequest Request(HTTPLibRequest, HTTPLibResponse);
122 Handler(Request);
123 });
124 return Error::success();
125}
126
127Error HTTPServer::bind(unsigned ListenPort, const char *HostInterface) {
128 if (!Server->bind_to_port(HostInterface, ListenPort))
129 return createStringError(errc::io_error,
130 "Could not assign requested address.");
131 Port = ListenPort;
132 return Error::success();
133}
134
135Expected<unsigned> HTTPServer::bind(const char *HostInterface) {
136 int ListenPort = Server->bind_to_any_port(HostInterface);
137 if (ListenPort < 0)
138 return createStringError(errc::io_error,
139 "Could not assign any port on requested address.");
140 return Port = ListenPort;
141}
142
143Error HTTPServer::listen() {
144 if (!Port)
145 return createStringError(errc::io_error,
146 "Cannot listen without first binding to a port.");
147 if (!Server->listen_after_bind())
148 return createStringError(
149 errc::io_error,
150 "An unknown error occurred when cpp-httplib attempted to listen.");
151 return Error::success();
152}
153
154void HTTPServer::stop() {
155 Server->stop();
156 Port = 0;
157}
158
159#else
160
161// TODO: Implement barebones standalone HTTP server implementation.
162bool HTTPServer::isAvailable() { return false; }
163
164HTTPServer::HTTPServer() = default;
165
166HTTPServer::~HTTPServer() = default;
167
168void HTTPServerRequest::setResponse(HTTPResponse Response) {
169 llvm_unreachable("no httplib");
170}
171
172void HTTPServerRequest::setResponse(StreamingHTTPResponse Response) {
173 llvm_unreachable("no httplib");
174}
175
176Error HTTPServer::get(StringRef UrlPathPattern, HTTPRequestHandler Handler) {
177 // TODO(https://github.com/llvm/llvm-project/issues/63873) We would ideally
178 // return an error as well but that's going to require refactoring of error
179 // handling in DebuginfodServer.
180 return Error::success();
181}
182
183Error HTTPServer::bind(unsigned ListenPort, const char *HostInterface) {
184 return make_error<HTTPServerError>(Args: "no httplib");
185}
186
187Expected<unsigned> HTTPServer::bind(const char *HostInterface) {
188 return make_error<HTTPServerError>(Args: "no httplib");
189}
190
191Error HTTPServer::listen() { return make_error<HTTPServerError>(Args: "no httplib"); }
192
193void HTTPServer::stop() { llvm_unreachable("no httplib"); }
194
195#endif // LLVM_ENABLE_HTTPLIB
196