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
						
							b26e9d0338
						
					
				
				
					commit
					f84bc53c8d
				
			
							
								
								
									
										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);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user