add simple test + set default http handler

This commit is contained in:
Benjamin Sergeant 2019-06-23 13:21:27 -07:00
parent 22b8fc831c
commit cfc1b8f1c2
14 changed files with 342 additions and 113 deletions

View File

@ -97,7 +97,6 @@ namespace ix
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
@ -127,6 +126,8 @@ namespace ix
return false;
}
return socket->writeBytes(response->payload, nullptr);
return response->payload.empty()
? true
: socket->writeBytes(response->payload, nullptr);
}
}

View File

@ -11,22 +11,45 @@
#include <iostream>
#include <sstream>
#include <future>
#include <string.h>
#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
{
const int HttpServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
HttpServer::HttpServer(int port,
const std::string& host,
int backlog,
size_t maxConnections,
int handshakeTimeoutSecs) : SocketServer(port, host, backlog, maxConnections),
_handshakeTimeoutSecs(handshakeTimeoutSecs),
size_t maxConnections) : SocketServer(port, host, backlog, maxConnections),
_connectedClientsCount(0)
{
setDefaultConnectionCallback();
}
HttpServer::~HttpServer()
@ -56,16 +79,21 @@ namespace ix
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
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
Http::sendResponse(response, socket);
logInfo("HttpServer::handleConnection() done");
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--;
@ -75,4 +103,49 @@ namespace ix
{
return _connectedClientsCount;
}
void HttpServer::setDefaultConnectionCallback()
{
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
{
std::string path("." + request->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;
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,
WebSocketHttpHeaders(),
content);
}
);
}
}

View File

@ -19,17 +19,16 @@
namespace ix
{
using OnConnectionCallback =
std::function<HttpResponsePtr(HttpRequestPtr, std::shared_ptr<ConnectionState>)>;
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,
int handshakeTimeoutSecs = HttpServer::kDefaultHandShakeTimeoutSecs);
size_t maxConnections = SocketServer::kDefaultMaxConnections);
virtual ~HttpServer();
virtual void stop() final;
@ -37,15 +36,15 @@ namespace ix
private:
// Member variables
int _handshakeTimeoutSecs;
OnConnectionCallback _onConnectionCallback;
const static int kDefaultHandShakeTimeoutSecs;
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

View File

@ -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())

View File

@ -11,7 +11,6 @@
#include <iostream>
#include <sstream>
#include <future>
#include <string.h>
#include <assert.h>

View File

@ -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,

View File

