can send a response, cannot process body yet

This commit is contained in:
Benjamin Sergeant 2019-06-20 16:20:04 -07:00
parent 44f37a4140
commit 74cc6b815a
12 changed files with 146 additions and 141 deletions

View File

@ -25,7 +25,9 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXCancellationRequest.cpp ixwebsocket/IXCancellationRequest.cpp
ixwebsocket/IXConnectionState.cpp ixwebsocket/IXConnectionState.cpp
ixwebsocket/IXDNSLookup.cpp ixwebsocket/IXDNSLookup.cpp
ixwebsocket/IXHttp.cpp
ixwebsocket/IXHttpClient.cpp ixwebsocket/IXHttpClient.cpp
ixwebsocket/IXHttpServer.cpp
ixwebsocket/IXNetSystem.cpp ixwebsocket/IXNetSystem.cpp
ixwebsocket/IXSelectInterrupt.cpp ixwebsocket/IXSelectInterrupt.cpp
ixwebsocket/IXSelectInterruptFactory.cpp ixwebsocket/IXSelectInterruptFactory.cpp
@ -51,7 +53,9 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXCancellationRequest.h ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXConnectionState.h ixwebsocket/IXConnectionState.h
ixwebsocket/IXDNSLookup.h ixwebsocket/IXDNSLookup.h
ixwebsocket/IXHttp.h
ixwebsocket/IXHttpClient.h ixwebsocket/IXHttpClient.h
ixwebsocket/IXHttpServer.h
ixwebsocket/IXNetSystem.h ixwebsocket/IXNetSystem.h
ixwebsocket/IXProgressCallback.h ixwebsocket/IXProgressCallback.h
ixwebsocket/IXSelectInterrupt.h ixwebsocket/IXSelectInterrupt.h

View File

@ -5,6 +5,8 @@
*/ */
#include "IXHttp.h" #include "IXHttp.h"
#include "IXCancellationRequest.h"
#include "IXSocket.h"
#include <sstream> #include <sstream>
#include <vector> #include <vector>
@ -53,4 +55,74 @@ namespace ix
return std::make_tuple(method, requestUri, httpVersion); 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);
}
// FIXME: Write a mime type
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";
ss << "\r\n";
if (!socket->writeBytes(ss.str(), nullptr))
{
return false;
}
return socket->writeBytes(response->payload, nullptr);
}
} }

View File

