1 | /* |
2 | * IXHttpServer.cpp |
3 | * Author: Benjamin Sergeant |
4 | * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |
5 | */ |
6 | |
7 | #include "IXHttpServer.h" |
8 | |
9 | #include "IXGzipCodec.h" |
10 | #include "IXNetSystem.h" |
11 | #include "IXSocketConnect.h" |
12 | #include "IXUserAgent.h" |
13 | #include <cstring> |
14 | #include <fstream> |
15 | #include <sstream> |
16 | #include <vector> |
17 | |
18 | namespace |
19 | { |
20 | std::pair<bool, std::vector<uint8_t>> load(const std::string& path) |
21 | { |
22 | std::vector<uint8_t> memblock; |
23 | |
24 | std::ifstream file(path); |
25 | if (!file.is_open()) return std::make_pair(false, memblock); |
26 | |
27 | file.seekg(0, file.end); |
28 | std::streamoff size = file.tellg(); |
29 | file.seekg(0, file.beg); |
30 | |
31 | memblock.resize((size_t) size); |
32 | file.read((char*) &memblock.front(), static_cast<std::streamsize>(size)); |
33 | |
34 | return std::make_pair(true, memblock); |
35 | } |
36 | |
37 | std::pair<bool, std::string> readAsString(const std::string& path) |
38 | { |
39 | auto res = load(path); |
40 | auto vec = res.second; |
41 | return std::make_pair(res.first, std::string(vec.begin(), vec.end())); |
42 | } |
43 | } // namespace |
44 | |
45 | namespace ix |
46 | { |
47 | const int HttpServer::kDefaultTimeoutSecs(30); |
48 | |
49 | HttpServer::HttpServer(int port, |
50 | const std::string& host, |
51 | int backlog, |
52 | size_t maxConnections, |
53 | int addressFamily, |
54 | int timeoutSecs) |
55 | : SocketServer(port, host, backlog, maxConnections, addressFamily) |
56 | , _connectedClientsCount(0) |
57 | , _timeoutSecs(timeoutSecs) |
58 | { |
59 | setDefaultConnectionCallback(); |
60 | } |
61 | |
62 | HttpServer::~HttpServer() |
63 | { |
64 | stop(); |
65 | } |
66 | |
67 | void HttpServer::stop() |
68 | { |
69 | stopAcceptingConnections(); |
70 | |
71 | // FIXME: cancelling / closing active clients ... |
72 | |
73 | SocketServer::stop(); |
74 | } |
75 | |
76 | void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback) |
77 | { |
78 | _onConnectionCallback = callback; |
79 | } |
80 | |
81 | void HttpServer::handleConnection(std::unique_ptr<Socket> socket, |
82 | std::shared_ptr<ConnectionState> connectionState) |
83 | { |
84 | _connectedClientsCount++; |
85 | |
86 | auto ret = Http::parseRequest(socket, _timeoutSecs); |
87 | // FIXME: handle errors in parseRequest |
88 | |
89 | if (std::get<0>(ret)) |
90 | { |
91 | auto response = _onConnectionCallback(std::get<2>(ret), connectionState); |
92 | if (!Http::sendResponse(response, socket)) |
93 | { |
94 | logError("Cannot send response" ); |
95 | } |
96 | } |
97 | connectionState->setTerminated(); |
98 | |
99 | _connectedClientsCount--; |
100 | } |
101 | |
102 | size_t HttpServer::getConnectedClientsCount() |
103 | { |
104 | return _connectedClientsCount; |
105 | } |
106 | |
107 | void HttpServer::setDefaultConnectionCallback() |
108 | { |
109 | setOnConnectionCallback( |
110 | [this](HttpRequestPtr request, |
111 | std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr { |
112 | std::string uri(request->uri); |
113 | if (uri.empty() || uri == "/" ) |
114 | { |
115 | uri = "/index.html" ; |
116 | } |
117 | |
118 | WebSocketHttpHeaders ; |
119 | headers["Server" ] = userAgent(); |
120 | |
121 | std::string path("." + uri); |
122 | auto res = readAsString(path); |
123 | bool found = res.first; |
124 | if (!found) |
125 | { |
126 | return std::make_shared<HttpResponse>( |
127 | 404, "Not Found" , HttpErrorCode::Ok, WebSocketHttpHeaders(), std::string()); |
128 | } |
129 | |
130 | std::string content = res.second; |
131 | |
132 | #ifdef IXWEBSOCKET_USE_ZLIB |
133 | std::string acceptEncoding = request->headers["Accept-encoding" ]; |
134 | if (acceptEncoding == "*" || acceptEncoding.find("gzip" ) != std::string::npos) |
135 | { |
136 | content = gzipCompress(content); |
137 | headers["Content-Encoding" ] = "gzip" ; |
138 | } |
139 | #endif |
140 | |
141 | // Log request |
142 | std::stringstream ss; |
143 | ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort() |
144 | << " " << request->method << " " << request->headers["User-Agent" ] << " " |
145 | << request->uri << " " << content.size(); |
146 | logInfo(ss.str()); |
147 | |
148 | // FIXME: check extensions to set the content type |
149 | // headers["Content-Type"] = "application/octet-stream"; |
150 | headers["Accept-Ranges" ] = "none" ; |
151 | |
152 | for (auto&& it : request->headers) |
153 | { |
154 | headers[it.first] = it.second; |
155 | } |
156 | |
157 | return std::make_shared<HttpResponse>( |
158 | 200, "OK" , HttpErrorCode::Ok, headers, content); |
159 | }); |
160 | } |
161 | |
162 | void HttpServer::makeRedirectServer(const std::string& redirectUrl) |
163 | { |
164 | // |
165 | // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections |
166 | // |
167 | setOnConnectionCallback( |
168 | [this, |
169 | redirectUrl](HttpRequestPtr request, |
170 | std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr { |
171 | WebSocketHttpHeaders ; |
172 | headers["Server" ] = userAgent(); |
173 | |
174 | // Log request |
175 | std::stringstream ss; |
176 | ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort() |
177 | << " " << request->method << " " << request->headers["User-Agent" ] << " " |
178 | << request->uri; |
179 | logInfo(ss.str()); |
180 | |
181 | if (request->method == "POST" ) |
182 | { |
183 | return std::make_shared<HttpResponse>( |
184 | 200, "OK" , HttpErrorCode::Ok, headers, std::string()); |
185 | } |
186 | |
187 | headers["Location" ] = redirectUrl; |
188 | |
189 | return std::make_shared<HttpResponse>( |
190 | 301, "OK" , HttpErrorCode::Ok, headers, std::string()); |
191 | }); |
192 | } |
193 | |
194 | // |
195 | // Display the client parameter and body on the console |
196 | // |
197 | void HttpServer::makeDebugServer() |
198 | { |
199 | setOnConnectionCallback( |
200 | [this](HttpRequestPtr request, |
201 | std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr { |
202 | WebSocketHttpHeaders ; |
203 | headers["Server" ] = userAgent(); |
204 | |
205 | // Log request |
206 | std::stringstream ss; |
207 | ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort() |
208 | << " " << request->method << " " << request->headers["User-Agent" ] << " " |
209 | << request->uri; |
210 | logInfo(ss.str()); |
211 | |
212 | logInfo("== Headers == " ); |
213 | for (auto&& it : request->headers) |
214 | { |
215 | std::ostringstream oss; |
216 | oss << it.first << ": " << it.second; |
217 | logInfo(oss.str()); |
218 | } |
219 | logInfo("" ); |
220 | |
221 | logInfo("== Body == " ); |
222 | logInfo(request->body); |
223 | logInfo("" ); |
224 | |
225 | return std::make_shared<HttpResponse>( |
226 | 200, "OK" , HttpErrorCode::Ok, headers, std::string("OK" )); |
227 | }); |
228 | } |
229 | |
230 | int HttpServer::getTimeoutSecs() |
231 | { |
232 | return _timeoutSecs; |
233 | } |
234 | |
235 | } // namespace ix |
236 | |