@ -28,6 +28,7 @@ include_directories(
set (SOURCES
test_runner.cpp
IXTest.cpp
IXGetFreePort.cpp
../third_party/msgpack11/msgpack11.cpp
../ws/ixcore/utils/IXCoreLogger.cpp
@ -38,6 +39,8 @@ set (SOURCES
IXUrlParserTest.cpp
IXWebSocketServerTest.cpp
IXHttpClientTest.cpp
IXHttpServerTest.cpp
IXUnityBuildsTest.cpp
)
# Some unittest don't work on windows yet

93
test/IXGetFreePort.cpp Normal file
View File

@ -0,0 +1,93 @@
/*
* IXGetFreePort.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include "IXGetFreePort.h"
#include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXSocket.h>
#include <string>
#include <random>
namespace ix
{
int getAnyFreePortRandom()
{
std::random_device rd;
std::uniform_int_distribution<int> dist(1024 + 1, 65535);
return dist(rd);
}
int getAnyFreePort()
{
int defaultPort = 8090;
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
return getAnyFreePortRandom();
}
int enable = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
(char*) &enable, sizeof(enable)) < 0)
{
return getAnyFreePortRandom();
}
// Bind to port 0. This is the standard way to get a free port.
struct sockaddr_in server; // server address information
server.sin_family = AF_INET;
server.sin_port = htons(0);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
{
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
}
struct sockaddr_in sa; // server address information
socklen_t len = sizeof(sa);
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
{
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
}
int port = ntohs(sa.sin_port);
Socket::closeSocket(sockfd);
return port;
}
int getFreePort()
{
while (true)
{
#if defined(__has_feature)
# if __has_feature(address_sanitizer)
int port = getAnyFreePortRandom();
# else
int port = getAnyFreePort();
# endif
#else
int port = getAnyFreePort();
#endif
//
// Only port above 1024 can be used by non root users, but for some
// reason I got port 7 returned with macOS when binding on port 0...
//
if (port > 1024)
{
return port;
}
}
return -1;
}
} // namespace ix

12
test/IXGetFreePort.h Normal file
View File

@ -0,0 +1,12 @@
/*
* IXGetFreePort.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#pragma once
namespace ix
{
int getFreePort();
} // namespace ix

70
test/IXHttpServerTest.cpp Normal file
View File

@ -0,0 +1,70 @@
/*
* IXSocketTest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <ixwebsocket/IXHttpClient.h>
#include <ixwebsocket/IXHttpServer.h>
#include "IXGetFreePort.h"
#include "catch.hpp"
using namespace ix;
TEST_CASE("http server", "[httpd]")
{
SECTION("Connect to a local HTTP server")
{
int port = getFreePort();
ix::HttpServer server(port, "127.0.0.1");
auto res = server.listen();
REQUIRE(res.first);
server.start();
HttpClient httpClient;
WebSocketHttpHeaders headers;
std::string url("http://127.0.0.1:");
url += std::to_string(port);
url += "/data/foo.txt";
auto args = httpClient.createRequest(url);
args->extraHeaders = headers;
args->connectTimeout = 60;
args->transferTimeout = 60;
args->followRedirects = true;
args->maxRedirects = 10;
args->verbose = true;
args->compress = true;
args->logger = [](const std::string& msg)
{
std::cout << msg;
};
args->onProgressCallback = [](int current, int total) -> bool
{
std::cerr << "\r" << "Downloaded "
<< current << " bytes out of " << total;
return true;
};
auto response = httpClient.get(url, args);
for (auto it : response->headers)
{
std::cerr << it.first << ": " << it.second << std::endl;
}
std::cerr << "Upload size: " << response->uploadSize << std::endl;
std::cerr << "Download size: " << response->downloadSize << std::endl;
std::cerr << "Status: " << response->statusCode << std::endl;
std::cerr << "Error message: " << response->errorMsg << std::endl;
REQUIRE(response->errorCode == HttpErrorCode::Ok);
REQUIRE(response->statusCode == 200);
server.stop();
}
}

View File

@ -72,88 +72,6 @@ namespace ix
Logger() << msg;
}
int getAnyFreePortRandom()
{
std::random_device rd;
std::uniform_int_distribution<int> dist(1024 + 1, 65535);
return dist(rd);
}
int getAnyFreePort()
{
int defaultPort = 8090;
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
log("Cannot compute a free port. socket error.");
return getAnyFreePortRandom();
}
int enable = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
(char*) &enable, sizeof(enable)) < 0)
{
log("Cannot compute a free port. setsockopt error.");
return getAnyFreePortRandom();
}
// Bind to port 0. This is the standard way to get a free port.
struct sockaddr_in server; // server address information
server.sin_family = AF_INET;
server.sin_port = htons(0);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
{
log("Cannot compute a free port. bind error.");
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
}
struct sockaddr_in sa; // server address information
socklen_t len = sizeof(sa);
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
{
log("Cannot compute a free port. getsockname error.");
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
}
int port = ntohs(sa.sin_port);
Socket::closeSocket(sockfd);
return port;
}
int getFreePort()
{
while (true)
{
#if defined(__has_feature)
# if __has_feature(address_sanitizer)
int port = getAnyFreePortRandom();
# else
int port = getAnyFreePort();
# endif
#else
int port = getAnyFreePort();
#endif
//
// Only port above 1024 can be used by non root users, but for some
// reason I got port 7 returned with macOS when binding on port 0...
//
if (port > 1024)
{
return port;
}
}
return -1;
}
void hexDump(const std::string& prefix,
const std::string& s)
{

View File

@ -8,6 +8,7 @@
#include <iostream>
#include <ixwebsocket/IXWebSocketServer.h>
#include "IXGetFreePort.h"
#include <mutex>
#include <spdlog/spdlog.h>
#include <sstream>
@ -46,7 +47,5 @@ namespace ix
void log(const std::string& msg);
int getFreePort();
bool startWebSocketEchoServer(ix::WebSocketServer& server);
} // namespace ix

View File

@ -0,0 +1,52 @@
/*
* IXUnityBuildsTest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <ixwebsocket/IXCancellationRequest.h>
#include <ixwebsocket/IXConnectionState.h>
#include <ixwebsocket/IXDNSLookup.h>
#include <ixwebsocket/IXHttp.h>
#include <ixwebsocket/IXHttpClient.h>
#include <ixwebsocket/IXHttpServer.h>
#include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXProgressCallback.h>
#include <ixwebsocket/IXSelectInterrupt.h>
#include <ixwebsocket/IXSelectInterruptFactory.h>
#include <ixwebsocket/IXSetThreadName.h>
#include <ixwebsocket/IXSocket.h>
#include <ixwebsocket/IXSocketConnect.h>
#include <ixwebsocket/IXSocketFactory.h>
#include <ixwebsocket/IXSocketServer.h>
#include <ixwebsocket/IXUrlParser.h>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXWebSocketCloseConstants.h>
#include <ixwebsocket/IXWebSocketCloseInfo.h>
#include <ixwebsocket/IXWebSocketErrorInfo.h>
#include <ixwebsocket/IXWebSocketHandshake.h>
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
#include <ixwebsocket/IXWebSocketMessage.h>
#include <ixwebsocket/IXWebSocketMessageQueue.h>
#include <ixwebsocket/IXWebSocketMessageType.h>
#include <ixwebsocket/IXWebSocketOpenInfo.h>
#include <ixwebsocket/IXWebSocketPerMessageDeflate.h>
#include <ixwebsocket/IXWebSocketPerMessageDeflateCodec.h>
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
#include <ixwebsocket/IXWebSocketSendInfo.h>
#include <ixwebsocket/IXWebSocketServer.h>
#include <ixwebsocket/IXWebSocketTransport.h>
#include <ixwebsocket/LUrlParser.h>
#include <ixwebsocket/libwshandshake.hpp>
#include "catch.hpp"
using namespace ix;
TEST_CASE("unity build", "[unity_build]")
{
SECTION("dummy test")
{
REQUIRE(true);
}
}

1
test/data/foo.txt Normal file
View File

@ -0,0 +1 @@
Hello world