@ -35,6 +35,7 @@ namespace ix
struct HttpResponse struct HttpResponse
{ {
int statusCode; int statusCode;
std::string description;
HttpErrorCode errorCode; HttpErrorCode errorCode;
WebSocketHttpHeaders headers; WebSocketHttpHeaders headers;
std::string payload; std::string payload;
@ -43,6 +44,7 @@ namespace ix
uint64_t downloadSize; uint64_t downloadSize;
HttpResponse(int s = 0, HttpResponse(int s = 0,
const std::string& des = std::string(),
const HttpErrorCode& c = HttpErrorCode::Ok, const HttpErrorCode& c = HttpErrorCode::Ok,
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(), const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
const std::string& p = std::string(), const std::string& p = std::string(),
@ -50,6 +52,7 @@ namespace ix
uint64_t u = 0, uint64_t u = 0,
uint64_t d = 0) uint64_t d = 0)
: statusCode(s) : statusCode(s)
, description(des)
, errorCode(c) , errorCode(c)
, headers(h) , headers(h)
, payload(p) , payload(p)
@ -84,9 +87,33 @@ namespace ix
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>; 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 class Http
{ {
public: 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::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
static std::string trim(const std::string& str); static std::string trim(const std::string& str);
}; };

View File

@ -118,6 +118,7 @@ namespace ix
int code = 0; int code = 0;
WebSocketHttpHeaders headers; WebSocketHttpHeaders headers;
std::string payload; std::string payload;
std::string description;
std::string protocol, host, path, query; std::string protocol, host, path, query;
int port; int port;
@ -126,9 +127,9 @@ namespace ix
{ {
std::stringstream ss; std::stringstream ss;
ss << "Cannot parse url: " << url; ss << "Cannot parse url: " << url;
return std::make_shared<HttpResponse>(code, HttpErrorCode::UrlMalformed, return std::make_shared<HttpResponse>(code, description, HttpErrorCode::UrlMalformed,
headers, payload, ss.str(), headers, payload, ss.str(),
uploadSize, downloadSize); uploadSize, downloadSize);
} }
bool tls = protocol == "https"; bool tls = protocol == "https";
@ -137,9 +138,9 @@ namespace ix
if (!_socket) if (!_socket)
{ {
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotCreateSocket, return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotCreateSocket,
headers, payload, errorMsg, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
// Build request string // Build request string
@ -200,7 +201,7 @@ namespace ix
{ {
std::stringstream ss; std::stringstream ss;
ss << "Cannot connect to url: " << url << " / error : " << errMsg; 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(), headers, payload, ss.str(),
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -226,7 +227,7 @@ namespace ix
if (!_socket->writeBytes(req, isCancellationRequested)) if (!_socket->writeBytes(req, isCancellationRequested))
{ {
std::string errorMsg("Cannot send request"); 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, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -240,7 +241,7 @@ namespace ix
if (!lineValid) if (!lineValid)
{ {
std::string errorMsg("Cannot retrieve status line"); 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, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -255,7 +256,7 @@ namespace ix
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1) if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
{ {
std::string errorMsg("Cannot parse response code from status line"); 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, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -267,7 +268,7 @@ namespace ix
if (!headersValid) if (!headersValid)
{ {
std::string errorMsg("Cannot parse http headers"); 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, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -278,7 +279,7 @@ namespace ix
if (headers.find("Location") == headers.end()) if (headers.find("Location") == headers.end())
{ {
std::string errorMsg("Missing location header for redirect"); 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, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -287,7 +288,7 @@ namespace ix
{ {
std::stringstream ss; std::stringstream ss;
ss << "Too many redirects: " << redirects; 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(), headers, payload, ss.str(),
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -299,7 +300,7 @@ namespace ix
if (verb == "HEAD") if (verb == "HEAD")
{ {
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok, return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Ok,
headers, payload, std::string(), headers, payload, std::string(),
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -320,7 +321,7 @@ namespace ix
if (!chunkResult.first) if (!chunkResult.first)
{ {
errorMsg = "Cannot read chunk"; errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError, return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -338,7 +339,7 @@ namespace ix
if (!lineResult.first) if (!lineResult.first)
{ {
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError, return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -365,7 +366,7 @@ namespace ix
if (!chunkResult.first) if (!chunkResult.first)
{ {
errorMsg = "Cannot read chunk"; errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError, return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -376,7 +377,7 @@ namespace ix
if (!lineResult.first) if (!lineResult.first)
{ {
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError, return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -391,7 +392,7 @@ namespace ix
else else
{ {
std::string errorMsg("Cannot read http body"); 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, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
@ -405,14 +406,14 @@ namespace ix
if (!gzipInflate(payload, decompressedPayload)) if (!gzipInflate(payload, decompressedPayload))
{ {
std::string errorMsg("Error decompressing payload"); 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, headers, payload, errorMsg,
uploadSize, downloadSize); uploadSize, downloadSize);
} }
payload = decompressedPayload; payload = decompressedPayload;
} }
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok, return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Ok,
headers, payload, std::string(), headers, payload, std::string(),
uploadSize, downloadSize); uploadSize, downloadSize);
} }

View File

@ -8,6 +8,7 @@
#include "IXSocket.h" #include "IXSocket.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include "IXHttp.h"
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
#include <condition_variable> #include <condition_variable>
@ -20,78 +21,6 @@
namespace ix 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 class HttpClient
{ {
public: public:

View File

@ -56,9 +56,13 @@ namespace ix
std::string errorMsg; std::string errorMsg;
auto socket = createSocket(fd, errorMsg); auto socket = createSocket(fd, errorMsg);
// Set the socket to non blocking mode + other tweaks
SocketConnect::configure(fd);
std::cout << "I was here" << std::endl; auto ret = Http::parseRequest(socket);
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
Http::sendResponse(response, socket);
#if 0 #if 0
// Parse request // Parse request
auto httpRequest = std::make_shared<HttpRequestArgs>(); auto httpRequest = std::make_shared<HttpRequestArgs>();

View File

@ -8,6 +8,7 @@
#include "IXSocketServer.h" #include "IXSocketServer.h"
#include "IXWebSocket.h" #include "IXWebSocket.h"
#include "IXHttp.h"
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -19,7 +20,7 @@
namespace ix namespace ix
{ {
using OnConnectionCallback = using OnConnectionCallback =
std::function<void(std::shared_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>; std::function<HttpResponsePtr(HttpRequestPtr, std::shared_ptr<ConnectionState>)>;
class HttpServer final : public SocketServer class HttpServer final : public SocketServer
{ {

View File

@ -7,6 +7,7 @@
#include "IXWebSocketHandshake.h" #include "IXWebSocketHandshake.h"
#include "IXSocketConnect.h" #include "IXSocketConnect.h"
#include "IXUrlParser.h" #include "IXUrlParser.h"
#include "IXHttp.h"
#include "libwshandshake.hpp" #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) bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
{ {
return std::equal(a.begin(), a.end(), 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 WebSocketHandshake::genRandomString(const int len)
{ {
std::string alphanum = std::string alphanum =
@ -294,7 +252,7 @@ namespace ix
} }
// Validate request line (GET /foo HTTP/1.1\r\n) // 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 method = std::get<0>(requestLine);
auto uri = std::get<1>(requestLine); auto uri = std::get<1>(requestLine);
auto httpVersion = std::get<2>(requestLine); auto httpVersion = std::get<2>(requestLine);

View File

@ -64,8 +64,6 @@ namespace ix
// Parse HTTP headers // Parse HTTP headers
WebSocketInitResult sendErrorResponse(int code, const std::string& reason); 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); bool insensitiveStringCompare(const std::string& a, const std::string& b);
std::atomic<bool>& _requestInitCancellation; std::atomic<bool>& _requestInitCancellation;

View File

@ -68,6 +68,7 @@ add_executable(ws
ws_cobra_to_statsd.cpp ws_cobra_to_statsd.cpp
ws_cobra_to_sentry.cpp ws_cobra_to_sentry.cpp
ws_snake.cpp ws_snake.cpp
ws_httpd.cpp
ws.cpp) ws.cpp)
target_link_libraries(ws ixwebsocket) target_link_libraries(ws ixwebsocket)

View File

@ -216,6 +216,10 @@ int main(int argc, char** argv)
->check(CLI::ExistingPath); ->check(CLI::ExistingPath);
runApp->add_flag("-v", verbose, "Verbose"); runApp->add_flag("-v", verbose, "Verbose");
CLI::App* httpServerApp = app.add_subcommand("httpd", "HTTP server");
httpServerApp->add_option("--port", port, "Port");
httpServerApp->add_option("--host", hostname, "Hostname");
CLI11_PARSE(app, argc, argv); CLI11_PARSE(app, argc, argv);
// pid file handling // pid file handling
@ -313,6 +317,10 @@ int main(int argc, char** argv)
redisPassword, verbose, redisPassword, verbose,
appsConfigPath); appsConfigPath);
} }
else if (app.got_subcommand("httpd"))
{
ret = ix::ws_httpd_main(port, hostname);
}
ix::uninitNetSystem(); ix::uninitNetSystem();
return ret; return ret;

View File

@ -93,4 +93,6 @@ namespace ix
const std::string& redisPassword, const std::string& redisPassword,
bool verbose, bool verbose,
const std::string& appsConfigPath); const std::string& appsConfigPath);
int ws_httpd_main(int port, const std::string& hostname);
} // namespace ix } // namespace ix