You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
8.0 KiB
178 lines
8.0 KiB
/**
|
|
* Digital Voice Modem - Host Software
|
|
* GPLv2 Open Source. Use is subject to license terms.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* @package DVM / Host Software
|
|
*
|
|
*/
|
|
//
|
|
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
|
|
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
|
|
//
|
|
/*
|
|
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
|
|
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person or organization
|
|
* obtaining a copy of the software and accompanying documentation covered by
|
|
* this license (the “Software”) to use, reproduce, display, distribute, execute,
|
|
* and transmit the Software, and to prepare derivative works of the Software, and
|
|
* to permit third-parties to whom the Software is furnished to do so, all subject
|
|
* to the following:
|
|
*
|
|
* The copyright notices in the Software and this entire statement, including the
|
|
* above license grant, this restriction and the following disclaimer, must be included
|
|
* in all copies of the Software, in whole or in part, and all derivative works of the
|
|
* Software, unless such copies or derivative works are solely in the form of
|
|
* machine-executable object code generated by a source language processor.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
* PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
|
|
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
#if !defined(__REST_HTTP__CONNECTION_H__)
|
|
#define __REST_HTTP__CONNECTION_H__
|
|
|
|
#include "Defines.h"
|
|
#include "network/rest/http/HTTPReply.h"
|
|
#include "network/rest/http/HTTPRequest.h"
|
|
#include "network/rest/http/HTTPRequestLexer.h"
|
|
|
|
#include <array>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <iterator>
|
|
#include <asio.hpp>
|
|
|
|
namespace network {
|
|
namespace rest {
|
|
namespace http {
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Class Prototypes
|
|
// ---------------------------------------------------------------------------
|
|
|
|
template<class> class ConnectionManager;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Class Declaration
|
|
// This class represents a single connection from a client.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
template <typename RequestHandlerType>
|
|
class Connection : public std::enable_shared_from_this<Connection<RequestHandlerType>>
|
|
{
|
|
typedef Connection<RequestHandlerType> selfType;
|
|
typedef std::shared_ptr<selfType> selfTypePtr;
|
|
typedef ConnectionManager<selfTypePtr> ConnectionManagerType;
|
|
public:
|
|
/// <summary>Initializes a new instance of the Connection class.</summary>
|
|
explicit Connection(asio::ip::tcp::socket socket, ConnectionManagerType& manager, RequestHandlerType& handler,
|
|
bool persistent = false) :
|
|
m_socket(std::move(socket)),
|
|
m_connectionManager(manager),
|
|
m_requestHandler(handler),
|
|
m_persistent(persistent)
|
|
{
|
|
/* stub */
|
|
}
|
|
/// <summary>Initializes a copy instance of the Connection class.</summary>
|
|
Connection(const Connection&) = delete;
|
|
|
|
/// <summary></summary>
|
|
Connection& operator=(const Connection&) = delete;
|
|
|
|
/// <summary>Start the first asynchronous operation for the connection.</summary>
|
|
void start() { read(); }
|
|
/// <summary>Stop all asynchronous operations associated with the connection.</summary>
|
|
void stop() { m_socket.close(); }
|
|
private:
|
|
/// <summary>Perform an asynchronous read operation.</summary>
|
|
void read()
|
|
{
|
|
if (!m_persistent) {
|
|
auto self(this->shared_from_this());
|
|
}
|
|
|
|
m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) {
|
|
if (!ec) {
|
|
HTTPRequestLexer::ResultType result;
|
|
char* data;
|
|
|
|
std::tie(result, data) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred);
|
|
auto header = std::find_if(m_request.headers.begin(), m_request.headers.end(), [](const HTTPHeader& h) { return h.name == "content-length"; });
|
|
if (header != m_request.headers.end()) {
|
|
size_t length = (size_t)::strtoul(header->value.c_str(), NULL, 10);
|
|
m_request.data = std::string(data, length);
|
|
}
|
|
|
|
if (result == HTTPRequestLexer::GOOD) {
|
|
m_requestHandler.handleRequest(m_request, m_reply);
|
|
write();
|
|
}
|
|
else if (result == HTTPRequestLexer::BAD) {
|
|
m_reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST);
|
|
write();
|
|
}
|
|
else {
|
|
read();
|
|
}
|
|
}
|
|
else if (ec != asio::error::operation_aborted) {
|
|
m_connectionManager.stop(this->shared_from_this());
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>Perform an asynchronous write operation.</summary>
|
|
void write()
|
|
{
|
|
if (!m_persistent) {
|
|
auto self(this->shared_from_this());
|
|
} else {
|
|
m_reply.headers.emplace_back("Connection:", "keep-alive");
|
|
}
|
|
|
|
asio::async_write(m_socket, m_reply.toBuffers(), [=](asio::error_code ec, std::size_t) {
|
|
if (m_persistent) {
|
|
m_lexer.reset();
|
|
m_reply.headers.resize(0);
|
|
m_reply.status = HTTPReply::OK;
|
|
m_reply.content = "";
|
|
m_request = HTTPRequest();
|
|
read();
|
|
}
|
|
else
|
|
{
|
|
if (!ec) {
|
|
// initiate graceful connection closure
|
|
asio::error_code ignored_ec;
|
|
m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
|
|
}
|
|
|
|
if (ec != asio::error::operation_aborted) {
|
|
m_connectionManager.stop(this->shared_from_this());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
asio::ip::tcp::socket m_socket;
|
|
ConnectionManagerType& m_connectionManager;
|
|
RequestHandlerType& m_requestHandler;
|
|
std::array<char, 8192> m_buffer;
|
|
HTTPRequest m_request;
|
|
HTTPRequestLexer m_lexer;
|
|
HTTPReply m_reply;
|
|
bool m_persistent;
|
|
};
|
|
} // namespace http
|
|
} // namespace rest
|
|
} // namespace network
|
|
|
|
#endif // __REST_HTTP__CONNECTION_H__
|