]>
Commit | Line | Data |
---|---|---|
1e59de90 TL |
1 | // Copyright The OpenTelemetry Authors |
2 | // SPDX-License-Identifier: Apache-2.0 | |
3 | ||
4 | #pragma once | |
5 | ||
6 | #include <fstream> | |
7 | #include <iostream> | |
8 | #include <string> | |
9 | #include <unordered_map> | |
10 | #include <vector> | |
11 | ||
12 | #include "opentelemetry/ext/http/server/http_server.h" | |
13 | ||
14 | namespace HTTP_SERVER_NS | |
15 | { | |
16 | ||
17 | class FileHttpServer : public HTTP_SERVER_NS::HttpServer | |
18 | { | |
19 | protected: | |
20 | /** | |
21 | * Construct the server by initializing the endpoint for serving static files, | |
22 | * which show up on the web if the user is on the given host:port. Static | |
23 | * files can be seen relative to the folder where the executable was ran. | |
24 | */ | |
25 | FileHttpServer(const std::string &host = "127.0.0.1", int port = 3333) : HttpServer() | |
26 | { | |
27 | std::ostringstream os; | |
28 | os << host << ":" << port; | |
29 | setServerName(os.str()); | |
30 | addListeningPort(port); | |
31 | }; | |
32 | ||
33 | /** | |
34 | * Set the HTTP server to serve static files from the root of host:port. | |
35 | * Derived HTTP servers should initialize the file endpoint AFTER they | |
36 | * initialize their own, otherwise everything will be served like a file | |
37 | * @param server should be an instance of this object | |
38 | */ | |
39 | void InitializeFileEndpoint(FileHttpServer &server) { server[root_endpt_] = ServeFile; } | |
40 | ||
41 | private: | |
42 | /** | |
43 | * Return whether a file is found whose location is searched for relative to | |
44 | * where the executable was triggered. If the file is valid, fill result with | |
45 | * the file data/information required to display it on a webpage | |
46 | * @param name of the file to look for, | |
47 | * @param resulting file information, necessary for displaying them on a | |
48 | * webpage | |
49 | * @returns whether a file was found and result filled with display | |
50 | * information | |
51 | */ | |
52 | bool FileGetSuccess(const std::string &filename, std::vector<char> &result) | |
53 | { | |
54 | #ifdef _WIN32 | |
55 | std::replace(filename.begin(), filename.end(), '/', '\\'); | |
56 | #endif | |
57 | std::streampos size; | |
58 | std::ifstream file(filename, std::ios::in | std::ios::binary | std::ios::ate); | |
59 | if (file.is_open()) | |
60 | { | |
61 | size = file.tellg(); | |
62 | if (size) | |
63 | { | |
64 | result.resize(size); | |
65 | file.seekg(0, std::ios::beg); | |
66 | file.read(result.data(), size); | |
67 | } | |
68 | file.close(); | |
69 | return true; | |
70 | } | |
71 | return false; | |
72 | }; | |
73 | ||
74 | /** | |
75 | * Returns the extension of a file | |
76 | * @param name of the file | |
77 | * @returns file extension type under HTTP protocol | |
78 | */ | |
79 | std::string GetMimeContentType(const std::string &filename) | |
80 | { | |
81 | std::string file_ext = filename.substr(filename.find_last_of(".") + 1); | |
82 | auto file_type = mime_types_.find(file_ext); | |
83 | return (file_type != mime_types_.end()) ? file_type->second : HTTP_SERVER_NS::CONTENT_TYPE_TEXT; | |
84 | }; | |
85 | ||
86 | /** | |
87 | * Returns the standardized name of a file by removing backslashes, and | |
88 | * assuming index.html is the wanted file if a directory is given | |
89 | * @param name of the file | |
90 | */ | |
91 | std::string GetFileName(std::string name) | |
92 | { | |
93 | if (name.back() == '/') | |
94 | { | |
95 | auto temp = name.substr(0, name.size() - 1); | |
96 | name = temp; | |
97 | } | |
98 | // If filename appears to be a directory, serve the hypothetical index.html | |
99 | // file there | |
100 | if (name.find(".") == std::string::npos) | |
101 | name += "/index.html"; | |
102 | ||
103 | return name; | |
104 | } | |
105 | ||
106 | /** | |
107 | * Sets the response object with the correct file data based on the requested | |
108 | * file address, or return 404 error if a file isn't found | |
109 | * @param req is the HTTP request, which we use to figure out the response to | |
110 | * send | |
111 | * @param resp is the HTTP response we want to send to the frontend, including | |
112 | * file data | |
113 | */ | |
114 | HTTP_SERVER_NS::HttpRequestCallback ServeFile{ | |
115 | [&](HTTP_SERVER_NS::HttpRequest const &req, HTTP_SERVER_NS::HttpResponse &resp) { | |
116 | LOG_INFO("File: %s\n", req.uri.c_str()); | |
117 | auto f = GetFileName(req.uri); | |
118 | auto filename = f.c_str() + 1; | |
119 | ||
120 | std::vector<char> content; | |
121 | if (FileGetSuccess(filename, content)) | |
122 | { | |
123 | resp.headers[HTTP_SERVER_NS::CONTENT_TYPE] = GetMimeContentType(filename); | |
124 | resp.body = std::string(content.data(), content.size()); | |
125 | resp.code = 200; | |
126 | resp.message = HTTP_SERVER_NS::HttpServer::getDefaultResponseMessage(resp.code); | |
127 | return resp.code; | |
128 | } | |
129 | // Two additional 'special' return codes possible here: | |
130 | // 0 - proceed to next handler | |
131 | // -1 - immediately terminate and close connection | |
132 | resp.headers[HTTP_SERVER_NS::CONTENT_TYPE] = HTTP_SERVER_NS::CONTENT_TYPE_TEXT; | |
133 | resp.code = 404; | |
134 | resp.message = HTTP_SERVER_NS::HttpServer::getDefaultResponseMessage(resp.code); | |
135 | resp.body = resp.message; | |
136 | return 404; | |
137 | }}; | |
138 | ||
139 | // Maps file extensions to their HTTP-compatible mime file type | |
140 | const std::unordered_map<std::string, std::string> mime_types_ = { | |
141 | {"css", "text/css"}, {"png", "image/png"}, {"js", "text/javascript"}, | |
142 | {"htm", "text/html"}, {"html", "text/html"}, {"json", "application/json"}, | |
143 | {"txt", "text/plain"}, {"jpg", "image/jpeg"}, {"jpeg", "image/jpeg"}, | |
144 | }; | |
145 | const std::string root_endpt_ = "/"; | |
146 | }; | |
147 | ||
148 | } // namespace HTTP_SERVER_NS |