Feature/httpd (#94)
* Stub code for http server * can send a response, cannot process body yet * write headers to the response * remove commented code * add simple test + set default http handler * tweak CI + unittest * add missing file * rewrite http::trim in a simple way * doc
This commit is contained in:
committed by
GitHub
parent
43deaba547
commit
691502d7ad
138
ixwebsocket/IXHttp.cpp
Normal file
138
ixwebsocket/IXHttp.cpp
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* IXHttp.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXHttp.h"
|
||||
#include "IXCancellationRequest.h"
|
||||
#include "IXSocket.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
std::string Http::trim(const std::string& str)
|
||||
{
|
||||
std::string out;
|
||||
for (auto c : str)
|
||||
{
|
||||
if (c != ' ' && c != '\n' && c != '\r')
|
||||
{
|
||||
out += c;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string, std::string> Http::parseRequestLine(const std::string& line)
|
||||
{
|
||||
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||
std::string token;
|
||||
std::stringstream tokenStream(line);
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
// Split by ' '
|
||||
while (std::getline(tokenStream, token, ' '))
|
||||
{
|
||||
tokens.push_back(token);
|
||||
}
|
||||
|
||||
std::string method;
|
||||
if (tokens.size() >= 1)
|
||||
{
|
||||
method = trim(tokens[0]);
|
||||
}
|
||||
|
||||
std::string requestUri;
|
||||
if (tokens.size() >= 2)
|
||||
{
|
||||
requestUri = trim(tokens[1]);
|
||||
}
|
||||
|
||||
std::string httpVersion;
|
||||
if (tokens.size() >= 3)
|
||||
{
|
||||
httpVersion = trim(tokens[2]);
|
||||
}
|
||||
|
||||
return std::make_tuple(method, requestUri, httpVersion);
|
||||
}
|
||||
|
||||
std::tuple<bool, std::string, HttpRequestPtr> Http::parseRequest(std::shared_ptr<Socket> socket)
|
||||
{
|
||||
HttpRequestPtr httpRequest;
|
||||
|
||||
std::atomic<bool> requestInitCancellation(false);
|
||||
|
||||
int timeoutSecs = 5; // FIXME
|
||||
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation);
|
||||
|
||||
// Read first line
|
||||
auto lineResult = socket->readLine(isCancellationRequested);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
if (!lineValid)
|
||||
{
|
||||
return std::make_tuple(false, "Error reading HTTP request line", httpRequest);
|
||||
}
|
||||
|
||||
// Parse request line (GET /foo HTTP/1.1\r\n)
|
||||
auto requestLine = Http::parseRequestLine(line);
|
||||
auto method = std::get<0>(requestLine);
|
||||
auto uri = std::get<1>(requestLine);
|
||||
auto httpVersion = std::get<2>(requestLine);
|
||||
|
||||
// Retrieve and validate HTTP headers
|
||||
auto result = parseHttpHeaders(socket, isCancellationRequested);
|
||||
auto headersValid = result.first;
|
||||
auto headers = result.second;
|
||||
|
||||
if (!headersValid)
|
||||
{
|
||||
return std::make_tuple(false, "Error parsing HTTP headers", httpRequest);
|
||||
}
|
||||
|
||||
httpRequest = std::make_shared<HttpRequest>(uri, method, httpVersion, headers);
|
||||
return std::make_tuple(true, "", httpRequest);
|
||||
}
|
||||
|
||||
bool Http::sendResponse(HttpResponsePtr response, std::shared_ptr<Socket> socket)
|
||||
{
|
||||
// Write the response to the socket
|
||||
std::stringstream ss;
|
||||
ss << "HTTP/1.1 ";
|
||||
ss << response->statusCode;
|
||||
ss << " ";
|
||||
ss << response->description;
|
||||
ss << "\r\n";
|
||||
|
||||
if (!socket->writeBytes(ss.str(), nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write headers
|
||||
ss.str("");
|
||||
ss << "Content-Length: " << response->payload.size() << "\r\n";
|
||||
for (auto&& it : response->headers)
|
||||
{
|
||||
ss << it.first << ": " << it.second << "\r\n";
|
||||
}
|
||||
ss << "\r\n";
|
||||
|
||||
if (!socket->writeBytes(ss.str(), nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return response->payload.empty()
|
||||
? true
|
||||
: socket->writeBytes(response->payload, nullptr);
|
||||
}
|
||||
}
|
120
ixwebsocket/IXHttp.h
Normal file
120
ixwebsocket/IXHttp.h
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* IXHttp.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include "IXProgressCallback.h"
|
||||
#include <tuple>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
enum class HttpErrorCode : int
|
||||
{
|
||||
Ok = 0,
|
||||
CannotConnect = 1,
|
||||
Timeout = 2,
|
||||
Gzip = 3,
|
||||
UrlMalformed = 4,
|
||||
CannotCreateSocket = 5,
|
||||
SendError = 6,
|
||||
ReadError = 7,
|
||||
CannotReadStatusLine = 8,
|
||||
MissingStatus = 9,
|
||||
HeaderParsingError = 10,
|
||||
MissingLocation = 11,
|
||||
TooManyRedirects = 12,
|
||||
ChunkReadError = 13,
|
||||
CannotReadBody = 14,
|
||||
Invalid = 100
|
||||
};
|
||||
|
||||
struct HttpResponse
|
||||
{
|
||||
int statusCode;
|
||||
std::string description;
|
||||
HttpErrorCode errorCode;
|
||||
WebSocketHttpHeaders headers;
|
||||
std::string payload;
|
||||
std::string errorMsg;
|
||||
uint64_t uploadSize;
|
||||
uint64_t downloadSize;
|
||||
|
||||
HttpResponse(int s = 0,
|
||||
const std::string& des = std::string(),
|
||||
const HttpErrorCode& c = HttpErrorCode::Ok,
|
||||
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
|
||||
const std::string& p = std::string(),
|
||||
const std::string& e = std::string(),
|
||||
uint64_t u = 0,
|
||||
uint64_t d = 0)
|
||||
: statusCode(s)
|
||||
, description(des)
|
||||
, errorCode(c)
|
||||
, headers(h)
|
||||
, payload(p)
|
||||
, errorMsg(e)
|
||||
, uploadSize(u)
|
||||
, downloadSize(d)
|
||||
{
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
||||
using HttpParameters = std::map<std::string, std::string>;
|
||||
using Logger = std::function<void(const std::string&)>;
|
||||
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
|
||||
|
||||
struct HttpRequestArgs
|
||||
{
|
||||
std::string url;
|
||||
std::string verb;
|
||||
WebSocketHttpHeaders extraHeaders;
|
||||
std::string body;
|
||||
int connectTimeout;
|
||||
int transferTimeout;
|
||||
bool followRedirects;
|
||||
int maxRedirects;
|
||||
bool verbose;
|
||||
bool compress;
|
||||
Logger logger;
|
||||
OnProgressCallback onProgressCallback;
|
||||
};
|
||||
|
||||
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
|
||||
|
||||
struct HttpRequest
|
||||
{
|
||||
std::string uri;
|
||||
std::string method;
|
||||
std::string version;
|
||||
WebSocketHttpHeaders headers;
|
||||
|
||||
HttpRequest(const std::string& u,
|
||||
const std::string& m,
|
||||
const std::string& v,
|
||||
const WebSocketHttpHeaders& h = WebSocketHttpHeaders())
|
||||
: uri(u)
|
||||
, method(m)
|
||||
, version(v)
|
||||
, headers(h)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
|
||||
|
||||
class Http
|
||||
{
|
||||
public:
|
||||
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(std::shared_ptr<Socket> socket);
|
||||
static bool sendResponse(HttpResponsePtr response, std::shared_ptr<Socket> socket);
|
||||
|
||||
static std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
|
||||
static std::string trim(const std::string& str);
|
||||
};
|
||||
}
|
@ -118,6 +118,7 @@ namespace ix
|
||||
int code = 0;
|
||||
WebSocketHttpHeaders headers;
|
||||
std::string payload;
|
||||
std::string description;
|
||||
|
||||
std::string protocol, host, path, query;
|
||||
int port;
|
||||
@ -126,9 +127,9 @@ namespace ix
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Cannot parse url: " << url;
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::UrlMalformed,
|
||||
headers, payload, ss.str(),
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::UrlMalformed,
|
||||
headers, payload, ss.str(),
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
|
||||
bool tls = protocol == "https";
|
||||
@ -137,9 +138,9 @@ namespace ix
|
||||
|
||||
if (!_socket)
|
||||
{
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotCreateSocket,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotCreateSocket,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
|
||||
// Build request string
|
||||
@ -200,7 +201,7 @@ namespace ix
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotConnect,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotConnect,
|
||||
headers, payload, ss.str(),
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -226,7 +227,7 @@ namespace ix
|
||||
if (!_socket->writeBytes(req, isCancellationRequested))
|
||||
{
|
||||
std::string errorMsg("Cannot send request");
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::SendError,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::SendError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -240,7 +241,7 @@ namespace ix
|
||||
if (!lineValid)
|
||||
{
|
||||
std::string errorMsg("Cannot retrieve status line");
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotReadStatusLine,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotReadStatusLine,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -255,7 +256,7 @@ namespace ix
|
||||
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
|
||||
{
|
||||
std::string errorMsg("Cannot parse response code from status line");
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::MissingStatus,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::MissingStatus,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -267,7 +268,7 @@ namespace ix
|
||||
if (!headersValid)
|
||||
{
|
||||
std::string errorMsg("Cannot parse http headers");
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::HeaderParsingError,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::HeaderParsingError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -278,7 +279,7 @@ namespace ix
|
||||
if (headers.find("Location") == headers.end())
|
||||
{
|
||||
std::string errorMsg("Missing location header for redirect");
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::MissingLocation,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::MissingLocation,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -287,7 +288,7 @@ namespace ix
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Too many redirects: " << redirects;
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::TooManyRedirects,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::TooManyRedirects,
|
||||
headers, payload, ss.str(),
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -299,7 +300,7 @@ namespace ix
|
||||
|
||||
if (verb == "HEAD")
|
||||
{
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Ok,
|
||||
headers, payload, std::string(),
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -320,7 +321,7 @@ namespace ix
|
||||
if (!chunkResult.first)
|
||||
{
|
||||
errorMsg = "Cannot read chunk";
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -338,7 +339,7 @@ namespace ix
|
||||
|
||||
if (!lineResult.first)
|
||||
{
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -365,7 +366,7 @@ namespace ix
|
||||
if (!chunkResult.first)
|
||||
{
|
||||
errorMsg = "Cannot read chunk";
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -376,7 +377,7 @@ namespace ix
|
||||
|
||||
if (!lineResult.first)
|
||||
{
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -391,7 +392,7 @@ namespace ix
|
||||
else
|
||||
{
|
||||
std::string errorMsg("Cannot read http body");
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotReadBody,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotReadBody,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
@ -405,14 +406,14 @@ namespace ix
|
||||
if (!gzipInflate(payload, decompressedPayload))
|
||||
{
|
||||
std::string errorMsg("Error decompressing payload");
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::Gzip,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Gzip,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
payload = decompressedPayload;
|
||||
}
|
||||
|
||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok,
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Ok,
|
||||
headers, payload, std::string(),
|
||||
uploadSize, downloadSize);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "IXSocket.h"
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include "IXHttp.h"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
@ -20,78 +21,6 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
enum class HttpErrorCode : int
|
||||
{
|
||||
Ok = 0,
|
||||
CannotConnect = 1,
|
||||
Timeout = 2,
|
||||
Gzip = 3,
|
||||
UrlMalformed = 4,
|
||||
CannotCreateSocket = 5,
|
||||
SendError = 6,
|
||||
ReadError = 7,
|
||||
CannotReadStatusLine = 8,
|
||||
MissingStatus = 9,
|
||||
HeaderParsingError = 10,
|
||||
MissingLocation = 11,
|
||||
TooManyRedirects = 12,
|
||||
ChunkReadError = 13,
|
||||
CannotReadBody = 14,
|
||||
Invalid = 100
|
||||
};
|
||||
|
||||
struct HttpResponse
|
||||
{
|
||||
int statusCode;
|
||||
HttpErrorCode errorCode;
|
||||
WebSocketHttpHeaders headers;
|
||||
std::string payload;
|
||||
std::string errorMsg;
|
||||
uint64_t uploadSize;
|
||||
uint64_t downloadSize;
|
||||
|
||||
HttpResponse(int s = 0,
|
||||
const HttpErrorCode& c = HttpErrorCode::Ok,
|
||||
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
|
||||
const std::string& p = std::string(),
|
||||
const std::string& e = std::string(),
|
||||
uint64_t u = 0,
|
||||
uint64_t d = 0)
|
||||
: statusCode(s)
|
||||
, errorCode(c)
|
||||
, headers(h)
|
||||
, payload(p)
|
||||
, errorMsg(e)
|
||||
, uploadSize(u)
|
||||
, downloadSize(d)
|
||||
{
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
||||
using HttpParameters = std::map<std::string, std::string>;
|
||||
using Logger = std::function<void(const std::string&)>;
|
||||
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
|
||||
|
||||
struct HttpRequestArgs
|
||||
{
|
||||
std::string url;
|
||||
std::string verb;
|
||||
WebSocketHttpHeaders extraHeaders;
|
||||
std::string body;
|
||||
int connectTimeout;
|
||||
int transferTimeout;
|
||||
bool followRedirects;
|
||||
int maxRedirects;
|
||||
bool verbose;
|
||||
bool compress;
|
||||
Logger logger;
|
||||
OnProgressCallback onProgressCallback;
|
||||
};
|
||||
|
||||
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
|
||||
|
||||
class HttpClient
|
||||
{
|
||||
public:
|
||||
|
158
ixwebsocket/IXHttpServer.cpp
Normal file
158
ixwebsocket/IXHttpServer.cpp
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* IXHttpServer.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXHttpServer.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXSocketFactory.h"
|
||||
#include "IXNetSystem.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
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);
|
||||
file.read((char*)&memblock.front(), static_cast<std::streamsize>(size));
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
namespace ix
|
||||
{
|
||||
HttpServer::HttpServer(int port,
|
||||
const std::string& host,
|
||||
int backlog,
|
||||
size_t maxConnections) : SocketServer(port, host, backlog, maxConnections),
|
||||
_connectedClientsCount(0)
|
||||
{
|
||||
setDefaultConnectionCallback();
|
||||
}
|
||||
|
||||
HttpServer::~HttpServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void HttpServer::stop()
|
||||
{
|
||||
stopAcceptingConnections();
|
||||
|
||||
// FIXME: cancelling / closing active clients ...
|
||||
|
||||
SocketServer::stop();
|
||||
}
|
||||
|
||||
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
|
||||
{
|
||||
_onConnectionCallback = callback;
|
||||
}
|
||||
|
||||
void HttpServer::handleConnection(
|
||||
int fd,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
_connectedClientsCount++;
|
||||
|
||||
std::string errorMsg;
|
||||
auto socket = createSocket(fd, errorMsg);
|
||||
|
||||
// Set the socket to non blocking mode + other tweaks
|
||||
SocketConnect::configure(fd);
|
||||
|
||||
auto ret = Http::parseRequest(socket);
|
||||
// FIXME: handle errors in parseRequest
|
||||
|
||||
if (std::get<0>(ret))
|
||||
{
|
||||
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
|
||||
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,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
|
||||
{
|
||||
std::string uri(request->uri);
|
||||
if (uri.empty() || uri == "/")
|
||||
{
|
||||
uri = "/index.html";
|
||||
}
|
||||
|
||||
std::string path("." + uri);
|
||||
auto res = readAsString(path);
|
||||
bool found = res.first;
|
||||
if (!found)
|
||||
{
|
||||
return std::make_shared<HttpResponse>(404, "Not Found",
|
||||
HttpErrorCode::Ok,
|
||||
WebSocketHttpHeaders(),
|
||||
std::string());
|
||||
}
|
||||
|
||||
std::string content = res.second;
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << request->method
|
||||
<< " "
|
||||
<< request->uri
|
||||
<< " "
|
||||
<< content.size();
|
||||
logInfo(ss.str());
|
||||
|
||||
WebSocketHttpHeaders headers;
|
||||
// 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;
|
||||
}
|
||||
|
||||
return std::make_shared<HttpResponse>(200, "OK",
|
||||
HttpErrorCode::Ok,
|
||||
headers,
|
||||
content);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
50
ixwebsocket/IXHttpServer.h
Normal file
50
ixwebsocket/IXHttpServer.h
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* IXHttpServer.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXSocketServer.h"
|
||||
#include "IXWebSocket.h"
|
||||
#include "IXHttp.h"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility> // pair
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class HttpServer final : public SocketServer
|
||||
{
|
||||
public:
|
||||
using OnConnectionCallback =
|
||||
std::function<HttpResponsePtr(HttpRequestPtr, std::shared_ptr<ConnectionState>)>;
|
||||
|
||||
HttpServer(int port = SocketServer::kDefaultPort,
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections);
|
||||
virtual ~HttpServer();
|
||||
virtual void stop() final;
|
||||
|
||||
void setOnConnectionCallback(const OnConnectionCallback& callback);
|
||||
|
||||
private:
|
||||
// Member variables
|
||||
OnConnectionCallback _onConnectionCallback;
|
||||
std::atomic<int> _connectedClientsCount;
|
||||
|
||||
// Methods
|
||||
virtual void handleConnection(int fd,
|
||||
std::shared_ptr<ConnectionState> connectionState) final;
|
||||
virtual size_t getConnectedClientsCount() final;
|
||||
|
||||
void setDefaultConnectionCallback();
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -232,19 +232,28 @@ namespace ix
|
||||
bool Socket::writeBytes(const std::string& str,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
char* buffer = const_cast<char*>(str.c_str());
|
||||
int len = (int) str.size();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (isCancellationRequested && isCancellationRequested()) return false;
|
||||
|
||||
char* buffer = const_cast<char*>(str.c_str());
|
||||
int len = (int) str.size();
|
||||
|
||||
ssize_t ret = send(buffer, len);
|
||||
|
||||
// We wrote some bytes, as needed, all good.
|
||||
if (ret > 0)
|
||||
{
|
||||
return ret == len;
|
||||
if (ret == len)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer += ret;
|
||||
len -= ret;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// There is possibly something to be writen, try again
|
||||
else if (ret < 0 && Socket::isWaitNeeded())
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <future>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "IXWebSocketHandshake.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXUrlParser.h"
|
||||
#include "IXHttp.h"
|
||||
|
||||
#include "libwshandshake.hpp"
|
||||
|
||||
@ -31,15 +32,6 @@ namespace ix
|
||||
|
||||
}
|
||||
|
||||
std::string WebSocketHandshake::trim(const std::string& str)
|
||||
{
|
||||
std::string out(str);
|
||||
out.erase(std::remove(out.begin(), out.end(), ' '), out.end());
|
||||
out.erase(std::remove(out.begin(), out.end(), '\r'), out.end());
|
||||
out.erase(std::remove(out.begin(), out.end(), '\n'), out.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
|
||||
{
|
||||
return std::equal(a.begin(), a.end(),
|
||||
@ -50,40 +42,6 @@ namespace ix
|
||||
});
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string, std::string> WebSocketHandshake::parseRequestLine(const std::string& line)
|
||||
{
|
||||
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||
std::string token;
|
||||
std::stringstream tokenStream(line);
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
// Split by ' '
|
||||
while (std::getline(tokenStream, token, ' '))
|
||||
{
|
||||
tokens.push_back(token);
|
||||
}
|
||||
|
||||
std::string method;
|
||||
if (tokens.size() >= 1)
|
||||
{
|
||||
method = trim(tokens[0]);
|
||||
}
|
||||
|
||||
std::string requestUri;
|
||||
if (tokens.size() >= 2)
|
||||
{
|
||||
requestUri = trim(tokens[1]);
|
||||
}
|
||||
|
||||
std::string httpVersion;
|
||||
if (tokens.size() >= 3)
|
||||
{
|
||||
httpVersion = trim(tokens[2]);
|
||||
}
|
||||
|
||||
return std::make_tuple(method, requestUri, httpVersion);
|
||||
}
|
||||
|
||||
std::string WebSocketHandshake::genRandomString(const int len)
|
||||
{
|
||||
std::string alphanum =
|
||||
@ -294,7 +252,7 @@ namespace ix
|
||||
}
|
||||
|
||||
// Validate request line (GET /foo HTTP/1.1\r\n)
|
||||
auto requestLine = parseRequestLine(line);
|
||||
auto requestLine = Http::parseRequestLine(line);
|
||||
auto method = std::get<0>(requestLine);
|
||||
auto uri = std::get<1>(requestLine);
|
||||
auto httpVersion = std::get<2>(requestLine);
|
||||
|
@ -64,8 +64,6 @@ namespace ix
|
||||
// Parse HTTP headers
|
||||
WebSocketInitResult sendErrorResponse(int code, const std::string& reason);
|
||||
|
||||
std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
|
||||
std::string trim(const std::string& str);
|
||||
bool insensitiveStringCompare(const std::string& a, const std::string& b);
|
||||
|
||||
std::atomic<bool>& _requestInitCancellation;
|
||||
|
@ -19,12 +19,12 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
using OnConnectionCallback =
|
||||
std::function<void(std::shared_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>;
|
||||
|
||||
class WebSocketServer final : public SocketServer
|
||||
{
|
||||
public:
|
||||
using OnConnectionCallback =
|
||||
std::function<void(std::shared_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>;
|
||||
|
||||
WebSocketServer(int port = SocketServer::kDefaultPort,
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||
|
Reference in New Issue
Block a user