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
18namespace
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
45namespace 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 headers;
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 headers;
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 headers;
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