2019-06-23 23:54:21 +02:00
|
|
|
/*
|
|
|
|
* IXHttpServer.cpp
|
|
|
|
* Author: Benjamin Sergeant
|
|
|
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "IXHttpServer.h"
|
2019-09-23 19:25:23 +02:00
|
|
|
|
2020-09-28 19:19:27 +02:00
|
|
|
#include "IXGzipCodec.h"
|
2019-09-23 19:25:23 +02:00
|
|
|
#include "IXNetSystem.h"
|
2019-06-23 23:54:21 +02:00
|
|
|
#include "IXSocketConnect.h"
|
2019-09-26 18:45:59 +02:00
|
|
|
#include "IXUserAgent.h"
|
2020-07-08 19:39:46 +02:00
|
|
|
#include <cstring>
|
2019-09-23 19:25:23 +02:00
|
|
|
#include <fstream>
|
2019-06-23 23:54:21 +02:00
|
|
|
#include <sstream>
|
|
|
|
#include <vector>
|
2020-08-01 07:54:57 +02:00
|
|
|
|
2019-06-23 23:54:21 +02:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
std::pair<bool, std::vector<uint8_t>> load(const std::string& path)
|
|
|
|
{
|
|
|
|
std::vector<uint8_t> memblock;
|
|
|
|
|
|
|
|
std::ifstream file(path);
|
|
|
|
if (!file.is_open()) return std::make_pair(false, memblock);
|
|
|
|
|
|
|
|
file.seekg(0, file.end);
|
|
|
|
std::streamoff size = file.tellg();
|
|
|
|
file.seekg(0, file.beg);
|
|
|
|
|
|
|
|
memblock.resize((size_t) size);
|
2019-09-23 19:25:23 +02:00
|
|
|
file.read((char*) &memblock.front(), static_cast<std::streamsize>(size));
|
2019-06-23 23:54:21 +02:00
|
|
|
|
|
|
|
return std::make_pair(true, memblock);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::pair<bool, std::string> readAsString(const std::string& path)
|
|
|
|
{
|
|
|
|
auto res = load(path);
|
|
|
|
auto vec = res.second;
|
|
|
|
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
|
|
|
}
|
2019-09-23 19:25:23 +02:00
|
|
|
} // namespace
|
2019-06-23 23:54:21 +02:00
|
|
|
|
|
|
|
namespace ix
|
|
|
|
{
|
2020-09-12 23:17:06 +02:00
|
|
|
const int HttpServer::kDefaultTimeoutSecs(30);
|
|
|
|
|
|
|
|
HttpServer::HttpServer(int port,
|
|
|
|
const std::string& host,
|
|
|
|
int backlog,
|
|
|
|
size_t maxConnections,
|
|
|
|
int addressFamily,
|
|
|
|
int timeoutSecs)
|
2020-01-27 01:17:26 +01:00
|
|
|
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
2019-09-23 19:25:23 +02:00
|
|
|
, _connectedClientsCount(0)
|
2020-09-12 23:17:06 +02:00
|
|
|
, _timeoutSecs(timeoutSecs)
|
2019-06-23 23:54:21 +02:00
|
|
|
{
|
|
|
|
setDefaultConnectionCallback();
|
|
|
|
}
|
|
|
|
|
|
|
|
HttpServer::~HttpServer()
|
|
|
|
{
|
|
|
|
stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpServer::stop()
|
|
|
|
{
|
|
|
|
stopAcceptingConnections();
|
|
|
|
|
|
|
|
// FIXME: cancelling / closing active clients ...
|
|
|
|
|
|
|
|
SocketServer::stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
|
|
|
|
{
|
|
|
|
_onConnectionCallback = callback;
|
|
|
|
}
|
|
|
|
|
2020-03-24 20:40:58 +01:00
|
|
|
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
|
2020-08-28 23:55:40 +02:00
|
|
|
std::shared_ptr<ConnectionState> connectionState)
|
2019-06-23 23:54:21 +02:00
|
|
|
{
|
|
|
|
_connectedClientsCount++;
|
|
|
|
|
2020-09-12 23:17:06 +02:00
|
|
|
auto ret = Http::parseRequest(socket, _timeoutSecs);
|
2019-06-23 23:54:21 +02:00
|
|
|
// FIXME: handle errors in parseRequest
|
|
|
|
|
|
|
|
if (std::get<0>(ret))
|
|
|
|
{
|
2020-08-28 23:55:40 +02:00
|
|
|
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
|
2019-06-23 23:54:21 +02:00
|
|
|
if (!Http::sendResponse(response, socket))
|
|
|
|
{
|
|
|
|
logError("Cannot send response");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
connectionState->setTerminated();
|
|
|
|
|
|
|
|
_connectedClientsCount--;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t HttpServer::getConnectedClientsCount()
|
|
|
|
{
|
|
|
|
return _connectedClientsCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpServer::setDefaultConnectionCallback()
|
|
|
|
{
|
|
|
|
setOnConnectionCallback(
|
|
|
|
[this](HttpRequestPtr request,
|
2020-08-28 23:55:40 +02:00
|
|
|
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
2019-06-23 23:54:21 +02:00
|
|
|
std::string uri(request->uri);
|
|
|
|
if (uri.empty() || uri == "/")
|
|
|
|
{
|
|
|
|
uri = "/index.html";
|
|
|
|
}
|
|
|
|
|
2019-09-26 18:45:59 +02:00
|
|
|
WebSocketHttpHeaders headers;
|
|
|
|
headers["Server"] = userAgent();
|
|
|
|
|
2019-06-23 23:54:21 +02:00
|
|
|
std::string path("." + uri);
|
|
|
|
auto res = readAsString(path);
|
|
|
|
bool found = res.first;
|
|
|
|
if (!found)
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(
|
|
|
|
404, "Not Found", HttpErrorCode::Ok, WebSocketHttpHeaders(), std::string());
|
2019-06-23 23:54:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string content = res.second;
|
|
|
|
|
2020-08-01 07:54:57 +02:00
|
|
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
2020-05-30 01:49:29 +02:00
|
|
|
std::string acceptEncoding = request->headers["Accept-encoding"];
|
2020-06-02 02:11:42 +02:00
|
|
|
if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos)
|
2020-05-30 01:49:29 +02:00
|
|
|
{
|
|
|
|
content = gzipCompress(content);
|
2020-06-02 02:11:42 +02:00
|
|
|
headers["Content-Encoding"] = "gzip";
|
2020-05-30 01:49:29 +02:00
|
|
|
}
|
2020-08-01 07:54:57 +02:00
|
|
|
#endif
|
2020-05-30 01:49:29 +02:00
|
|
|
|
2019-06-23 23:54:21 +02:00
|
|
|
// Log request
|
|
|
|
std::stringstream ss;
|
2020-08-28 23:55:40 +02:00
|
|
|
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
|
|
|
<< " " << request->method << " " << request->headers["User-Agent"] << " "
|
2019-09-23 19:25:23 +02:00
|
|
|
<< request->uri << " " << content.size();
|
2019-06-23 23:54:21 +02:00
|
|
|
logInfo(ss.str());
|
|
|
|
|
|
|
|
// FIXME: check extensions to set the content type
|
|
|
|
// headers["Content-Type"] = "application/octet-stream";
|
|
|
|
headers["Accept-Ranges"] = "none";
|
|
|
|
|
|
|
|
for (auto&& it : request->headers)
|
|
|
|
{
|
|
|
|
headers[it.first] = it.second;
|
|
|
|
}
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(
|
|
|
|
200, "OK", HttpErrorCode::Ok, headers, content);
|
|
|
|
});
|
2019-06-23 23:54:21 +02:00
|
|
|
}
|
2019-09-26 18:45:59 +02:00
|
|
|
|
|
|
|
void HttpServer::makeRedirectServer(const std::string& redirectUrl)
|
|
|
|
{
|
|
|
|
//
|
|
|
|
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
|
|
|
//
|
|
|
|
setOnConnectionCallback(
|
2020-08-28 23:55:40 +02:00
|
|
|
[this,
|
|
|
|
redirectUrl](HttpRequestPtr request,
|
|
|
|
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
2019-09-26 18:45:59 +02:00
|
|
|
WebSocketHttpHeaders headers;
|
|
|
|
headers["Server"] = userAgent();
|
|
|
|
|
|
|
|
// Log request
|
|
|
|
std::stringstream ss;
|
2020-08-28 23:55:40 +02:00
|
|
|
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
|
|
|
<< " " << request->method << " " << request->headers["User-Agent"] << " "
|
2019-09-26 18:45:59 +02:00
|
|
|
<< request->uri;
|
|
|
|
logInfo(ss.str());
|
|
|
|
|
|
|
|
if (request->method == "POST")
|
|
|
|
{
|
|
|
|
return std::make_shared<HttpResponse>(
|
|
|
|
200, "OK", HttpErrorCode::Ok, headers, std::string());
|
|
|
|
}
|
|
|
|
|
|
|
|
headers["Location"] = redirectUrl;
|
|
|
|
|
|
|
|
return std::make_shared<HttpResponse>(
|
|
|
|
301, "OK", HttpErrorCode::Ok, headers, std::string());
|
|
|
|
});
|
|
|
|
}
|
2020-10-08 21:43:18 +02:00
|
|
|
|
|
|
|
//
|
|
|
|
// Display the client parameter and body on the console
|
|
|
|
//
|
|
|
|
void HttpServer::makeDebugServer()
|
|
|
|
{
|
|
|
|
setOnConnectionCallback(
|
|
|
|
[this](HttpRequestPtr request,
|
|
|
|
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
|
|
|
WebSocketHttpHeaders headers;
|
|
|
|
headers["Server"] = userAgent();
|
|
|
|
|
|
|
|
// Log request
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
|
|
|
<< " " << request->method << " " << request->headers["User-Agent"] << " "
|
|
|
|
<< request->uri;
|
|
|
|
logInfo(ss.str());
|
|
|
|
|
|
|
|
logInfo("== Headers == ");
|
|
|
|
for (auto&& it : request->headers)
|
|
|
|
{
|
|
|
|
std::ostringstream oss;
|
|
|
|
oss << it.first << ": " << it.second;
|
|
|
|
logInfo(oss.str());
|
|
|
|
}
|
|
|
|
logInfo("");
|
|
|
|
|
|
|
|
logInfo("== Body == ");
|
|
|
|
logInfo(request->body);
|
|
|
|
logInfo("");
|
|
|
|
|
|
|
|
return std::make_shared<HttpResponse>(
|
|
|
|
200, "OK", HttpErrorCode::Ok, headers, std::string("OK"));
|
|
|
|
});
|
|
|
|
}
|
2019-09-23 19:25:23 +02:00
|
|
|
} // namespace ix
|