├── localhost.pfx ├── wrong-host.pfx ├── include └── http │ ├── Version.hpp │ ├── net │ ├── OpenSslCert.hpp │ ├── TlsListenSocket.hpp │ ├── TlsSocket.hpp │ ├── Schannel.hpp │ ├── OpenSslListenSocket.hpp │ ├── SchannelListenSocket.hpp │ ├── TcpListenSocket.hpp │ ├── OpenSsl.hpp │ ├── Cert.hpp │ ├── Os.hpp │ ├── TcpSocket.hpp │ ├── Socket.hpp │ ├── OpenSslSocket.hpp │ ├── Net.hpp │ ├── SchannelSocket.hpp │ └── AsyncIo.hpp │ ├── Time.hpp │ ├── Method.hpp │ ├── Request.hpp │ ├── Response.hpp │ ├── client │ ├── SocketFactory.hpp │ ├── Client.hpp │ └── ClientConnection.hpp │ ├── util │ └── Thread.hpp │ ├── Status.hpp │ ├── server │ ├── CoreServer.hpp │ └── Router.hpp │ ├── Error.hpp │ ├── headers │ └── Accept.hpp │ ├── core │ ├── Writer.hpp │ ├── ParserUtils.hpp │ └── Parser.hpp │ ├── Headers.hpp │ └── Url.hpp ├── .gitignore ├── tests ├── Main.cpp ├── net │ ├── Cert.cpp │ ├── TlsSocket.cpp │ ├── TcpSocket.cpp │ └── TlsServer.cpp ├── TestSocketFactory.hpp ├── TestSocketFactory.cpp ├── Headers.cpp ├── client │ ├── ClientConnection.cpp │ ├── Client.cpp │ └── AsyncClient.cpp ├── Time.cpp ├── TestThread.hpp ├── TestSocket.hpp ├── server │ ├── Router.cpp │ └── CoreServer.cpp ├── Url.cpp ├── core │ └── ParserUtils.cpp └── headers │ └── Accept.cpp ├── third_party └── boost64.bat ├── source ├── net │ ├── Socket.cpp │ ├── OpenSslListenSocket.cpp │ ├── SchannelListenSocket.cpp │ ├── SocketUtils.hpp │ ├── TcpListenSocket.cpp │ ├── Net.cpp │ ├── OpenSsl.cpp │ └── TcpSocket.cpp ├── client │ ├── SocketFactory.cpp │ ├── Client.cpp │ └── ClientConnection.cpp ├── Headers.cpp ├── String.hpp ├── Method.cpp ├── Status.cpp ├── server │ └── Router.cpp ├── Time.cpp ├── headers │ └── Accept.cpp └── Url.cpp ├── BUILD.md ├── LICENSE ├── common.props ├── makefile ├── wrong-host.crt ├── localhost.crt ├── cpphttp.sln ├── cpphttp-ut.vcxproj.filters ├── localhost.key └── wrong-host.key /localhost.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wnewbery/cpphttp/HEAD/localhost.pfx -------------------------------------------------------------------------------- /wrong-host.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wnewbery/cpphttp/HEAD/wrong-host.pfx -------------------------------------------------------------------------------- /include/http/Version.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | namespace http 3 | { 4 | /**HTTP version number.*/ 5 | struct Version 6 | { 7 | int major; 8 | int minor; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /third_party/boost 2 | /Intermediate 3 | /Win32-Debug 4 | /Win32-Release 5 | /x64-Debug 6 | /x64-Release 7 | /cpphttp-ut.vcxproj.user 8 | /.vs 9 | /cpphttp.VC.db 10 | /cpphttp.VC.VC.opendb 11 | /cpphttp.vcxproj.user 12 | /bin 13 | /coverage 14 | /obj 15 | /api_docs 16 | /third_party/openssl 17 | /x64-DebugOpenSSL 18 | -------------------------------------------------------------------------------- /tests/Main.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE "C++ HTTP Unit Tests" 2 | #include 3 | #include "net/Net.hpp" 4 | 5 | struct Startup 6 | { 7 | Startup() 8 | { 9 | http::init_net(); 10 | } 11 | ~Startup() 12 | { 13 | 14 | } 15 | }; 16 | BOOST_GLOBAL_FIXTURE(Startup); 17 | -------------------------------------------------------------------------------- /include/http/net/OpenSslCert.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | namespace http 6 | { 7 | struct OpenSslPrivateCertData 8 | { 9 | EVP_PKEY *pkey; 10 | X509 *cert; 11 | STACK_OF(X509) *ca; 12 | 13 | ~OpenSslPrivateCertData(); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /include/http/net/TlsListenSocket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Os.hpp" 3 | #ifdef HTTP_USE_OPENSSL 4 | #include "OpenSslListenSocket.hpp" 5 | namespace http 6 | { 7 | typedef OpenSslListenSocket TlsListenSocket; 8 | } 9 | #else 10 | #include "SchannelListenSocket.hpp" 11 | namespace http 12 | { 13 | typedef SchannelListenSocket TlsListenSocket; 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /third_party/boost64.bat: -------------------------------------------------------------------------------- 1 | pushd boost 2 | 3 | pushd tools\build 4 | call bootstrap.bat 5 | b2 install --prefix=..\..\boost_build 6 | popd 7 | set PATH=%CD%\boost_build\bin;%PATH% 8 | 9 | b2 --build-dir=build64 toolset=msvc -j 8 variant=debug,release link=static threading=multi address-model=64 runtime-link=shared --with-test --build-type=complete --stagedir=stage-x64 stage 10 | popd 11 | -------------------------------------------------------------------------------- /tests/net/Cert.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "net/Cert.hpp" 3 | 4 | using namespace http; 5 | 6 | BOOST_AUTO_TEST_SUITE(TestCert) 7 | BOOST_AUTO_TEST_CASE(file_cert) 8 | { 9 | BOOST_CHECK(load_pfx_cert("localhost.pfx", "password")); 10 | BOOST_CHECK_THROW(load_pfx_cert("localhost.pfx", "wrong"), std::runtime_error); 11 | } 12 | 13 | BOOST_AUTO_TEST_SUITE_END() 14 | -------------------------------------------------------------------------------- /include/http/Time.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace http 6 | { 7 | /**Format date and time for using in HTTP headers such as Date and Last-Modified.*/ 8 | std::string format_time(time_t utc); 9 | 10 | /**Parses date and time as specified by HTTP headers such as Date and Last-Modified.*/ 11 | time_t parse_time(const std::string &time); 12 | } 13 | -------------------------------------------------------------------------------- /include/http/net/TlsSocket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Os.hpp" 3 | #ifdef HTTP_USE_OPENSSL 4 | #include "OpenSslSocket.hpp" 5 | namespace http 6 | { 7 | typedef OpenSslSocket TlsSocket; 8 | typedef OpenSslServerSocket TlsServerSocket; 9 | } 10 | #else 11 | #include "SchannelSocket.hpp" 12 | namespace http 13 | { 14 | typedef SchannelSocket TlsSocket; 15 | typedef SchannelServerSocket TlsServerSocket; 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /source/net/Socket.cpp: -------------------------------------------------------------------------------- 1 | #include "net/Socket.hpp" 2 | #include "net/Net.hpp" 3 | namespace http 4 | { 5 | void Socket::send_all(const void *buffer, size_t len) 6 | { 7 | while (len > 0) 8 | { 9 | auto sent = send(buffer, len); 10 | if (sent == 0) throw SocketError("send_all failed"); 11 | len -= sent; 12 | buffer = ((const char*)buffer) + sent; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/client/SocketFactory.cpp: -------------------------------------------------------------------------------- 1 | #include "client/SocketFactory.hpp" 2 | #include "net/TcpSocket.hpp" 3 | #include "net/TlsSocket.hpp" 4 | 5 | namespace http 6 | { 7 | std::unique_ptr DefaultSocketFactory::connect(const std::string &host, uint16_t port, bool tls) 8 | { 9 | if (tls) return std::unique_ptr(new TlsSocket(host, port)); 10 | else return std::unique_ptr(new TcpSocket(host, port)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/net/OpenSslListenSocket.cpp: -------------------------------------------------------------------------------- 1 | #include "net/OpenSslListenSocket.hpp" 2 | #include "net/OpenSslSocket.hpp" 3 | namespace http 4 | { 5 | OpenSslListenSocket::OpenSslListenSocket(const std::string &bind, uint16_t port, const PrivateCert &cert) 6 | : tcp(bind, port), cert(cert) 7 | { 8 | } 9 | OpenSslServerSocket OpenSslListenSocket::accept() 10 | { 11 | auto sock = tcp.accept(); 12 | return OpenSslServerSocket(std::move(sock), cert); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/net/SchannelListenSocket.cpp: -------------------------------------------------------------------------------- 1 | #include "net/SchannelListenSocket.hpp" 2 | #include "net/SchannelSocket.hpp" 3 | namespace http 4 | { 5 | SchannelListenSocket::SchannelListenSocket(const std::string &bind, uint16_t port, const PrivateCert cert) 6 | : tcp(bind, port), cert(cert) 7 | { 8 | } 9 | SchannelServerSocket SchannelListenSocket::accept() 10 | { 11 | auto sock = tcp.accept(); 12 | return SchannelServerSocket(std::move(sock), cert); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BUILD.md: -------------------------------------------------------------------------------- 1 | The library source code uses the C++11 standard, and should build on VC++ 2015 and GCC 4.8. 2 | A Linux makefile, and a VS 2015 solution are provided. 3 | 4 | # Third Party Libraries 5 | 6 | ## Boost C++ 7 | A number of the boost libraries need to be available. 8 | For convinence on windows 64-bit for Visual C++, boost may be download to 9 | third_party\boost, and third_party/boost64.bat may be run from the 10 | "VS2015 x64 Native Tools Command Prompt" to build the required libraries. 11 | 12 | * Test 13 | -------------------------------------------------------------------------------- /include/http/Method.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #ifdef DELETE 4 | #undef DELETE 5 | #endif 6 | namespace http 7 | { 8 | /**Known HTTP methods enumeration.*/ 9 | enum Method 10 | { 11 | GET, 12 | HEAD, 13 | POST, 14 | PUT, 15 | DELETE, 16 | TRACE, 17 | OPTIONS, 18 | CONNECT, 19 | PATCH, 20 | 21 | LAST_METHOD 22 | }; 23 | 24 | Method method_from_string(const std::string &str); 25 | std::string to_string(Method method); 26 | } 27 | -------------------------------------------------------------------------------- /tests/TestSocketFactory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "client/SocketFactory.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | class TestSocket; 8 | class TestSocketFactory : public http::SocketFactory 9 | { 10 | public: 11 | TestSocketFactory() 12 | : recv_buffer(), last(nullptr), connect_count(0) 13 | {} 14 | ~TestSocketFactory(); 15 | 16 | std::string recv_buffer; 17 | TestSocket *last; 18 | std::atomic connect_count; 19 | std::mutex mutex; 20 | std::set alive_sockets; 21 | 22 | void remove_socket(TestSocket *sock); 23 | virtual std::unique_ptr connect(const std::string &host, uint16_t port, bool tls)override; 24 | }; 25 | 26 | 27 | -------------------------------------------------------------------------------- /source/net/SocketUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "net/Os.hpp" 3 | #include "net/Net.hpp" 4 | #include 5 | #include 6 | namespace http 7 | { 8 | inline void set_non_blocking(SOCKET sock, bool non_blocking) 9 | { 10 | assert(sock != INVALID_SOCKET); 11 | #ifdef WIN32 12 | unsigned long mode = non_blocking ? 0 : 1; 13 | auto ret = ioctlsocket(sock, FIONBIO, &mode); 14 | #else 15 | int flags = fcntl(sock, F_GETFL, 0); 16 | if (flags < 0) throw std::runtime_error("fcntl get failed"); 17 | flags = non_blocking ? (flags&~O_NONBLOCK) : (flags | O_NONBLOCK); 18 | auto ret = fcntl(sock, F_SETFL, flags); 19 | #endif 20 | if (ret) throw SocketError(last_net_error()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /include/http/net/Schannel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Os.hpp" 3 | 4 | #include 5 | #include 6 | 7 | namespace http 8 | { 9 | namespace detail 10 | { 11 | extern SecurityFunctionTableW *sspi; //http::init_net, Net.cpp 12 | 13 | struct SecBufferSingleAutoFree 14 | { 15 | SecBuffer buffer; 16 | SecBufferDesc desc; 17 | 18 | SecBufferSingleAutoFree() 19 | : buffer{ 0, SECBUFFER_TOKEN, nullptr } 20 | , desc{ SECBUFFER_VERSION, 1, &buffer } 21 | { 22 | } 23 | ~SecBufferSingleAutoFree() 24 | { 25 | if (buffer.pvBuffer) sspi->FreeContextBuffer(buffer.pvBuffer); 26 | } 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/TestSocketFactory.cpp: -------------------------------------------------------------------------------- 1 | #include "TestSocketFactory.hpp" 2 | #include "TestSocket.hpp" 3 | 4 | TestSocketFactory::~TestSocketFactory() 5 | { 6 | } 7 | 8 | std::unique_ptr TestSocketFactory::connect(const std::string &host, uint16_t port, bool tls) 9 | { 10 | ++connect_count; 11 | 12 | std::unique_lock lock(mutex); 13 | auto sock = std::unique_ptr(new TestSocket(this)); 14 | alive_sockets.insert(sock.get()); 15 | sock->host = host; 16 | sock->port = port; 17 | sock->tls = tls; 18 | sock->recv_buffer = recv_buffer; 19 | last = sock.get(); 20 | return std::move(sock); 21 | } 22 | 23 | void TestSocketFactory::remove_socket(TestSocket *sock) 24 | { 25 | std::unique_lock lock(mutex); 26 | alive_sockets.erase(sock); 27 | } 28 | -------------------------------------------------------------------------------- /include/http/Request.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Method.hpp" 3 | #include "Headers.hpp" 4 | #include "Url.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace http 10 | { 11 | /**HTTP Request message.*/ 12 | class Request 13 | { 14 | public: 15 | /**HTTP request method.*/ 16 | Method method; 17 | /**Raw HTTP url. 18 | * When sending a request, if this is non-empty, it is used in preference, else 19 | * "url.encode_request()" is used. 20 | */ 21 | std::string raw_url; 22 | /**URL object. See also raw_url.*/ 23 | Url url; 24 | 25 | /**HTTP headers.*/ 26 | Headers headers; 27 | /**Request body. Allthough this is an std::string, it may also contain binary data.*/ 28 | std::string body; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /source/Headers.cpp: -------------------------------------------------------------------------------- 1 | #include "Headers.hpp" 2 | namespace http 3 | { 4 | namespace 5 | { 6 | static const std::string EMPTY_STRING; 7 | } 8 | 9 | const std::string& Headers::get(const std::string &key)const 10 | { 11 | auto it = data.find(key); 12 | if (it != data.end()) return it->second; 13 | else return EMPTY_STRING; 14 | } 15 | 16 | ContentType Headers::content_type()const 17 | { 18 | auto val = get("Content-Type"); 19 | if (val.empty()) return {}; 20 | 21 | auto sep = val.find(';'); 22 | if (sep == std::string::npos) return { val, std::string() }; 23 | 24 | auto mime = val.substr(0, sep); 25 | auto charset_p = val.find("; charset="); 26 | auto charset = val.substr(charset_p + sizeof("; charset=") - 1); 27 | 28 | return { mime, charset }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /include/http/Response.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Headers.hpp" 3 | #include "Status.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace http 9 | { 10 | /**HTTP Response message.*/ 11 | class Response 12 | { 13 | public: 14 | /**Response status code and message.*/ 15 | Status status; 16 | /**Response headers.*/ 17 | Headers headers; 18 | /**Response body. Allthough this is an std::string, it may also contain binary data.*/ 19 | std::string body; 20 | 21 | /**Set the status code and message.*/ 22 | void status_code(StatusCode sc) 23 | { 24 | status.code = sc; 25 | status.msg = default_status_msg(sc); 26 | } 27 | /**Set the status code and message.*/ 28 | void status_code(int sc) 29 | { 30 | status_code((StatusCode)sc); 31 | } 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /tests/Headers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Headers.hpp" 3 | 4 | using namespace http; 5 | 6 | BOOST_AUTO_TEST_SUITE(TestHeaders) 7 | BOOST_AUTO_TEST_CASE(content_type) 8 | { 9 | Headers headers; 10 | BOOST_CHECK(!headers.has("Content-Type")); 11 | BOOST_CHECK_EQUAL("", headers.get("Content-Type")); 12 | 13 | headers.content_type("text/html"); 14 | BOOST_CHECK(headers.has("Content-Type")); 15 | BOOST_CHECK_EQUAL("text/html", headers.get("Content-Type")); 16 | BOOST_CHECK_EQUAL("text/html", headers.content_type().mime); 17 | BOOST_CHECK_EQUAL("", headers.content_type().charset); 18 | 19 | headers.content_type("text/html", "utf8"); 20 | BOOST_CHECK_EQUAL("text/html; charset=utf8", headers.get("Content-Type")); 21 | BOOST_CHECK_EQUAL("text/html", headers.content_type().mime); 22 | BOOST_CHECK_EQUAL("utf8", headers.content_type().charset); 23 | } 24 | BOOST_AUTO_TEST_SUITE_END() 25 | -------------------------------------------------------------------------------- /tests/net/TlsSocket.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "net/TlsSocket.hpp" 3 | #include "net/Net.hpp" 4 | 5 | using namespace http; 6 | 7 | BOOST_AUTO_TEST_SUITE(TestTlsSocket) 8 | 9 | BOOST_AUTO_TEST_CASE(connect) 10 | { 11 | const char *HTTP_REQ = 12 | "GET / HTTP/1.0\r\n" 13 | "Host: willnewbery.co.uk\r\n" 14 | "\r\n"; 15 | TlsSocket sock; 16 | sock.connect("eve.willnewbery.co.uk", 443); 17 | BOOST_CHECK_EQUAL("eve.willnewbery.co.uk:443", sock.address_str()); 18 | sock.send_all((const uint8_t*)HTTP_REQ, strlen(HTTP_REQ)); 19 | 20 | size_t total = 0; 21 | size_t max = 1024 * 1024; 22 | char buffer[4096]; 23 | while (auto len = sock.recv((uint8_t*)buffer, sizeof(buffer))) 24 | { 25 | total += len; 26 | if (total > max) BOOST_FAIL("Received more data than expected"); 27 | } 28 | BOOST_CHECK(total > 0); 29 | 30 | sock.disconnect(); 31 | } 32 | 33 | BOOST_AUTO_TEST_SUITE_END() 34 | -------------------------------------------------------------------------------- /include/http/client/SocketFactory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | namespace http 6 | { 7 | class Socket; 8 | /**Factory interface for creating client socket connections.*/ 9 | class SocketFactory 10 | { 11 | public: 12 | virtual ~SocketFactory() {} 13 | /**Connect to a remote client. 14 | * @param host DNS name or IP of host to connect to. 15 | * @param port Port number to connect to. 16 | * @param tls Socket is required to use TLS. 17 | * @return Connected socket ready to send and receieve. 18 | * @throws NetworkError if connection fails 19 | */ 20 | virtual std::unique_ptr connect(const std::string &host, uint16_t port, bool tls)=0; 21 | }; 22 | 23 | /**Factory using TcpSocket and TlsSocket.*/ 24 | class DefaultSocketFactory : public SocketFactory 25 | { 26 | public: 27 | virtual std::unique_ptr connect(const std::string &host, uint16_t port, bool tls)override; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /source/String.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | namespace http 4 | { 5 | inline bool ieq(const std::string &a, const std::string &b) 6 | { 7 | if (a.size() != b.size()) return false; 8 | for (size_t i = 0; i < a.size(); ++i) 9 | { 10 | char ca = a[i], cb = b[i]; 11 | if (ca >= 'a' && ca <= 'z') ca = (char)(ca - 'a' + 'A'); 12 | if (cb >= 'a' && cb <= 'z') cb = (char)(cb - 'a' + 'A'); 13 | if (ca != cb) return false; 14 | } 15 | return true; 16 | } 17 | } 18 | #ifdef _WIN32 19 | #include 20 | namespace http 21 | { 22 | inline std::string utf16_to_utf8(const std::wstring &utf16) 23 | { 24 | std::wstring_convert, wchar_t> convert; 25 | return convert.to_bytes(utf16); 26 | } 27 | inline std::wstring utf8_to_utf16(const std::string &utf8) 28 | { 29 | std::wstring_convert, wchar_t> convert; 30 | return convert.from_bytes(utf8); 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /include/http/net/OpenSslListenSocket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "TcpListenSocket.hpp" 3 | #include "net/Cert.hpp" 4 | namespace http 5 | { 6 | class OpenSslServerSocket; 7 | /**TCP server side listen socket.*/ 8 | class OpenSslListenSocket 9 | { 10 | public: 11 | /**Listen on a port for a specific local interface address. 12 | * The host name is used to select a certificate from the users personal certificate store 13 | * by a matching common name. 14 | */ 15 | OpenSslListenSocket(const std::string &bind, uint16_t port, const PrivateCert &cert); 16 | /**Listen on a port for all interfaces.*/ 17 | explicit OpenSslListenSocket(uint16_t port, const PrivateCert &cert) 18 | : OpenSslListenSocket("0.0.0.0", port, cert) {} 19 | OpenSslListenSocket()=default; 20 | 21 | /**Blocks awaiting the next inbound connection, then returns it as a TcpSocket object.*/ 22 | OpenSslServerSocket accept(); 23 | private: 24 | TcpListenSocket tcp; 25 | PrivateCert cert; 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /include/http/net/SchannelListenSocket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "TcpListenSocket.hpp" 3 | #include "Cert.hpp" 4 | #include 5 | namespace http 6 | { 7 | class SchannelServerSocket; 8 | /**TCP server side listen socket.*/ 9 | class SchannelListenSocket 10 | { 11 | public: 12 | /**Listen on a port for a specific local interface address. 13 | * The host name is used to select a certificate from the users personal certificate store 14 | * by a matching common name. 15 | */ 16 | SchannelListenSocket(const std::string &bind, uint16_t port, const PrivateCert cert); 17 | /**Listen on a port for all interfaces.*/ 18 | explicit SchannelListenSocket(uint16_t port, const PrivateCert cert) 19 | : SchannelListenSocket("0.0.0.0", port, cert) {} 20 | SchannelListenSocket()=default; 21 | 22 | /**Blocks awaiting the next inbound connection, then returns it as a TcpSocket object.*/ 23 | SchannelServerSocket accept(); 24 | private: 25 | TcpListenSocket tcp; 26 | const PrivateCert cert; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 William Newbery 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /common.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | $(SolutionDir)\$(Platform)-$(Configuration)\ 7 | Intermediate\$(ProjectName)\$(Platform)-$(Configuration)\ 8 | 9 | 10 | 11 | $(ProjectDir)include\http\;$(ProjectDir)source\;$(SolutionDir)third_party\boost\;$(SolutionDir)third_party\openssl\include;%(AdditionalIncludeDirectories) 12 | 13 | 14 | 15 | 16 | Level4 17 | true 18 | 19 | 20 | $(SolutionDir)third_party\boost\stage-$(Platform)\lib\;$(SolutionDir)third_party\openssl\lib\;$(OutDir);%(AdditionalLibraryDirectories) 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/client/ClientConnection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "client/ClientConnection.hpp" 3 | #include "../TestSocket.hpp" 4 | using namespace http; 5 | 6 | BOOST_AUTO_TEST_SUITE(TestClientConnection) 7 | 8 | 9 | BOOST_AUTO_TEST_CASE(test) 10 | { 11 | std::unique_ptr tmp(new TestSocket()); 12 | auto socket = tmp.get(); 13 | ClientConnection conn(std::move(tmp)); 14 | 15 | socket->recv_buffer = 16 | "HTTP/1.1 200 OK\r\n" 17 | "Content-Type: text/plain\r\n" 18 | "Content-Length: 10\r\n" 19 | "Connection: keep-alive\r\n" 20 | "\r\n" 21 | "0123456789"; 22 | 23 | Request req; 24 | req.method = GET; 25 | req.headers.add("Host", "localhost"); 26 | req.raw_url = "/index.html"; 27 | 28 | auto resp = conn.make_request(req); 29 | //TODO: Should check socket->send_buffer, but Date header and undefined order makes this tricky 30 | BOOST_CHECK_EQUAL("", socket->recv_remaining()); 31 | BOOST_CHECK_EQUAL(SC_OK, resp.status.code); 32 | BOOST_CHECK_EQUAL("OK", resp.status.msg); 33 | BOOST_CHECK_EQUAL("text/plain", resp.headers.get("Content-Type")); 34 | BOOST_CHECK_EQUAL("0123456789", resp.body); 35 | } 36 | 37 | 38 | BOOST_AUTO_TEST_SUITE_END() 39 | -------------------------------------------------------------------------------- /source/client/Client.cpp: -------------------------------------------------------------------------------- 1 | #include "client/Client.hpp" 2 | #include "net/Net.hpp" 3 | #include "net/Socket.hpp" 4 | #include 5 | namespace http 6 | { 7 | Client::Client(const std::string &host, uint16_t port, bool tls, SocketFactory *factory) 8 | : _def_headers(), _factory(factory), conn() 9 | , host(host), port(port), tls(tls) 10 | { 11 | _def_headers.set_default("Host", host + ":" + std::to_string(port)); 12 | } 13 | 14 | Client::~Client() 15 | { 16 | } 17 | 18 | Response Client::make_request(Request & request) 19 | { 20 | assert(_factory); 21 | 22 | for (auto &header : _def_headers) 23 | { 24 | request.headers.set_default(header.first, header.second); 25 | } 26 | 27 | // Send 28 | bool sent = false; 29 | if (conn.is_connected()) 30 | { 31 | try 32 | { 33 | conn.send_request(request); 34 | sent = true; 35 | } 36 | catch (const SocketError &) {} 37 | } 38 | if (!sent) 39 | { 40 | conn.reset(nullptr); 41 | conn.reset(_factory->connect(host, port, tls)); 42 | conn.send_request(request); 43 | } 44 | 45 | //Response 46 | return conn.recv_response(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/Method.cpp: -------------------------------------------------------------------------------- 1 | #include "Method.hpp" 2 | #include 3 | #include 4 | namespace http 5 | { 6 | namespace 7 | { 8 | const std::unordered_map STR_METHOD = 9 | { 10 | { "GET", GET }, 11 | { "HEAD", HEAD }, 12 | { "POST", POST }, 13 | { "PUT", PUT }, 14 | { "DELETE", DELETE }, 15 | { "TRACE", TRACE }, 16 | { "OPTIONS", OPTIONS }, 17 | { "CONNECT", CONNECT }, 18 | { "PATCH", PATCH } 19 | }; 20 | } 21 | Method method_from_string(const std::string &str) 22 | { 23 | auto it = STR_METHOD.find(str); 24 | if (it != STR_METHOD.end()) return it->second; 25 | else throw std::runtime_error("Invalid HTTP method " + str); 26 | } 27 | 28 | std::string to_string(Method method) 29 | { 30 | switch (method) 31 | { 32 | case GET: return "GET"; 33 | case HEAD: return "HEAD"; 34 | case POST: return "POST"; 35 | case PUT: return "PUT"; 36 | case DELETE: return "DELETE"; 37 | case TRACE: return "TRACE"; 38 | case OPTIONS: return "OPTIONS"; 39 | case CONNECT: return "CONNECT"; 40 | case PATCH: return "PATCH"; 41 | default: std::terminate(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /include/http/net/TcpListenSocket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Os.hpp" 3 | #include 4 | #include 5 | namespace http 6 | { 7 | class TcpSocket; 8 | /**TCP server side listen socket.*/ 9 | class TcpListenSocket 10 | { 11 | public: 12 | /**Listen on a port for a specific local interface address.*/ 13 | TcpListenSocket(const std::string &bind, uint16_t port); 14 | /**Listen on a port for all interfaces.*/ 15 | explicit TcpListenSocket(uint16_t port) : TcpListenSocket("0.0.0.0", port) {} 16 | TcpListenSocket(); 17 | TcpListenSocket(TcpListenSocket &&mv) : socket(mv.socket) 18 | { 19 | mv.socket = INVALID_SOCKET; 20 | } 21 | TcpListenSocket& operator = (TcpListenSocket &&mv) 22 | { 23 | std::swap(socket, mv.socket); 24 | return *this; 25 | } 26 | 27 | SOCKET get() { return socket; } 28 | /**Sets this sockets non-blocking flag.*/ 29 | void set_non_blocking(bool non_blocking=true); 30 | /**Accepts the next inbound connection, then returns it as a TcpSocket object. 31 | * If the socket is blocking, will wait for the next connection and always returns a 32 | * valid TcpSocket. 33 | * If the socket is non-blocking, an empty TcpSocket may be returned. 34 | */ 35 | TcpSocket accept(); 36 | private: 37 | SOCKET socket; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /include/http/client/Client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "SocketFactory.hpp" 3 | #include "ClientConnection.hpp" 4 | #include "../Request.hpp" 5 | #include "../Response.hpp" 6 | #include "../Headers.hpp" 7 | 8 | namespace http 9 | { 10 | /**@brief Simple single-connection HTTP client that manages the socket, parser, etc. components.*/ 11 | class Client 12 | { 13 | public: 14 | /**Create a new HTTP client which connects to the specified remote server.*/ 15 | Client(const std::string &host, uint16_t port, bool tls, SocketFactory *factory); 16 | ~Client(); 17 | 18 | /**Get the default headers object. Any headers added will be included in any request, 19 | * if that request does not already have a header by that name. 20 | * 21 | * This can be useful for adding headers such as User-Agent. 22 | * 23 | * Note that Host, Date, Content-Length and Transfer-Encoding are always set by the 24 | * request as needed. 25 | */ 26 | Headers& def_headers() { return _def_headers; } 27 | 28 | /**Make a HTTP request. 29 | * Additional headers may be added to the request. 30 | */ 31 | Response make_request(Request &request); 32 | private: 33 | Headers _def_headers; 34 | SocketFactory *_factory; 35 | ClientConnection conn; 36 | const std::string &host; 37 | uint16_t port; 38 | bool tls; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /include/http/client/ClientConnection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../core/Parser.hpp" 3 | #include "../Request.hpp" 4 | #include "../Response.hpp" 5 | #include 6 | 7 | namespace http 8 | { 9 | class Socket; 10 | /**Client side HTTP or HTTPS connection.*/ 11 | class ClientConnection 12 | { 13 | public: 14 | ClientConnection(); 15 | ClientConnection(const ClientConnection&)=delete; 16 | /**Construct by taking ownership of an existing socket.*/ 17 | explicit ClientConnection(std::unique_ptr &&socket); 18 | ~ClientConnection(); 19 | 20 | ClientConnection& operator = (const ClientConnection&) = delete; 21 | 22 | /**Use a new socket.*/ 23 | void reset(std::unique_ptr &&new_socket); 24 | /**True if expected to currently be connected to the remote server.*/ 25 | bool is_connected()const; 26 | /**Make a HTTP request using send_request and recv_response.*/ 27 | Response make_request(Request &request) 28 | { 29 | send_request(request); 30 | return recv_response(); 31 | } 32 | 33 | /**Sends a HTTP request. 34 | * Request will be modified with relevant Content-Length header if needed. 35 | */ 36 | void send_request(Request &request); 37 | /**Receieves a HTTP response.*/ 38 | Response recv_response(); 39 | private: 40 | std::unique_ptr socket; 41 | ResponseParser parser; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /include/http/util/Thread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #ifdef _WIN32 4 | #include 5 | namespace http 6 | { 7 | /**Sets the name of a thread as a debug aid. 8 | * 9 | * On Linux the name is limited to 15 bytes. 10 | * 11 | * On Windows thread names are only stored by an attached debugger. If no debugger is attached 12 | * at the time, then the name will not be set. 13 | */ 14 | inline void set_thread_name(const std::string &str) 15 | { 16 | const DWORD MS_VC_EXCEPTION = 0x406D1388; 17 | #pragma pack(push,8) 18 | struct ThreadInfo 19 | { 20 | DWORD dwType; // Must be 0x1000. 21 | LPCSTR szName; // Pointer to name (in user addr space). 22 | DWORD dwThreadID; // Thread ID (-1=caller thread). 23 | DWORD dwFlags; // Reserved for future use, must be zero. 24 | }; 25 | #pragma pack(pop) 26 | DWORD threadId = GetCurrentThreadId(); 27 | ThreadInfo info = { 0x1000, str.c_str(), threadId, 0 }; 28 | #pragma warning(push) 29 | #pragma warning(disable: 6320 6322) 30 | __try { 31 | RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); 32 | } 33 | __except (EXCEPTION_EXECUTE_HANDLER) { 34 | } 35 | #pragma warning(pop) 36 | } 37 | } 38 | #else 39 | #include 40 | namespace http 41 | { 42 | inline void set_thread_name(const std::string &str) 43 | { 44 | auto str2 = str.substr(0, 15); 45 | pthread_setname_np(pthread_self(), str2.c_str()); 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /tests/Time.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Time.hpp" 3 | #include 4 | 5 | BOOST_AUTO_TEST_SUITE(TestTime) 6 | BOOST_AUTO_TEST_CASE(test) 7 | { 8 | BOOST_CHECK_EQUAL("Fri, 24 Jun 2016 09:47:55 GMT", http::format_time(1466761675)); 9 | 10 | BOOST_CHECK_EQUAL(1466761675, http::parse_time("Fri, 24 Jun 2016 09:47:55 GMT")); 11 | BOOST_CHECK_EQUAL(1466761675, http::parse_time("Friday, 24-Jun-16 09:47:55 GMT")); 12 | BOOST_CHECK_EQUAL(1466761675, http::parse_time("Fri Jun 24 09:47:55 2016")); 13 | 14 | BOOST_CHECK_EQUAL(1466761675, http::parse_time("XXX, 24 Jun 2016 09:47:55 GMT")); 15 | 16 | BOOST_CHECK_THROW(http::parse_time(""), std::runtime_error); 17 | BOOST_CHECK_THROW(http::parse_time("Fri, 24 Jun 2016 09:47:55 PST"), std::runtime_error); 18 | BOOST_CHECK_THROW(http::parse_time("Fri, 24 Jun 2016 09:47:55"), std::runtime_error); 19 | BOOST_CHECK_THROW(http::parse_time("Fri, 24 Jun 2016 GMT"), std::runtime_error); 20 | BOOST_CHECK_THROW(http::parse_time("Fri, 24 XXX 2016 09:47:55 GMT"), std::runtime_error); 21 | BOOST_CHECK_THROW(http::parse_time("Fri, 40 Mar 2016 09:47:55 GMT"), std::runtime_error); 22 | BOOST_CHECK_THROW(http::parse_time("Fri, 24 Jun 1899 09:47:55 GMT"), std::runtime_error); 23 | BOOST_CHECK_THROW(http::parse_time("Fri, 24 Jun 4000 09:47:55 GMT"), std::runtime_error); 24 | BOOST_CHECK_THROW(http::parse_time("Fri, 24 Jun 2016 30:47:55 GMT"), std::runtime_error); 25 | BOOST_CHECK_THROW(http::parse_time("Fri, 24 Jun 2016 09:65:55 GMT"), std::runtime_error); 26 | BOOST_CHECK_THROW(http::parse_time("Fri, 24 Jun 2016 09:47:62 GMT"), std::runtime_error); 27 | } 28 | BOOST_AUTO_TEST_SUITE_END() 29 | -------------------------------------------------------------------------------- /include/http/net/OpenSsl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Os.hpp" 3 | #include "Net.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef OPENSSL_THREADS 10 | # error OPENSSL_THREADS required 11 | #endif 12 | #pragma comment(lib, "libcrypto.lib") 13 | #pragma comment(lib, "libssl.lib") 14 | 15 | namespace http 16 | { 17 | namespace detail 18 | { 19 | struct OpenSslDeleter 20 | { 21 | void operator()(SSL *ssl)const 22 | { 23 | if (ssl) SSL_free(ssl); 24 | } 25 | void operator()(SSL_CTX *ctx)const 26 | { 27 | if (ctx) SSL_CTX_free(ctx); 28 | } 29 | }; 30 | 31 | extern std::unique_ptr openssl_ctx; 32 | extern const SSL_METHOD *openssl_method; 33 | extern std::unique_ptr openssl_server_ctx; 34 | extern const SSL_METHOD *openssl_server_method; 35 | 36 | /**Get a descriptive error string for an OpenSSL error code.*/ 37 | std::string openssl_err_str(SSL *ssl, int error); 38 | void init_openssl(); 39 | } 40 | /**Errors from OpenSSL.*/ 41 | class OpenSslSocketError : public SocketError 42 | { 43 | public: 44 | /**Create an error message using openssl_err_str.*/ 45 | OpenSslSocketError(SSL *ssl, int error) 46 | : SocketError(detail::openssl_err_str(ssl, error)) 47 | { 48 | } 49 | OpenSslSocketError(const std::string &msg, SSL *ssl, int error) 50 | : SocketError(msg + ": " + detail::openssl_err_str(ssl, error)) 51 | { 52 | } 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | LIBS := pthread ssl crypto 2 | CFLAGS := -Wall -Wconversion -std=c++11 3 | LDFLAGS := 4 | 5 | CFLAGS += -g --coverage 6 | LDFLAGS += -g --coverage 7 | 8 | INC_DIRS := include/http source 9 | OBJ_DIR := obj/lib 10 | TEST_OBJ_DIR := obj/tests 11 | BIN_DIR := bin 12 | 13 | SOURCES := $(shell find source/ -name "*.cpp" ! \( -name "SchannelSocket.cpp" -or -name "SchannelServerSocket.cpp" -or -name "SchannelListenSocket.cpp" \) ) 14 | TEST_SOURCES := $(shell find tests/ -name "*.cpp") 15 | 16 | OBJECTS := $(patsubst %, $(OBJ_DIR)/%.o, $(SOURCES)) 17 | TEST_OBJECTS := $(patsubst %, $(TEST_OBJ_DIR)/%.o, $(TEST_SOURCES)) 18 | 19 | CLEAN_FILES := $(OBJ_DIR) $(TEST_OBJ_DIR) $(BIN_DIR) 20 | DEPS := $(OBJECTS:.o=.d) $(TEST_OBJECTS:.o=.d) 21 | 22 | all: build test 23 | clean: 24 | rm -rf $(CLEAN_FILES) coverage 25 | 26 | build: bin/libhttp.a 27 | 28 | bin/libhttp.a: $(OBJECTS) 29 | @mkdir -p $(@D) 30 | ar rcs $@ $(filter %.o,$^) 31 | 32 | $(OBJ_DIR)/%.cpp.o: %.cpp 33 | @mkdir -p $(@D) 34 | g++ $(CFLAGS) $(addprefix -I, $(INC_DIRS)) -c -MMD -MP $< -o $@ 35 | 36 | bin/test: bin/libhttp.a $(TEST_OBJECTS) 37 | @mkdir -p $(@D) 38 | g++ $(LDFLAGS) $(filter %.o,$^) -Lbin -lhttp $(addprefix -l, $(LIBS)) -o $@ 39 | $(TEST_OBJ_DIR)/%.cpp.o: %.cpp 40 | @mkdir -p $(@D) 41 | g++ $(CFLAGS) $(addprefix -I, $(INC_DIRS)) -c -MMD -MP $< -o $@ 42 | 43 | test: bin/test 44 | @mkdir -p coverage 45 | rm -f coverage/all.info coverage/coverage.info 46 | rm -f $(shell find $(OBJ_DIR)/ -name "*.gcda") 47 | bin/test 48 | lcov --capture --directory obj --base-directory . --output-file coverage/all.info -q 49 | lcov --extract coverage/all.info $(shell pwd)/include/\* $(shell pwd)/source/\* --output-file coverage/coverage.info -q 50 | genhtml coverage/coverage.info --output-directory coverage/ 51 | 52 | -include $(DEPS) 53 | 54 | -------------------------------------------------------------------------------- /include/http/net/Cert.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | namespace http 6 | { 7 | struct OpenSslPrivateCertData; 8 | /**A single private key and certificate used to authenticate a TLS server or client.*/ 9 | class PrivateCert 10 | { 11 | public: 12 | #if defined(_WIN32) && !defined(HTTP_USE_OPENSSL) 13 | /**PCCERT_CONTEXT*/ 14 | typedef const void *Native; 15 | #else 16 | typedef std::shared_ptr Native; 17 | #endif 18 | PrivateCert() : native(nullptr) {} 19 | explicit PrivateCert(Native native) : native(native) { } 20 | PrivateCert(const PrivateCert &cp) : native(dup(cp.native)) { } 21 | PrivateCert(PrivateCert &&mv); 22 | ~PrivateCert(); 23 | 24 | PrivateCert& operator = (const PrivateCert &cp) 25 | { 26 | if (native) free(native); 27 | if (cp.native) native = dup(cp.native); 28 | return *this; 29 | } 30 | PrivateCert& operator = (PrivateCert &&mv) 31 | { 32 | std::swap(native, mv.native); 33 | return *this; 34 | } 35 | 36 | explicit operator bool()const 37 | { 38 | return native != nullptr; 39 | } 40 | Native get()const { return native; } 41 | private: 42 | Native native; 43 | 44 | static void free(Native native); 45 | static Native dup(Native native); 46 | }; 47 | 48 | /**Load a certificate with private key from a PKCS#12 archive.*/ 49 | PrivateCert load_pfx_cert(const std::string &file, const std::string &password); 50 | 51 | /**Load a certificate with private key from a pair of pem files. 52 | * NOT COMPLETE 53 | */ 54 | PrivateCert load_pem_priv_cert(const std::string &crt_file, const std::string &key_file); 55 | } 56 | -------------------------------------------------------------------------------- /tests/TestThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #ifdef _WIN32 7 | # include 8 | #else 9 | # include 10 | # include // pthread_kill 11 | #endif 12 | /**Thread wrapper that forcefully kills the child rather than calling abort on failure.*/ 13 | class TestThread : public std::thread 14 | { 15 | public: 16 | template 17 | static std::function wrap(F func) 18 | { 19 | return [func]() -> void 20 | { 21 | try 22 | { 23 | func(); 24 | } 25 | catch (const std::exception &e) 26 | { 27 | BOOST_ERROR(std::string("Unexpected exception: ") + typeid(e).name() + " " + e.what()); 28 | } 29 | }; 30 | } 31 | 32 | TestThread() : std::thread() {} 33 | template 34 | TestThread(F func) : std::thread(wrap(func)) {} 35 | 36 | TestThread& operator = (TestThread &&mv) 37 | { 38 | std::thread::operator=(std::move(mv)); 39 | return *this; 40 | } 41 | ~TestThread() 42 | { 43 | if (joinable()) 44 | { 45 | auto &&handle = native_handle(); 46 | #ifdef _WIN32 47 | if (WaitForSingleObject(handle, 1000) == WAIT_OBJECT_0) 48 | join(); 49 | else 50 | { 51 | BOOST_ERROR("Forcefully terminating test child thread"); 52 | TerminateThread(handle, (DWORD)-1); 53 | join(); 54 | } 55 | #else 56 | timespec time = { 1, 0 }; 57 | void *retval; 58 | if (pthread_timedjoin_np(handle, &retval, &time) == 0) 59 | detach(); 60 | else 61 | { 62 | BOOST_ERROR("Forgetting test child thread"); 63 | //pthread_cancel(handle); 64 | detach(); 65 | } 66 | #endif 67 | } 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /tests/net/TcpSocket.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "net/TcpSocket.hpp" 3 | #include "net/Net.hpp" 4 | 5 | using namespace http; 6 | 7 | BOOST_AUTO_TEST_SUITE(TestTcpSocket) 8 | BOOST_AUTO_TEST_CASE(invalid_host) 9 | { 10 | TcpSocket sock; 11 | BOOST_CHECK_THROW(sock.connect("not#a@valid@domain", 80), NetworkError); 12 | } 13 | 14 | //TODO: this is more difficult to test. Some annoying ISP's like BT like to return a "fake" DNS record to insert there own web pages with the default DHCP DNS servers... 15 | // The result of this is that the address lookup, the thing intending to test, does succeed 16 | // picking a port such ISP's dont open would fail, but is still not testing the intended thing 17 | /*BOOST_AUTO_TEST_CASE(unknown_host) 18 | { 19 | TcpSocket sock; 20 | BOOST_CHECK_THROW(sock.connect("does-not-exist.willnewbery.co.uk", 80), NetworkError); 21 | }*/ 22 | BOOST_AUTO_TEST_CASE(no_connect) 23 | { 24 | //Assumes 3432 is an unused port 25 | TcpSocket sock; 26 | BOOST_CHECK_THROW(sock.connect("localhost", 3432), NetworkError); 27 | } 28 | 29 | BOOST_AUTO_TEST_CASE(connect) 30 | { 31 | //TODO: Come up with something better to connect to 32 | const char *HTTP_REQ = 33 | "GET / HTTP/1.0\r\n" 34 | "Host: willnewbery.co.uk\r\n" 35 | "\r\n"; 36 | TcpSocket sock; 37 | sock.connect("willnewbery.co.uk", 80); 38 | BOOST_CHECK_EQUAL("willnewbery.co.uk", sock.host()); 39 | BOOST_CHECK_EQUAL(80, sock.port()); 40 | BOOST_CHECK_EQUAL("willnewbery.co.uk:80", sock.address_str()); 41 | sock.send_all((const uint8_t*)HTTP_REQ, strlen(HTTP_REQ)); 42 | 43 | size_t total = 0; 44 | size_t max = 1024 * 1024; 45 | char buffer[4096]; 46 | while (auto len = sock.recv((uint8_t*)buffer, sizeof(buffer))) 47 | { 48 | total += len; 49 | if (total > max) BOOST_FAIL("Received more data than expected"); 50 | } 51 | BOOST_CHECK(total > 0); 52 | 53 | BOOST_CHECK_NO_THROW(sock.disconnect()); 54 | } 55 | 56 | BOOST_AUTO_TEST_SUITE_END() 57 | -------------------------------------------------------------------------------- /wrong-host.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 31 | Heiei1BwvJsMZRsDwOUe5/I= 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /include/http/net/Os.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #if !defined(_WIN32) && !defined(HTTP_USE_OPENSSL) 3 | #define HTTP_USE_OPENSSL 4 | #endif 5 | #if !defined(HTTP_USE_SELECT) && !defined(HTTP_USE_IOCP) 6 | #ifdef _WIN32 7 | #define HTTP_USE_IOCP 8 | #else 9 | #define HTTP_USE_SELECT 10 | #endif 11 | #endif 12 | 13 | #ifdef _WIN32 14 | 15 | #ifndef NOMINMAX 16 | # define NOMINMAX 17 | #endif 18 | #ifndef SECURITY_WIN32 19 | # define SECURITY_WIN32 20 | #endif 21 | #if !defined(WIN32_MEAN_AND_LEAN) && !defined(HTTP_NO_WIN32_MEAN_AND_LEAN) 22 | # define WIN32_MEAN_AND_LEAN 23 | #endif 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #pragma comment(lib, "Ws2_32.lib") 30 | #pragma comment(lib, "Crypt32.lib") 31 | #pragma comment(lib, "Secur32.lib") 32 | #pragma comment(lib, "Mswsock.lib") 33 | 34 | namespace http 35 | { 36 | inline SOCKET create_socket(int af = AF_INET, int type = SOCK_STREAM, int prot = IPPROTO_TCP) 37 | { 38 | return WSASocket(af, type, prot, nullptr, 0, WSA_FLAG_OVERLAPPED); 39 | } 40 | } 41 | #else 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | namespace http 50 | { 51 | //WinSock provides some useful types and constants that Linux does not 52 | typedef int SOCKET; 53 | static const int INVALID_SOCKET = -1; 54 | static const int SOCKET_ERROR = -1; 55 | //WinSock and Linux use different names for the `shutdown` constants 56 | static const int SD_RECEIVE = SHUT_RD; 57 | static const int SD_SEND = SHUT_WR; 58 | static const int SD_BOTH = SHUT_RDWR; 59 | 60 | inline SOCKET create_socket(int af = AF_INET, int type = SOCK_STREAM, int prot = IPPROTO_TCP) 61 | { 62 | return ::socket(af, type, prot); 63 | } 64 | //On Windows file descriptors and sockets are different. But on Linux all sockets are file descriptors. 65 | inline void closesocket(SOCKET socket) 66 | { 67 | ::close(socket); 68 | } 69 | } 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 31 | RvdLMTn9jLJQy2jx9vazA0MUGhqJqXU3ww== 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /source/net/TcpListenSocket.cpp: -------------------------------------------------------------------------------- 1 | #include "net/TcpListenSocket.hpp" 2 | #include "net/Net.hpp" 3 | #include "net/TcpSocket.hpp" 4 | #include "net/SocketUtils.hpp" 5 | namespace http 6 | { 7 | TcpListenSocket::TcpListenSocket(const std::string &bind, uint16_t port) 8 | : socket(INVALID_SOCKET) 9 | { 10 | socket = create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 11 | if (socket == INVALID_SOCKET) throw std::runtime_error("Failed to create listen socket"); 12 | //allow fast restart 13 | #ifndef _WIN32 14 | int yes = 1; 15 | setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof(yes)); 16 | #endif 17 | //bind socket 18 | //TODO: Support IPv6 19 | sockaddr_in bind_addr = { 0 }; 20 | bind_addr.sin_family = AF_INET; 21 | bind_addr.sin_port = htons(port); 22 | if (inet_pton(AF_INET, bind.c_str(), &bind_addr.sin_addr) != 1) 23 | throw std::runtime_error("Invalid bind address: " + bind); 24 | 25 | if (::bind(socket, (const sockaddr*)&bind_addr, sizeof(bind_addr))) 26 | throw std::runtime_error("Failed to bind listen socket to " + bind + ":" + std::to_string(port)); 27 | //listen 28 | if (::listen(socket, 10)) 29 | throw std::runtime_error("Socket listen failed for " + bind + ":" + std::to_string(port)); 30 | } 31 | TcpListenSocket::TcpListenSocket() 32 | { 33 | closesocket(socket); 34 | } 35 | void TcpListenSocket::set_non_blocking(bool non_blocking) 36 | { 37 | http::set_non_blocking(socket, non_blocking); 38 | } 39 | TcpSocket TcpListenSocket::accept() 40 | { 41 | sockaddr_storage client_addr = { 0 }; 42 | socklen_t client_addr_len = (socklen_t)sizeof(client_addr); 43 | auto client_socket = ::accept(socket, (sockaddr*)&client_addr, &client_addr_len); 44 | if (client_socket == INVALID_SOCKET) 45 | throw SocketError("socket accept failed", last_net_error()); 46 | return { client_socket, (sockaddr*)&client_addr }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/client/Client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "client/Client.hpp" 3 | #include "../TestSocket.hpp" 4 | #include "../TestSocketFactory.hpp" 5 | using namespace http; 6 | 7 | BOOST_AUTO_TEST_SUITE(TestClient) 8 | 9 | 10 | BOOST_AUTO_TEST_CASE(test) 11 | { 12 | TestSocketFactory socket_factory; 13 | socket_factory.recv_buffer = 14 | "HTTP/1.1 200 OK\r\n" 15 | "Content-Type: text/plain\r\n" 16 | "Content-Length: 10\r\n" 17 | "Connection: keep-alive\r\n" 18 | "\r\n" 19 | "0123456789"; 20 | 21 | Client client("localhost", 80, false, &socket_factory); 22 | client.def_headers().set("User-Agent", "test"); 23 | 24 | { 25 | Request req; 26 | req.method = GET; 27 | req.raw_url = "/index.html"; 28 | 29 | auto resp = client.make_request(req); 30 | 31 | BOOST_REQUIRE(socket_factory.alive_sockets.count(socket_factory.last) > 0); 32 | BOOST_CHECK_EQUAL("", socket_factory.last->recv_remaining()); 33 | BOOST_CHECK_EQUAL("localhost:80", req.headers.get("Host")); 34 | BOOST_CHECK_EQUAL("test", req.headers.get("User-Agent")); 35 | 36 | BOOST_CHECK_EQUAL(SC_OK, resp.status.code); 37 | BOOST_CHECK_EQUAL("OK", resp.status.msg); 38 | BOOST_CHECK_EQUAL("text/plain", resp.headers.get("Content-Type")); 39 | BOOST_CHECK_EQUAL("0123456789", resp.body); 40 | } 41 | //connection reuse 42 | socket_factory.last->recv_buffer = 43 | "HTTP/1.1 204 No Content\r\n" 44 | "Server: example\r\n" 45 | "Connection: keep-alive\r\n" 46 | "\r\n"; 47 | { 48 | Request req; 49 | req.method = POST; 50 | req.raw_url = "/example"; 51 | req.body = {'X'}; 52 | 53 | auto resp = client.make_request(req); 54 | 55 | BOOST_REQUIRE(socket_factory.alive_sockets.count(socket_factory.last) > 0); 56 | BOOST_CHECK_EQUAL("", socket_factory.last->recv_remaining()); 57 | BOOST_CHECK_EQUAL("localhost:80", req.headers.get("Host")); 58 | BOOST_CHECK_EQUAL("test", req.headers.get("User-Agent")); 59 | BOOST_CHECK_EQUAL("1", req.headers.get("Content-Length")); 60 | 61 | BOOST_CHECK_EQUAL(SC_NO_CONTENT, resp.status.code); 62 | BOOST_CHECK_EQUAL("No Content", resp.status.msg); 63 | BOOST_CHECK_EQUAL("", resp.body); 64 | } 65 | } 66 | 67 | 68 | BOOST_AUTO_TEST_SUITE_END() 69 | -------------------------------------------------------------------------------- /tests/TestSocket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "net/Socket.hpp" 3 | #include "TestSocketFactory.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | /**@brief A fake network socket using a string buffer.*/ 9 | class TestSocket : public http::Socket 10 | { 11 | public: 12 | TestSocket(TestSocketFactory *factory = nullptr) 13 | : factory(factory), tls(false), port(80) 14 | , recv_p(0), sent_last(false) {} 15 | ~TestSocket() 16 | { 17 | if (factory) factory->remove_socket(this); 18 | } 19 | #if _WIN32 20 | virtual SOCKET get()override { return 0; } 21 | #else 22 | virtual http::SOCKET get()override { return 0; } 23 | #endif 24 | virtual std::string address_str()const 25 | { 26 | return host; 27 | } 28 | 29 | virtual void close()override {} 30 | virtual void disconnect()override {} 31 | virtual bool check_recv_disconnect()override { return false; } 32 | 33 | virtual size_t recv(void *buffer, size_t len)override 34 | { 35 | if (sent_last) 36 | { 37 | recv_p = 0; 38 | sent_last = false; 39 | } 40 | len = std::min(len, recv_buffer.size() - recv_p); 41 | memcpy(buffer, recv_buffer.c_str() + recv_p, len); 42 | recv_p += len; 43 | return len; 44 | } 45 | 46 | virtual size_t send(const void *buffer, size_t len)override 47 | { 48 | sent_last = true; 49 | send_buffer.append((const char*)buffer, len); 50 | return len; 51 | } 52 | 53 | std::string recv_remaining()const 54 | { 55 | return { recv_buffer.begin() + recv_p, recv_buffer.end() }; 56 | } 57 | 58 | virtual void async_disconnect(http::AsyncIo &, 59 | std::function, http::AsyncIo::ErrorHandler)override {} 60 | virtual void async_recv(http::AsyncIo &, void *, size_t, 61 | http::AsyncIo::RecvHandler, http::AsyncIo::ErrorHandler)override {} 62 | virtual void async_send(http::AsyncIo &, const void *, size_t, 63 | http::AsyncIo::SendHandler, http::AsyncIo::ErrorHandler)override {} 64 | virtual void async_send_all(http::AsyncIo &, const void *, size_t, 65 | http::AsyncIo::SendHandler, http::AsyncIo::ErrorHandler)override {} 66 | 67 | TestSocketFactory *factory; 68 | bool tls; 69 | std::string host; 70 | uint16_t port; 71 | std::string recv_buffer; 72 | size_t recv_p; 73 | bool sent_last; 74 | std::string send_buffer; 75 | }; 76 | -------------------------------------------------------------------------------- /source/client/ClientConnection.cpp: -------------------------------------------------------------------------------- 1 | #include "client/ClientConnection.hpp" 2 | #include "core/Writer.hpp" 3 | #include "net/Socket.hpp" 4 | #include 5 | #include 6 | namespace http 7 | { 8 | ClientConnection::ClientConnection() : socket(nullptr) {} 9 | ClientConnection::ClientConnection(std::unique_ptr &&socket) : socket(nullptr) 10 | { 11 | reset(std::move(socket)); 12 | } 13 | ClientConnection::~ClientConnection() 14 | {} 15 | 16 | void ClientConnection::reset(std::unique_ptr &&new_socket) 17 | { 18 | socket = std::move(new_socket); 19 | } 20 | 21 | bool ClientConnection::is_connected()const 22 | { 23 | return socket != nullptr && !socket->check_recv_disconnect(); 24 | } 25 | 26 | void ClientConnection::send_request(Request &request) 27 | { 28 | assert(socket); 29 | request.headers.set_default("Connection", "keep-alive"); 30 | add_default_headers(request); 31 | http::send_request(socket.get(), request); 32 | 33 | parser.reset(request.method); 34 | } 35 | 36 | Response ClientConnection::recv_response() 37 | { 38 | assert(socket); 39 | char buffer[ResponseParser::LINE_SIZE]; 40 | size_t buffer_capacity = sizeof(buffer); 41 | size_t buffer_len = 0; 42 | 43 | while (!parser.is_completed()) 44 | { 45 | auto recv = socket->recv(buffer + buffer_len, buffer_capacity - buffer_len); 46 | if (recv == 0) 47 | { 48 | if (parser.state() == ResponseParser::START) 49 | throw std::runtime_error("Server disconnected before response was sent"); 50 | else throw std::runtime_error("Server disconnected before response was complete"); 51 | } 52 | buffer_len += recv; 53 | 54 | auto end = buffer + buffer_len; 55 | auto read_end = parser.read(buffer, end); 56 | if (read_end == buffer && buffer_len == buffer_capacity) 57 | { 58 | throw std::runtime_error("Response line too large"); 59 | } 60 | 61 | buffer_len = (size_t)(end - read_end); 62 | memmove(buffer, read_end, buffer_len); 63 | } 64 | if (buffer_len != 0) throw std::runtime_error("Unexpected content after response"); 65 | 66 | Response resp; 67 | resp.headers = std::move(parser.headers()); 68 | resp.body = std::move(parser.body()); 69 | resp.status = std::move(parser.status()); 70 | 71 | if (resp.headers.get("Connection") != "keep-alive") 72 | { 73 | socket = nullptr; 74 | } 75 | return resp; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /source/net/Net.cpp: -------------------------------------------------------------------------------- 1 | #include "net/Net.hpp" 2 | #include "net/Os.hpp" 3 | 4 | #include "String.hpp" 5 | #include 6 | 7 | #ifdef HTTP_USE_OPENSSL 8 | #include "net/OpenSsl.hpp" 9 | #endif 10 | 11 | #ifdef _WIN32 12 | #include "net/Schannel.hpp" 13 | namespace http 14 | { 15 | namespace detail 16 | { 17 | SecurityFunctionTableW *sspi; 18 | } 19 | 20 | void init_net() 21 | { 22 | WSADATA wsa_data; 23 | WSAStartup(MAKEWORD(2, 2), &wsa_data); 24 | #ifdef HTTP_USE_OPENSSL 25 | detail::init_openssl(); 26 | #else 27 | detail::sspi = InitSecurityInterfaceW(); 28 | #endif 29 | } 30 | std::string win_error_string(int err) 31 | { 32 | struct Buffer 33 | { 34 | wchar_t *p; 35 | Buffer() : p(nullptr) {} 36 | ~Buffer() 37 | { 38 | LocalFree(p); 39 | } 40 | }; 41 | Buffer buffer; 42 | FormatMessageW( 43 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 44 | nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 45 | (LPWSTR)&buffer.p, 0, nullptr); 46 | return utf16_to_utf8(buffer.p); 47 | } 48 | int last_net_error() 49 | { 50 | return WSAGetLastError(); 51 | } 52 | bool would_block(int err) 53 | { 54 | return err == WSAEWOULDBLOCK; 55 | } 56 | std::string errno_string(int err) 57 | { 58 | char buffer[1024]; 59 | if (strerror_s(buffer, sizeof(buffer), err)) 60 | throw std::runtime_error("strerror_s failed"); 61 | return buffer; 62 | } 63 | WinError::WinError(const char *msg) 64 | : WinError(msg, GetLastError()) 65 | {} 66 | } 67 | #else 68 | #include //strerror_r 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | 75 | namespace http 76 | { 77 | 78 | void init_net() 79 | { 80 | detail::init_openssl(); 81 | //Linux sends a sigpipe when a socket or pipe has an error. better to handle the error where it 82 | //happens at the read/write site 83 | signal(SIGPIPE, SIG_IGN); 84 | } 85 | int last_net_error() 86 | { 87 | return errno; 88 | } 89 | bool would_block(int err) 90 | { 91 | return err == EAGAIN || err == EWOULDBLOCK; 92 | } 93 | std::string errno_string(int err) 94 | { 95 | char buffer[1024]; 96 | errno = 0; 97 | auto ret = strerror_r(err, buffer, sizeof(buffer)); 98 | if (errno) throw std::runtime_error("strerror_r failed"); 99 | return ret; 100 | } 101 | } 102 | #endif 103 | 104 | -------------------------------------------------------------------------------- /include/http/Status.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | namespace http 4 | { 5 | //http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml 6 | //2016-03-01 7 | 8 | /**HTTP known status codes enumeration.*/ 9 | enum StatusCode 10 | { 11 | SC_CONTINUE = 100, 12 | SC_SWITCHING_PROTOCOLS = 101, 13 | SC_PROCESSING = 102, 14 | 15 | SC_OK = 200, 16 | SC_CREATED = 201, 17 | SC_ACCEPTED = 202, 18 | SC_NON_AUTHORITATIVE_INFORMATION = 203, 19 | SC_NO_CONTENT = 204, 20 | SC_RESET_CONTENT = 205, 21 | SC_PARTIAL_CONTENT = 206, 22 | SC_MULTI_STATUS = 207, 23 | SC_ALREADY_REPORTED = 208, 24 | SC_IM_USED = 226, 25 | 26 | SC_MULTIPLE_CHOICES = 300, 27 | SC_MOVED_PERMANENTLY = 301, 28 | SC_FOUND = 302, 29 | SC_SEE_OTHER = 303, 30 | SC_NOT_MODIFIED = 304, 31 | SC_USE_PROXY = 305, 32 | SC_TEMPORARY_REDIRECT = 307, 33 | SC_PERMANENT_REDIRECT = 308, 34 | 35 | SC_BAD_REQUEST = 400, 36 | SC_UNAUTHORIZED = 401, 37 | SC_PAYMENT_REQUIRED = 402, 38 | SC_FORBIDDEN = 403, 39 | SC_NOT_FOUND = 404, 40 | SC_METHOD_NOT_ALLOWED = 405, 41 | SC_NOT_ACCEPTABLE = 406, 42 | SC_PROXY_AUTHENTICATION_REQUIRED = 407, 43 | SC_REQUEST_TIMEOUT = 408, 44 | SC_CONFIICT = 409, 45 | SC_GONE = 410, 46 | SC_LENGTH_REQUIRED = 411, 47 | SC_PRECONDITION_FAILED = 412, 48 | SC_PAYLOAD_TOO_LARGE = 413, 49 | SC_URI_TOO_LONG = 414, 50 | SC_UNSUPPORTED_MEDIA_TYPE = 415, 51 | SC_RANGE_NOT_SATISFIABLE = 416, 52 | SC_EXPECTATION_FAILED = 417, 53 | SC_MISDIRECTED_REQUEST = 421, 54 | SC_UNPROCESSABLE_ENTITY = 422, 55 | SC_LOCKED = 423, 56 | SC_FAILED_DEPENDENCY = 424, 57 | SC_UPGRADE_REQUIRED = 426, 58 | SC_PRECONDITION_REQUIRED = 428, 59 | SC_TOO_MANY_REQUESTS = 429, 60 | SC_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, 61 | SC_UNAVAILABLE_FOR_LEGAL_REASONS = 451, 62 | 63 | SC_INTERNAL_SERVER_ERROR = 500, 64 | SC_NOT_IMPLEMENTED = 501, 65 | SC_BAD_GATEWAY = 502, 66 | SC_SERVICE_UNAVAILABLE = 503, 67 | SC_GATEWAY_TIMEOUT = 504, 68 | SC_HTTP_VERSION_NOT_SUPPORTED = 505, 69 | SC_VARIANT_ALSO_NEGOTIATES = 506, 70 | SC_INSUFFICIENT_STORAGE = 507, 71 | SC_LOOP_DETECTED = 508, 72 | SC_NOT_EXTENDED = 510, 73 | SC_NETWORK_AUTHENTICATION_REQUIRED = 511 74 | }; 75 | 76 | /**Gets a default text message for a status code. 77 | * e.g. "Not Found" for 404. 78 | */ 79 | std::string default_status_msg(StatusCode sc); 80 | 81 | /**A status code and message.*/ 82 | struct Status 83 | { 84 | StatusCode code; 85 | std::string msg; 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /include/http/net/TcpSocket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Socket.hpp" 3 | #include "Os.hpp" 4 | #include 5 | namespace http 6 | { 7 | /**Basic unencrypted TCP stream socket using SOCKET and related system API's.*/ 8 | class TcpSocket : public Socket 9 | { 10 | public: 11 | TcpSocket(); 12 | /**Establish a new client connection. See connect.*/ 13 | TcpSocket(const std::string &host, uint16_t port); 14 | /**Construct using an existing socket. See set_socket.*/ 15 | TcpSocket(SOCKET socket, const sockaddr *address); 16 | virtual ~TcpSocket(); 17 | 18 | TcpSocket(const TcpSocket&) = delete; 19 | TcpSocket& operator= (const TcpSocket&) = delete; 20 | 21 | TcpSocket(TcpSocket &&mv); 22 | TcpSocket& operator = (TcpSocket &&mv); 23 | 24 | explicit operator bool ()const { return socket != INVALID_SOCKET; } 25 | 26 | /**Take ownership of an existing TCP SOCKET object. 27 | * The remote address object is used to populate the host and port. 28 | */ 29 | void set_socket(SOCKET socket, const sockaddr *address); 30 | /**Create a new client side connection to a remote host or port. 31 | * Host can either be a hostname or an IP address. 32 | */ 33 | void connect(const std::string &host, uint16_t port); 34 | 35 | virtual SOCKET get()override { return socket; } 36 | /**Sets this sockets non-blocking flag.*/ 37 | void set_non_blocking(bool non_blocking = true); 38 | /**Gets the remote address as a string in the form `host() + ':' + port()`.*/ 39 | virtual std::string address_str()const override; 40 | /**Get the remote host name or IP address.*/ 41 | const std::string &host()const { return _host; } 42 | /**Get the remote port number.*/ 43 | uint16_t port()const { return _port;; } 44 | virtual void close()override; 45 | virtual void disconnect()override; 46 | virtual size_t recv(void *buffer, size_t len)override; 47 | virtual size_t send(const void *buffer, size_t len)override; 48 | virtual bool check_recv_disconnect()override; 49 | virtual void async_disconnect(AsyncIo &aio, 50 | std::function handler, AsyncIo::ErrorHandler error)override; 51 | virtual void async_recv(AsyncIo &aio, void *buffer, size_t len, 52 | AsyncIo::RecvHandler handler, AsyncIo::ErrorHandler error)override; 53 | virtual void async_send(AsyncIo &aio, const void *buffer, size_t len, 54 | AsyncIo::SendHandler handler, AsyncIo::ErrorHandler error)override; 55 | virtual void async_send_all(AsyncIo &aio, const void *buffer, size_t len, 56 | AsyncIo::SendHandler handler, AsyncIo::ErrorHandler error)override; 57 | private: 58 | SOCKET socket; 59 | std::string _host; 60 | uint16_t _port; 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /include/http/server/CoreServer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../net/AsyncIo.hpp" 3 | #include "../net/TcpListenSocket.hpp" 4 | #include "../net/Cert.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | namespace http 13 | { 14 | class Socket; 15 | class TcpSocket; 16 | class Request; 17 | class Response; 18 | class ParserError; 19 | //TODO: add clean-shutdown functionality 20 | /**A minimalistic server implementation. 21 | * Uses multiple threads for connections, but contains no logic for 22 | * processing the recieved requests. 23 | */ 24 | class CoreServer 25 | { 26 | public: 27 | /**Create a server with no initial listeners.*/ 28 | CoreServer(){} 29 | /**Listen on a specific interface local IP and port.*/ 30 | CoreServer(const std::string &bind, uint16_t port) 31 | : CoreServer() 32 | { 33 | add_tcp_listener(bind, port); 34 | } 35 | /**Listen on all interfaces for a given port.*/ 36 | explicit CoreServer(uint16_t port) : CoreServer("0.0.0.0", port) {} 37 | virtual ~CoreServer(); 38 | 39 | /**Add a listener before calling run.*/ 40 | void add_tcp_listener(const std::string &bind, uint16_t port); 41 | /**Add a TLS listener before calling run.*/ 42 | void add_tls_listener(const std::string &bind, uint16_t port, const PrivateCert &cert); 43 | void run(); 44 | /**Signals the thread in run() and all workers to exit, then waits for them.*/ 45 | void exit(); 46 | 47 | protected: 48 | /**Process the request. This may be called by multiple internal threads. 49 | * Any uncaught exception will kill the thread. 50 | */ 51 | virtual Response handle_request(Request &request)=0; 52 | /**Create an error response page. 53 | * This may be called by CoreServer instead of handle_request if there was an issue reading 54 | * the HTTP request. 55 | * 56 | * Any uncaught exception will kill the thread. 57 | */ 58 | virtual Response parser_error_page(const ParserError &err)=0; 59 | private: 60 | struct Listener 61 | { 62 | TcpListenSocket socket; 63 | bool tls; 64 | PrivateCert tls_cert; 65 | }; 66 | class Connection; 67 | 68 | AsyncIo aio; 69 | std::vector listeners; 70 | /**Held by run(), preventing exit() from continueing until run() is finished.*/ 71 | std::mutex running_mutex; 72 | std::mutex handle_mutex; 73 | std::vector> in_progress_handlers; 74 | 75 | void accept_next(Listener &listener); 76 | void accept(Listener &listener, TcpSocket &&sock); 77 | void accept_error(); 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /include/http/net/Socket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "AsyncIo.hpp" 3 | #include "Os.hpp" 4 | #include 5 | namespace http 6 | { 7 | /**Base socket type. 8 | * Different underlying protocols may potentionally be used, e.g. TCP/IP or TLS, but it will 9 | * always be a stream socket. 10 | * 11 | * Socket objects generally own the underlying socket, and will close it when the object is 12 | * destroyed in the destructor. 13 | */ 14 | class Socket 15 | { 16 | public: 17 | Socket() {} 18 | Socket(Socket&&)=default; 19 | virtual ~Socket() {} 20 | Socket& operator = (Socket&&)=default; 21 | 22 | /**Get the underlaying socket.*/ 23 | virtual SOCKET get() = 0; 24 | /**Gets the remote address as a string for display purposes. 25 | * Includes the port number and may use a hostname or ip address. 26 | */ 27 | virtual std::string address_str()const = 0; 28 | /**Close the socket, without waiting for a clean disconnect.*/ 29 | virtual void close() = 0; 30 | /**Disconnect this socket if connected. Sending and receiving will fail after.*/ 31 | virtual void disconnect() = 0; 32 | /**True if the socket has locally cached data pending recv. 33 | * Because this data is locally cached, and not pending OS data, it will not trigger 34 | * a read on select() or similar low-level network API's. 35 | */ 36 | virtual bool recv_pending()const { return false; } 37 | /**Receive up to len bytes. Works like Berkeley `recv`.*/ 38 | virtual size_t recv(void *buffer, size_t len) = 0; 39 | /**Send up to len bytes. Works like Berkeley `send`.*/ 40 | virtual size_t send(const void *buffer, size_t len) = 0; 41 | /**See if the socket is ready to recv, and if that recv returns 0. 42 | * If recv reads some data, that is considered an error. 43 | * 44 | * Intended to be used to see if the remote closed the connection in a 45 | * situation where no data would be valid. 46 | */ 47 | virtual bool check_recv_disconnect() = 0; 48 | /**Sends len bytes, calling send repeatedly if needed.*/ 49 | void send_all(const void *buffer, size_t len); 50 | 51 | virtual void async_disconnect(AsyncIo &aio, 52 | std::function handler, AsyncIo::ErrorHandler error) = 0; 53 | virtual void async_recv(AsyncIo &aio, void *buffer, size_t len, 54 | AsyncIo::RecvHandler handler, AsyncIo::ErrorHandler error) = 0; 55 | virtual void async_send(AsyncIo &aio, const void *buffer, size_t len, 56 | AsyncIo::SendHandler handler, AsyncIo::ErrorHandler error) = 0; 57 | virtual void async_send_all(AsyncIo &aio, const void *buffer, size_t len, 58 | AsyncIo::SendHandler handler, AsyncIo::ErrorHandler error) = 0; 59 | private: 60 | Socket(const Socket &socket); 61 | Socket& operator = (const Socket &socket); 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /source/net/OpenSsl.cpp: -------------------------------------------------------------------------------- 1 | #define _CRT_SECURE_NO_DEPRECATE 2 | #include "net/OpenSsl.hpp" 3 | #include "net/Net.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #ifdef _WIN32 10 | #include 11 | #endif 12 | namespace http 13 | { 14 | namespace detail 15 | { 16 | std::unique_ptr openssl_mutexes; 17 | std::unique_ptr openssl_ctx; 18 | const SSL_METHOD *openssl_method; 19 | std::unique_ptr openssl_server_ctx; 20 | const SSL_METHOD *openssl_server_method; 21 | 22 | std::string openssl_err_str(SSL *ssl, int ret) 23 | { 24 | auto e2 = errno; 25 | auto serr = SSL_get_error(ssl, ret); 26 | if (serr == SSL_ERROR_SYSCALL) return errno_string(e2); 27 | // Get OpenSSL error 28 | auto str = ERR_error_string(serr, nullptr); 29 | if (str) return str; 30 | else return errno_string(ret); 31 | } 32 | 33 | unsigned long openssl_thread_id(void) 34 | { 35 | #ifdef _WIN32 36 | return GetCurrentThreadId(); 37 | #else 38 | return (unsigned long)pthread_self(); 39 | #endif 40 | } 41 | void openssl_locking_callback(int mode, int type, const char * /*file*/, int /*line*/) 42 | { 43 | assert(openssl_mutexes); 44 | assert(type >= 0); 45 | assert(type < (int)CRYPTO_num_locks()); 46 | if (mode & CRYPTO_LOCK) 47 | { 48 | openssl_mutexes[type].lock(); 49 | } 50 | else 51 | { 52 | openssl_mutexes[type].unlock(); 53 | } 54 | } 55 | 56 | void init_openssl() 57 | { 58 | #ifdef _WIN32 59 | OPENSSL_Applink(); 60 | #endif 61 | SSL_load_error_strings(); //OpenSSL SSL error strings 62 | ERR_load_BIO_strings(); 63 | OpenSSL_add_ssl_algorithms(); 64 | 65 | // Client 66 | openssl_method = SSLv23_client_method();//TLS_client_method(); 67 | openssl_ctx.reset(SSL_CTX_new(openssl_method)); 68 | if (!openssl_ctx) throw std::runtime_error("SSL_CTX_new failed"); 69 | // Load default trust roots 70 | if (!SSL_CTX_set_default_verify_paths(openssl_ctx.get())) 71 | throw std::runtime_error("SSL_CTX_set_default_verify_paths failed"); 72 | // Server 73 | openssl_server_method = SSLv23_server_method(); 74 | openssl_server_ctx.reset(SSL_CTX_new(openssl_server_method)); 75 | if (!openssl_server_ctx) throw std::runtime_error("SSL_CTX_new failed"); 76 | // Multi-threading 77 | openssl_mutexes.reset(new std::mutex[CRYPTO_num_locks()]); 78 | CRYPTO_set_id_callback(openssl_thread_id); 79 | CRYPTO_set_locking_callback(openssl_locking_callback); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /cpphttp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cpphttp", "cpphttp.vcxproj", "{53A098C1-A321-4D50-AB68-1B46FF556100}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cpphttp-ut", "cpphttp-ut.vcxproj", "{1FCCDC8C-F6C8-47DA-8442-EB43E8170364}" 9 | ProjectSection(ProjectDependencies) = postProject 10 | {53A098C1-A321-4D50-AB68-1B46FF556100} = {53A098C1-A321-4D50-AB68-1B46FF556100} 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | DebugOpenSSL|x64 = DebugOpenSSL|x64 18 | DebugOpenSSL|x86 = DebugOpenSSL|x86 19 | Release|x64 = Release|x64 20 | Release|x86 = Release|x86 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {53A098C1-A321-4D50-AB68-1B46FF556100}.Debug|x64.ActiveCfg = Debug|x64 24 | {53A098C1-A321-4D50-AB68-1B46FF556100}.Debug|x64.Build.0 = Debug|x64 25 | {53A098C1-A321-4D50-AB68-1B46FF556100}.Debug|x86.ActiveCfg = Debug|Win32 26 | {53A098C1-A321-4D50-AB68-1B46FF556100}.Debug|x86.Build.0 = Debug|Win32 27 | {53A098C1-A321-4D50-AB68-1B46FF556100}.DebugOpenSSL|x64.ActiveCfg = DebugOpenSSL|x64 28 | {53A098C1-A321-4D50-AB68-1B46FF556100}.DebugOpenSSL|x64.Build.0 = DebugOpenSSL|x64 29 | {53A098C1-A321-4D50-AB68-1B46FF556100}.DebugOpenSSL|x86.ActiveCfg = DebugOpenSSL|Win32 30 | {53A098C1-A321-4D50-AB68-1B46FF556100}.DebugOpenSSL|x86.Build.0 = DebugOpenSSL|Win32 31 | {53A098C1-A321-4D50-AB68-1B46FF556100}.Release|x64.ActiveCfg = Release|x64 32 | {53A098C1-A321-4D50-AB68-1B46FF556100}.Release|x64.Build.0 = Release|x64 33 | {53A098C1-A321-4D50-AB68-1B46FF556100}.Release|x86.ActiveCfg = Release|Win32 34 | {53A098C1-A321-4D50-AB68-1B46FF556100}.Release|x86.Build.0 = Release|Win32 35 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.Debug|x64.ActiveCfg = Debug|x64 36 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.Debug|x64.Build.0 = Debug|x64 37 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.Debug|x86.ActiveCfg = Debug|Win32 38 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.Debug|x86.Build.0 = Debug|Win32 39 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.DebugOpenSSL|x64.ActiveCfg = DebugOpenSSL|x64 40 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.DebugOpenSSL|x64.Build.0 = DebugOpenSSL|x64 41 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.DebugOpenSSL|x86.ActiveCfg = DebugOpenSSL|Win32 42 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.DebugOpenSSL|x86.Build.0 = DebugOpenSSL|Win32 43 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.Release|x64.ActiveCfg = Release|x64 44 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.Release|x64.Build.0 = Release|x64 45 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.Release|x86.ActiveCfg = Release|Win32 46 | {1FCCDC8C-F6C8-47DA-8442-EB43E8170364}.Release|x86.Build.0 = Release|Win32 47 | EndGlobalSection 48 | GlobalSection(SolutionProperties) = preSolution 49 | HideSolutionNode = FALSE 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /cpphttp-ut.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {f7f6cfcd-886c-427e-9ee9-646124bacd50} 6 | 7 | 8 | {ca91ff06-7758-40ca-b6ac-53c07dd80106} 9 | 10 | 11 | {bab0b4a7-a771-47f5-93dd-7c0e7217d558} 12 | 13 | 14 | {0b826519-4ca7-4708-890d-17e4a0808f64} 15 | 16 | 17 | {2a03d343-3100-4840-8564-7535751e2820} 18 | 19 | 20 | {3e1f61f6-15e1-4ee8-a9f4-af36f22a9937} 21 | 22 | 23 | 24 | 25 | source 26 | 27 | 28 | source 29 | 30 | 31 | source 32 | 33 | 34 | source 35 | 36 | 37 | source\net 38 | 39 | 40 | source\net 41 | 42 | 43 | source\core 44 | 45 | 46 | source\core 47 | 48 | 49 | source\client 50 | 51 | 52 | source\client 53 | 54 | 55 | source\client 56 | 57 | 58 | source\server 59 | 60 | 61 | source\headers 62 | 63 | 64 | source\net 65 | 66 | 67 | source\server 68 | 69 | 70 | source\net 71 | 72 | 73 | source 74 | 75 | 76 | 77 | 78 | source 79 | 80 | 81 | source 82 | 83 | 84 | source 85 | 86 | 87 | -------------------------------------------------------------------------------- /tests/net/TlsServer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "net/TlsListenSocket.hpp" 3 | #include "net/TlsSocket.hpp" 4 | #include "net/Cert.hpp" 5 | #include "net/Net.hpp" 6 | #include "../TestThread.hpp" 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace http; 12 | 13 | BOOST_AUTO_TEST_SUITE(TestTlsServer) 14 | 15 | static const uint16_t BASE_PORT = 5000; 16 | 17 | struct Event 18 | { 19 | bool ready = false; 20 | std::mutex mutex; 21 | std::condition_variable cv; 22 | }; 23 | void success_server_thread(Event &event) 24 | { 25 | TlsListenSocket listen("127.0.0.1", BASE_PORT, load_pfx_cert("localhost.pfx", "password")); 26 | 27 | { 28 | std::unique_lock lock(event.mutex); 29 | event.ready = true; 30 | event.cv.notify_all(); 31 | } 32 | { 33 | auto sock = listen.accept(); 34 | 35 | char buffer[1024]; 36 | auto len = sock.recv(buffer, sizeof(buffer)); 37 | BOOST_CHECK_EQUAL(4, len); 38 | buffer[4] = 0; 39 | BOOST_CHECK_EQUAL("ping", buffer); 40 | 41 | sock.send_all("pong", 4); 42 | 43 | sock.disconnect(); 44 | } 45 | { 46 | auto sock = listen.accept(); 47 | sock.send_all("pong", 4); 48 | 49 | char buffer[1024]; 50 | auto len = sock.recv(buffer, sizeof(buffer)); 51 | BOOST_CHECK_EQUAL(4, len); 52 | buffer[4] = 0; 53 | BOOST_CHECK_EQUAL("ping", buffer); 54 | 55 | sock.disconnect(); 56 | } 57 | } 58 | BOOST_AUTO_TEST_CASE(success) 59 | { 60 | Event event; 61 | TestThread server([&event]{ success_server_thread(event); }); 62 | { 63 | std::unique_lock lock(event.mutex); 64 | event.cv.wait(lock, [&event]{ return event.ready; }); 65 | } 66 | { 67 | TlsSocket sock("localhost", BASE_PORT); 68 | sock.send_all("ping", 4); 69 | 70 | char buffer[1024]; 71 | auto len = sock.recv(buffer, sizeof(buffer)); 72 | BOOST_CHECK_EQUAL(4, len); 73 | buffer[4] = 0; 74 | BOOST_CHECK_EQUAL("pong", buffer); 75 | } 76 | { 77 | TlsSocket sock("localhost", BASE_PORT); 78 | char buffer[1024]; 79 | auto len = sock.recv(buffer, sizeof(buffer)); 80 | BOOST_CHECK_EQUAL(4, len); 81 | buffer[4] = 0; 82 | BOOST_CHECK_EQUAL("pong", buffer); 83 | 84 | sock.send_all("ping", 4); 85 | sock.disconnect(); 86 | } 87 | server.join(); 88 | } 89 | 90 | void invalid_cert_hostname_server() 91 | { 92 | TlsListenSocket listen("127.0.0.1", BASE_PORT + 1, load_pfx_cert("wrong-host.pfx", "password")); 93 | try 94 | { 95 | char buffer[1]; 96 | auto recved = listen.accept().recv(buffer, 1); 97 | BOOST_CHECK_EQUAL(0, recved); 98 | } catch(const std::exception &) {} 99 | } 100 | BOOST_AUTO_TEST_CASE(invalid_cert_hostname) 101 | { 102 | TestThread server(&invalid_cert_hostname_server); 103 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 104 | BOOST_CHECK_THROW(TlsSocket("localhost", BASE_PORT + 1).send_all("ping", 4), CertificateVerificationError); 105 | server.join(); 106 | } 107 | 108 | BOOST_AUTO_TEST_SUITE_END() 109 | -------------------------------------------------------------------------------- /include/http/Error.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "Method.hpp" 6 | 7 | namespace http 8 | { 9 | /**@brief Errors while parsing HTTP messages. 10 | * If status_code is non-zero, it is a suggested HTTP error status code to send if parsing a 11 | * request, such as an overlong URI or unsupported method. 12 | */ 13 | class ParserError : public std::runtime_error 14 | { 15 | public: 16 | ParserError(const std::string &msg, int status_code = 0) 17 | : std::runtime_error(msg), _status_code(status_code) 18 | {} 19 | 20 | /**The suggested HTTP response status code.*/ 21 | int status_code()const { return _status_code; } 22 | protected: 23 | int _status_code; 24 | }; 25 | 26 | /**Represents a non-success HTTP response.*/ 27 | class ErrorResponse : public std::runtime_error 28 | { 29 | public: 30 | /**HTTP error response with a message and status code.*/ 31 | ErrorResponse(const std::string &msg, int status_code) 32 | : std::runtime_error(msg), _status_code(status_code) 33 | {} 34 | /**The HTTP status code for the response.*/ 35 | int status_code()const { return _status_code; } 36 | private: 37 | int _status_code; 38 | }; 39 | 40 | /**4xx error.*/ 41 | class ClientErrorResponse : public ErrorResponse 42 | { 43 | public: 44 | using ErrorResponse::ErrorResponse; 45 | }; 46 | 47 | /**400 Bad Request.*/ 48 | class BadRequest : public ClientErrorResponse 49 | { 50 | public: 51 | /**Construct with a specified message body describing the error.*/ 52 | explicit BadRequest(const std::string &message) 53 | : ClientErrorResponse(message, 400) 54 | {} 55 | }; 56 | /**404 Not Found.*/ 57 | class NotFound : public ClientErrorResponse 58 | { 59 | public: 60 | /**Construct a generic message for a path.*/ 61 | explicit NotFound(const std::string &path) 62 | : ClientErrorResponse("Not Found " + path, 404) 63 | {} 64 | }; 65 | /**405 Method Not Allowed.*/ 66 | class MethodNotAllowed : public ClientErrorResponse 67 | { 68 | public: 69 | /**Construct a generic message for a method and path.*/ 70 | MethodNotAllowed(const std::string &method, const std::string &path) 71 | : ClientErrorResponse(method + " not allowed for " + path, 405) 72 | {} 73 | /**Construct a generic message for a method and path.*/ 74 | MethodNotAllowed(Method method, const std::string &path) 75 | : MethodNotAllowed(std::to_string(method), path) 76 | {} 77 | }; 78 | /**406 Not Acceptable.*/ 79 | class NotAcceptable : public ClientErrorResponse 80 | { 81 | public: 82 | /**Construct a "Not Acceptable" response with a list of content types that are acceptable.*/ 83 | NotAcceptable(const std::vector &accepts) 84 | : ClientErrorResponse("No acceptable content type", 406) 85 | , _accepts(accepts) 86 | {} 87 | /**Get the list of content types that would be accepted.*/ 88 | const std::vector& accepts()const { return _accepts; } 89 | private: 90 | std::vector _accepts; 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | u/YdYa463HoGOYWeRkbw3B19KeIxmNM= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /wrong-host.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | Lg2+P8602vfJFm/X0D+16NFfppW4xGs= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /tests/server/Router.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "server/Router.hpp" 3 | #include "Error.hpp" 4 | #include "Response.hpp" 5 | using namespace http; 6 | 7 | BOOST_AUTO_TEST_SUITE(TestRouter) 8 | 9 | Response handler(Request &, PathParams &) 10 | { 11 | throw std::runtime_error("Not implemented"); 12 | } 13 | BOOST_AUTO_TEST_CASE(simple) 14 | { 15 | Router router; 16 | 17 | BOOST_CHECK_NO_THROW(router.add("GET", "/", handler)); 18 | BOOST_CHECK_NO_THROW(router.add("GET", "/index.html", handler)); 19 | BOOST_CHECK_NO_THROW(router.add("GET", "/login", handler)); 20 | 21 | BOOST_CHECK_NO_THROW(router.add("GET", "/login/", handler)); 22 | BOOST_CHECK_NO_THROW(router.add("POST", "/login", handler)); 23 | 24 | BOOST_CHECK(router.get("GET", "/index.html").handler); 25 | BOOST_CHECK(router.get("GET", "/").handler); 26 | BOOST_CHECK(router.get("GET", "/login").handler); 27 | BOOST_CHECK(router.get("GET", "/login/").handler); 28 | BOOST_CHECK(router.get("POST", "/login").handler); 29 | // Extra slash not significant 30 | BOOST_CHECK(router.get("GET", "//index.html").handler); 31 | 32 | // Found path, but not method 33 | BOOST_CHECK_THROW(router.get("POST", "/index.html"), MethodNotAllowed); 34 | // Not found 35 | BOOST_CHECK(!router.get("GET", "/not_found")); 36 | BOOST_CHECK(!router.get("POST", "/not_found")); 37 | // Is case sensitive 38 | BOOST_CHECK(!router.get("GET", "/Index.html")); 39 | 40 | // Duplicate not allowed 41 | BOOST_CHECK_THROW(router.add("GET", "/index.html", handler), InvalidRouteError); 42 | // Invalid 43 | BOOST_CHECK_THROW(router.add("GET", "", handler), UrlError); 44 | } 45 | 46 | BOOST_AUTO_TEST_CASE(prefix) 47 | { 48 | Router router; 49 | BOOST_CHECK_NO_THROW(router.add("GET", "/assets/*", handler)); 50 | 51 | BOOST_CHECK(router.get("GET", "/assets/").handler); 52 | BOOST_CHECK(router.get("GET", "/assets/application.js").handler); 53 | BOOST_CHECK(router.get("GET", "/assets/dark/application.css").handler); 54 | 55 | BOOST_CHECK_THROW(router.get("POST", "/assets/hack.js"), MethodNotAllowed); 56 | 57 | BOOST_CHECK(!router.get("GET", "/index")); 58 | 59 | BOOST_CHECK_THROW(router.add("GET", "/assets/other", handler), InvalidRouteError); 60 | BOOST_CHECK_NO_THROW(router.add("GET", "/other/child", handler)); 61 | BOOST_CHECK_THROW(router.add("GET", "/other/*", handler), InvalidRouteError); 62 | } 63 | 64 | BOOST_AUTO_TEST_CASE(path_params) 65 | { 66 | Router router; 67 | BOOST_CHECK_NO_THROW(router.add("GET", "/forums/:forum_name", handler)); 68 | BOOST_CHECK_NO_THROW(router.add("GET", "/forums/:forum_name/post", handler)); 69 | BOOST_CHECK_NO_THROW(router.add("GET", "/forums/:forum_name/topics/:topic_id", handler)); 70 | BOOST_CHECK_NO_THROW(router.add("GET", "/forums/:forum_name/topics/:topic_id/post", handler)); 71 | 72 | auto matched = router.get("GET", "/forums/General"); 73 | BOOST_CHECK(matched); 74 | BOOST_CHECK_EQUAL("General", matched.path_params["forum_name"]); 75 | 76 | matched = router.get("GET", "/forums/General/topics/567/post"); 77 | BOOST_CHECK(matched); 78 | BOOST_CHECK_EQUAL("General", matched.path_params["forum_name"]); 79 | BOOST_CHECK_EQUAL("567", matched.path_params["topic_id"]); 80 | 81 | 82 | BOOST_CHECK_THROW(router.add("GET", "/forums/:forum_name2/unread", handler), InvalidRouteError); 83 | } 84 | 85 | BOOST_AUTO_TEST_SUITE_END() 86 | -------------------------------------------------------------------------------- /include/http/headers/Accept.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "Error.hpp" 5 | namespace http 6 | { 7 | /**Represents a HTTP Accept request header. */ 8 | class Accept 9 | { 10 | public: 11 | /**An acceptable content type.*/ 12 | struct Type 13 | { 14 | /**Mime content type, e.g. "text" or "application". 15 | * May be '*' for match all if subtype is also '*'. 16 | */ 17 | std::string type; 18 | /**Mime content sub-type, e.g. "png" or "html". 19 | * May be '*' for match all. 20 | */ 21 | std::string subtype; 22 | /**Quality level. If there is multiple possible response content types, then one with a 23 | * higher quality according to the requests Accept header is preferred. 24 | */ 25 | float quality; 26 | 27 | /**True if a given content type and subtype match this, taking account of wildcard accepts.*/ 28 | bool matches(const std::string &_type, const std::string &_subtype)const 29 | { 30 | if (type == "*") return true; 31 | if (type != _type) return false; 32 | if (subtype == "*") return true; 33 | return this->subtype == _subtype; 34 | } 35 | /**Return true if other is a better quality match than this. 36 | * - First highest quality if preferred. 37 | * - Then if this is ∗/∗ and other is x/∗ or x/y, other is preferred. 38 | * - Then if this is x/∗ and other is x/y, other is preferred. 39 | * - Else this is preferred. 40 | */ 41 | bool better_quality(const Type &other)const 42 | { 43 | if (other.quality > quality) return true; 44 | if (other.quality < quality) return false; 45 | 46 | if (type == "*" && other.type != "*") return true; 47 | if (subtype == "*" && other.subtype != "*") return true; 48 | 49 | return false; 50 | } 51 | }; 52 | 53 | Accept() : types() {} 54 | /**Constructor from Accept header value.*/ 55 | explicit Accept(const std::string &value) 56 | : Accept(value.c_str(), value.c_str() + value.size()) 57 | {} 58 | /**Constructor from Accept header value.*/ 59 | Accept(const char *begin, const char *end); 60 | 61 | /**Return true if content_type is accepted for any quality.*/ 62 | bool accepts(const std::string &content_type)const 63 | { 64 | return find(content_type) != nullptr; 65 | } 66 | /**@throws NotAcceptable if the type is not accept.*/ 67 | void check_accepts(const std::string &content_type)const 68 | { 69 | if (!accepts(content_type)) throw NotAcceptable({ content_type }); 70 | } 71 | 72 | /**Return which of the list of types is most preferred. 73 | * E.g. to select between JSON and XML. 74 | * @throws NotAcceptable if none of the types are accepted. 75 | */ 76 | std::string preferred(const std::vector &types)const; 77 | 78 | /**Gets the list of acceptable content types.*/ 79 | const std::vector& accepts()const { return types; } 80 | private: 81 | std::vector types; 82 | 83 | const Type *find(const std::string &type)const; 84 | void parse(const char *begin, const char *end); 85 | }; 86 | } 87 | 88 | -------------------------------------------------------------------------------- /include/http/core/Writer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Request.hpp" 3 | #include "Response.hpp" 4 | #include "Headers.hpp" 5 | #include "Method.hpp" 6 | #include "Time.hpp" 7 | #include "net/Socket.hpp" 8 | #include 9 | #include 10 | 11 | namespace http 12 | { 13 | /**Writes the header text to the output stream.*/ 14 | inline void write_headers(std::ostream &os, const Headers &headers) 15 | { 16 | for (auto &header : headers) 17 | { 18 | os << header.first << ": " << header.second << "\r\n"; 19 | } 20 | os << "\r\n"; 21 | } 22 | /**Writes the HTTP request first line and headers to the output stream.*/ 23 | inline void write_request_header(std::ostream &os, const Request &request) 24 | { 25 | os << to_string(request.method) << " "; 26 | if (!request.raw_url.empty()) os << request.raw_url; 27 | else request.url.encode_request(os); 28 | os << " HTTP/1.1\r\n"; 29 | write_headers(os, request.headers); 30 | } 31 | /**Writes the HTTP response first line and headers to the output stream.*/ 32 | inline void write_response_header(std::ostream &os, const Response &response) 33 | { 34 | os << "HTTP/1.1 " << response.status.code << " " << response.status.msg << "\r\n"; 35 | write_headers(os, response.headers); 36 | } 37 | /**Adds basic default headers to a request or response to be sent. 38 | * Currently this is just the "Date" header. 39 | */ 40 | template void add_default_headers(T &message) 41 | { 42 | Headers &headers = message.headers; 43 | headers.set("Date", format_time(time(nullptr))); 44 | } 45 | 46 | /**Sends a HTTP client side request to the socket using Socket::send_all.*/ 47 | inline void send_request(Socket *socket, Request &request) 48 | { 49 | if (!request.body.empty()) 50 | request.headers.set("Content-Length", std::to_string(request.body.size())); 51 | std::stringstream ss; 52 | write_request_header(ss, request); 53 | auto ss_str = ss.str(); 54 | socket->send_all(ss_str.data(), ss_str.size()); 55 | if (!request.body.empty()) socket->send_all(request.body.data(), request.body.size()); 56 | } 57 | 58 | /**Sends a HTTP server side response to the socket using Socket::send_all.*/ 59 | inline void send_response(Socket *socket, const std::string &req_method, Response &response) 60 | { 61 | auto sc = response.status.code; 62 | //For certain response codes, there must not be a message body 63 | bool message_body_allowed = sc != 204 && sc != 205 && sc != 304; 64 | 65 | //For HEAD requests, Content-Length etc. should be determined, but the body must not be sent 66 | bool send_message_body = message_body_allowed && req_method != "HEAD"; 67 | 68 | if (message_body_allowed) 69 | { 70 | //TODO: Support chunked streams in the future 71 | response.headers.set("Content-Length", std::to_string(response.body.size())); 72 | } 73 | else if (!response.body.empty()) 74 | { 75 | throw std::runtime_error("HTTP forbids this response from having a body"); 76 | } 77 | 78 | add_default_headers(response); 79 | std::stringstream ss; 80 | write_response_header(ss, response); 81 | auto ss_str = ss.str(); 82 | socket->send_all(ss_str.data(), ss_str.size()); 83 | 84 | if (send_message_body && !response.body.empty()) 85 | { 86 | socket->send_all(response.body.data(), response.body.size()); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /include/http/net/OpenSslSocket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Net.hpp" 3 | #include "Socket.hpp" 4 | #include "TcpSocket.hpp" 5 | #include "OpenSsl.hpp" 6 | #include 7 | 8 | namespace http 9 | { 10 | class PrivateCert; 11 | 12 | /**TLS secure socket using OpenSSL.*/ 13 | class OpenSslSocket : public Socket 14 | { 15 | public: 16 | OpenSslSocket(); 17 | /**Establish a client connection to a specific host and port.*/ 18 | OpenSslSocket(const std::string &host, uint16_t port); 19 | OpenSslSocket(const OpenSslSocket&)=delete; 20 | OpenSslSocket(OpenSslSocket &&mv)=default; 21 | virtual ~OpenSslSocket(); 22 | OpenSslSocket& operator = (const OpenSslSocket&)=delete; 23 | OpenSslSocket& operator = (OpenSslSocket &&mv)=default; 24 | 25 | virtual SOCKET get()override { return tcp.get(); } 26 | /**Establish a client connection to a specific host and port.*/ 27 | void connect(const std::string &host, uint16_t port); 28 | 29 | virtual std::string address_str()const override; 30 | virtual void close()override; 31 | virtual void disconnect()override; 32 | virtual bool recv_pending()const override; 33 | virtual size_t recv(void *buffer, size_t len)override; 34 | virtual size_t send(const void *buffer, size_t len)override; 35 | virtual bool check_recv_disconnect()override; 36 | 37 | virtual void async_disconnect(AsyncIo &aio, 38 | std::function handler, AsyncIo::ErrorHandler error)override; 39 | virtual void async_recv(AsyncIo &aio, void *buffer, size_t len, 40 | AsyncIo::RecvHandler handler, AsyncIo::ErrorHandler error)override; 41 | virtual void async_send(AsyncIo &aio, const void *buffer, size_t len, 42 | AsyncIo::SendHandler handler, AsyncIo::ErrorHandler error)override; 43 | virtual void async_send_all(AsyncIo &aio, const void *buffer, size_t len, 44 | AsyncIo::SendHandler handler, AsyncIo::ErrorHandler error)override; 45 | protected: 46 | TcpSocket tcp; 47 | std::unique_ptr ssl; 48 | /**Received encrpyted data ready for OpenSSL. Owned by ssl.*/ 49 | BIO *in_bio; 50 | /**Outgoing encrypted data ready to send to the remote. Owned by ssl.*/ 51 | BIO *out_bio; 52 | char bio_buffer[4096]; 53 | 54 | void async_send_next(AsyncIo &aio, const void *buffer, size_t len, size_t sent, 55 | AsyncIo::SendHandler handler, AsyncIo::ErrorHandler error); 56 | void async_send_bio(AsyncIo &aio, std::function handler, AsyncIo::ErrorHandler error); 57 | }; 58 | /**Server side OpenSSL socket. Presents a certificate on connection.*/ 59 | class OpenSslServerSocket : public OpenSslSocket 60 | { 61 | public: 62 | OpenSslServerSocket() : OpenSslSocket() {} 63 | OpenSslServerSocket(TcpSocket &&socket, const PrivateCert &cert); 64 | OpenSslServerSocket(const OpenSslServerSocket&)=delete; 65 | OpenSslServerSocket(OpenSslServerSocket&&)=default; 66 | OpenSslServerSocket& operator =(const OpenSslServerSocket&)=delete; 67 | OpenSslServerSocket& operator =(OpenSslServerSocket&&)=default; 68 | 69 | void async_create(AsyncIo &aio, TcpSocket &&socket, const PrivateCert &cert, 70 | std::function handler, AsyncIo::ErrorHandler error); 71 | private: 72 | std::unique_ptr openssl_ctx; 73 | 74 | void setup(TcpSocket &&socket, const PrivateCert &cert); 75 | void async_create_next(AsyncIo &aio, std::function handler, AsyncIo::ErrorHandler error); 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /include/http/Headers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | namespace http 5 | { 6 | /**Simple representation of the HTTP Content-Type header.*/ 7 | struct ContentType 8 | { 9 | /**Mime type for the content type.*/ 10 | std::string mime; 11 | /**Charset for the content type.*/ 12 | std::string charset; 13 | }; 14 | 15 | /**Container of HTTP request or response headers.*/ 16 | class Headers 17 | { 18 | public: 19 | typedef std::unordered_map Container; 20 | /**An unordered forward iterator.*/ 21 | typedef Container::iterator iterator; 22 | /**An unordered forward iterator.*/ 23 | typedef Container::const_iterator const_iterator; 24 | 25 | /**Begin iterator.*/ 26 | iterator begin() { return data.begin(); } 27 | /**Begin iterator.*/ 28 | const_iterator begin()const { return data.begin(); } 29 | /**Begin iterator.*/ 30 | const_iterator cbegin()const { return data.cbegin(); } 31 | /**End iterator.*/ 32 | iterator end() { return data.end(); } 33 | /**End iterator.*/ 34 | const_iterator end()const { return data.end(); } 35 | /**End iterator.*/ 36 | const_iterator cend()const { return data.cend(); } 37 | 38 | /**Deletes all headers.*/ 39 | void clear() { data.clear(); } 40 | 41 | /**Add a new header that is known to not already exist.*/ 42 | void add(const std::string &key, const std::string &value) 43 | { 44 | //TODO: Deal with multiple headers with same name 45 | data[key] = value; 46 | } 47 | void set(const std::string &key, const std::string &value) 48 | { 49 | //TODO: Error if already have multiple headers with same name 50 | data[key] = value; 51 | } 52 | /**Set, if not already set.*/ 53 | void set_default(const std::string &key, const std::string &value) 54 | { 55 | auto &v = data[key]; 56 | if (v.empty()) v = value; 57 | } 58 | 59 | /**True if there is a header by this name.*/ 60 | bool has(const std::string &key)const 61 | { 62 | return data.count(key) > 0; 63 | } 64 | /**Get the value of a header. An empty string is returned if the header is not present.*/ 65 | const std::string& get(const std::string &key)const; 66 | /**Find a header as an iterator.*/ 67 | const_iterator find(const std::string &key)const 68 | { 69 | return data.find(key); 70 | } 71 | 72 | /**Get the Content-Type header.*/ 73 | ContentType content_type()const; 74 | /**Set the Content-Type header.*/ 75 | void content_type(const std::string &mime, const std::string &charset = std::string()) 76 | { 77 | if (charset.empty()) add("Content-Type", mime); 78 | else add("Content-Type", mime + "; charset=" + charset); 79 | } 80 | /**Set the Content-Type header.*/ 81 | void content_type(const ContentType &type) 82 | { 83 | content_type(type.mime, type.charset); 84 | } 85 | 86 | /**Remove a header if it is present.*/ 87 | void remove(const std::string &key) 88 | { 89 | data.erase(key); 90 | } 91 | 92 | /**Get the number of headers.*/ 93 | size_t size()const { return data.size(); } 94 | private: 95 | Container data; 96 | }; 97 | typedef Headers ResponseHeaders; 98 | typedef Headers RequestHeaders; 99 | } 100 | -------------------------------------------------------------------------------- /source/Status.cpp: -------------------------------------------------------------------------------- 1 | #include "Status.hpp" 2 | namespace http 3 | { 4 | std::string default_status_msg(StatusCode sc) 5 | { 6 | switch (sc) 7 | { 8 | case SC_CONTINUE: return "Continue"; 9 | case SC_SWITCHING_PROTOCOLS: return "Switching Protocols"; 10 | case SC_PROCESSING: return "Processing"; 11 | 12 | case SC_OK: return "OK"; 13 | case SC_CREATED: return "Created"; 14 | case SC_ACCEPTED: return "Accepted"; 15 | case SC_NON_AUTHORITATIVE_INFORMATION: return "Non-Authoritative Information"; 16 | case SC_NO_CONTENT: return "No Content"; 17 | case SC_RESET_CONTENT: return "Reset Content"; 18 | case SC_PARTIAL_CONTENT: return "Partial Content"; 19 | case SC_MULTI_STATUS: return "Multi-Status"; 20 | case SC_ALREADY_REPORTED: return "Already Reported"; 21 | case SC_IM_USED: return "IM Used"; 22 | 23 | case SC_MULTIPLE_CHOICES: return "Multiple Choices"; 24 | case SC_MOVED_PERMANENTLY: return "Moved Permanently"; 25 | case SC_FOUND: return "Found"; 26 | case SC_SEE_OTHER: return "See Other"; 27 | case SC_NOT_MODIFIED: return "Not Modified"; 28 | case SC_USE_PROXY: return "Use Proxy"; 29 | case SC_TEMPORARY_REDIRECT: return "Temporary Redirect"; 30 | case SC_PERMANENT_REDIRECT: return "Permanent Redirect"; 31 | 32 | case SC_BAD_REQUEST: return "Bad Request"; 33 | case SC_UNAUTHORIZED: return "Unauthorized"; 34 | case SC_PAYMENT_REQUIRED: return "Payment Required"; 35 | case SC_FORBIDDEN: return "Forbidden"; 36 | case SC_NOT_FOUND: return "Not Found"; 37 | case SC_METHOD_NOT_ALLOWED: return "Method Not Allowed"; 38 | case SC_NOT_ACCEPTABLE: return "Not Acceptable"; 39 | case SC_PROXY_AUTHENTICATION_REQUIRED: return "Proxy Authentication Required"; 40 | case SC_REQUEST_TIMEOUT: return "Request Timeout"; 41 | case SC_CONFIICT: return "Conflict"; 42 | case SC_GONE: return "Gone"; 43 | case SC_LENGTH_REQUIRED: return "Length Required"; 44 | case SC_PRECONDITION_FAILED: return "Precondition Failed"; 45 | case SC_PAYLOAD_TOO_LARGE: return "Payload Too Large"; 46 | case SC_URI_TOO_LONG: return "URI Too Long"; 47 | case SC_UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type"; 48 | case SC_RANGE_NOT_SATISFIABLE: return "Range Not Satisfiable"; 49 | case SC_EXPECTATION_FAILED: return "Expectation Failed"; 50 | case SC_MISDIRECTED_REQUEST: return "Misdirected Request"; 51 | case SC_UNPROCESSABLE_ENTITY: return "Unprocessable Entity"; 52 | case SC_LOCKED: return "Locked"; 53 | case SC_FAILED_DEPENDENCY: return "Failed Dependency"; 54 | case SC_UPGRADE_REQUIRED: return "Upgrade Required"; 55 | case SC_PRECONDITION_REQUIRED: return "Precondition Required"; 56 | case SC_TOO_MANY_REQUESTS: return "Too Many Requests"; 57 | case SC_REQUEST_HEADER_FIELDS_TOO_LARGE: return "Request Header Fields Too Large"; 58 | case SC_UNAVAILABLE_FOR_LEGAL_REASONS: return "Unavailable For Legal Reasons"; 59 | 60 | case SC_INTERNAL_SERVER_ERROR: return "Internal Server Error"; 61 | case SC_NOT_IMPLEMENTED: return "Not Implemented"; 62 | case SC_BAD_GATEWAY: return "Bad Gateway"; 63 | case SC_SERVICE_UNAVAILABLE: return "Service Unavailable"; 64 | case SC_GATEWAY_TIMEOUT: return "Gateway Timeout"; 65 | case SC_HTTP_VERSION_NOT_SUPPORTED: return "HTTP Version Not Supported"; 66 | case SC_VARIANT_ALSO_NEGOTIATES: return "Variant Also Negotiates"; 67 | case SC_INSUFFICIENT_STORAGE: return "Insufficient Storage"; 68 | case SC_LOOP_DETECTED: return "Loop Detected"; 69 | case SC_NOT_EXTENDED: return "Not Extended"; 70 | case SC_NETWORK_AUTHENTICATION_REQUIRED: return "Network Authentication Required"; 71 | 72 | default: return "Unknown"; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /source/server/Router.cpp: -------------------------------------------------------------------------------- 1 | #include "server/Router.hpp" 2 | #include "Request.hpp" 3 | #include "Response.hpp" 4 | #include "Status.hpp" 5 | #include "Error.hpp" 6 | #include "Url.hpp" 7 | 8 | namespace http 9 | { 10 | Router::PathParts Router::get_parts(const std::string &path)const 11 | { 12 | if (path.empty() || path[0] != '/') throw UrlError(path, "Expect URL path to begin with '/'"); 13 | 14 | PathParts parts; 15 | for(size_t i = 1, next = 1; next < path.size(); i = next + 1) 16 | { 17 | next = path.find('/', i); 18 | if (next == i) continue; // Remove adjacent '/' 19 | else parts.push_back(url_decode(path.substr(i, next - i))); 20 | } 21 | return parts; 22 | } 23 | 24 | MatchedRoute Router::get(const std::string &method, const std::string &path)const 25 | { 26 | auto parts = get_parts(path); 27 | // Walk path segment tree 28 | MatchedRoute route; 29 | auto node = &root; 30 | for (auto i = parts.begin(); i != parts.end(); ++i) 31 | { 32 | // If found a prefix node that handles all (e.g. /assets/*), finish 33 | if (node->prefix) break; 34 | // Named segments take priority over path parameters 35 | auto child = node->children.find(*i); 36 | if (child != node->children.end()) 37 | { 38 | node = child->second.get(); 39 | } 40 | else if (node->param) 41 | { 42 | route.path_params[node->param.name] = *i; 43 | node = node->param.node.get(); 44 | } 45 | else return MatchedRoute(); // Not found 46 | } 47 | // Find method 48 | auto handler = node->methods.find(method); 49 | if (handler == node->methods.end()) throw MethodNotAllowed(method, path); 50 | route.handler = handler->second; 51 | 52 | return route; 53 | } 54 | 55 | void Router::add(const std::string &method, const std::string &path, RequestHandler handler) 56 | { 57 | auto parts = get_parts(path); 58 | bool prefix; 59 | if (!parts.empty() && parts.back() == "*") 60 | { 61 | parts.pop_back(); 62 | prefix = true; 63 | } 64 | else prefix = false; 65 | // Walk path segment tree 66 | auto node = &root; 67 | for (auto i = parts.begin(); i != parts.end(); ++i) 68 | { 69 | // If found a prefix node that handles all, and this is not a prefix part ('*' at end), its invalid 70 | if (node->prefix) 71 | { 72 | if (prefix) break; 73 | else throw InvalidRouteError(method, path, "Path already used as a prefix"); 74 | } 75 | bool param = !i->empty() && i->front() == ':'; 76 | if (param) 77 | { 78 | auto param_name = i->substr(1); 79 | if (node->param && node->param.name != param_name) 80 | throw InvalidRouteError(method, path, "Differing route parameter names for :" + param_name); 81 | if (!node->param) 82 | { 83 | node->param.name = param_name; 84 | node->param.node.reset(new Node()); 85 | } 86 | node = node->param.node.get(); 87 | } 88 | else // Path segment 89 | { 90 | auto &p = node->children[*i]; 91 | if (!p) p.reset(new Node()); 92 | node = p.get(); 93 | } 94 | } 95 | // Make node a prefix if needed 96 | if (prefix) 97 | { 98 | if (!node->children.empty() || node->param) 99 | throw InvalidRouteError(method, path, "Cant add as prefix because already has children"); 100 | node->prefix = true; 101 | } 102 | // Add the method handler to node 103 | if (!node->methods.emplace(method, handler).second) 104 | { 105 | throw InvalidRouteError(method, path, "Route already exists"); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/server/CoreServer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "client/Client.hpp" 3 | #include "client/SocketFactory.hpp" 4 | #include "server/CoreServer.hpp" 5 | #include "net/Cert.hpp" 6 | #include "net/Net.hpp" 7 | #include "net/TcpSocket.hpp" 8 | #include "Response.hpp" 9 | #include "../TestThread.hpp" 10 | #include 11 | 12 | using namespace http; 13 | 14 | BOOST_AUTO_TEST_SUITE(TestCoreServer) 15 | 16 | static const uint16_t BASE_PORT = 5100; 17 | 18 | class Server : public http::CoreServer 19 | { 20 | protected: 21 | virtual http::Response handle_request(http::Request &)override 22 | { 23 | http::Response resp; 24 | resp.status_code(200); 25 | resp.headers.add("Content-Type", "text/plain"); 26 | resp.body = "OK"; 27 | return resp; 28 | } 29 | virtual http::Response parser_error_page(const http::ParserError &)override 30 | { 31 | throw std::runtime_error("Unexpected parser_error_page"); 32 | } 33 | }; 34 | 35 | void success_server_thread(Server *server) 36 | { 37 | server->run(); 38 | } 39 | BOOST_AUTO_TEST_CASE(success) 40 | { 41 | TestThread server_thread; 42 | Server server; 43 | server.add_tcp_listener("127.0.0.1", BASE_PORT + 0); 44 | server.add_tls_listener("127.0.0.1", BASE_PORT + 1, load_pfx_cert("localhost.pfx", "password")); 45 | server.add_tls_listener("127.0.0.1", BASE_PORT + 2, load_pfx_cert("wrong-host.pfx", "password")); 46 | 47 | server_thread = TestThread(std::bind(&Server::run, &server)); 48 | 49 | http::DefaultSocketFactory socket_factory; 50 | 51 | { 52 | // http:// 53 | http::Request req; 54 | req.method = GET; 55 | req.headers.add("Host", "localhost"); 56 | req.raw_url = "/index.html"; 57 | 58 | auto resp = http::Client("localhost", BASE_PORT + 0, false, &socket_factory).make_request(req); 59 | 60 | BOOST_CHECK_EQUAL(200, resp.status.code); 61 | BOOST_CHECK_EQUAL("OK", resp.body); 62 | } 63 | { 64 | // https:// - TLS 65 | http::Request req; 66 | req.method = GET; 67 | req.headers.add("Host", "localhost"); 68 | req.raw_url = "/index.html"; 69 | 70 | auto resp = http::Client("localhost", BASE_PORT + 1, true, &socket_factory).make_request(req); 71 | 72 | BOOST_CHECK_EQUAL(200, resp.status.code); 73 | BOOST_CHECK_EQUAL("OK", resp.body); 74 | } 75 | { 76 | // Expected to fail since cant validate the certificate 77 | http::Request req; 78 | req.method = GET; 79 | req.headers.add("Host", "localhost"); 80 | req.raw_url = "/index.html"; 81 | BOOST_CHECK_THROW( 82 | http::Client("localhost", BASE_PORT + 2, true, &socket_factory).make_request(req), 83 | CertificateVerificationError); 84 | } 85 | server.exit(); 86 | server_thread.join(); 87 | } 88 | 89 | BOOST_AUTO_TEST_CASE(keep_alive) 90 | { 91 | TestThread server_thread; 92 | Server server; 93 | server.add_tcp_listener("127.0.0.1", BASE_PORT + 3); 94 | 95 | server_thread = TestThread(std::bind(&Server::run, &server)); 96 | 97 | { 98 | ClientConnection conn(std::unique_ptr(new TcpSocket("localhost", BASE_PORT + 3))); 99 | Request req; 100 | req.method = GET; 101 | req.headers.add("Host", "localhost"); 102 | req.headers.add("Connection", "close"); 103 | req.raw_url = "/index.html"; 104 | 105 | auto resp = conn.make_request(req); 106 | BOOST_CHECK_EQUAL("close", resp.headers.get("Connection")); 107 | BOOST_CHECK(!conn.is_connected()); 108 | } 109 | 110 | { 111 | ClientConnection conn(std::unique_ptr(new TcpSocket("localhost", BASE_PORT + 3))); 112 | Request req; 113 | req.method = GET; 114 | req.headers.add("Host", "localhost"); 115 | req.headers.add("Connection", "keep-alive"); 116 | req.raw_url = "/index.html"; 117 | 118 | auto resp = conn.make_request(req); 119 | BOOST_CHECK_EQUAL("keep-alive", resp.headers.get("Connection")); 120 | BOOST_CHECK(conn.is_connected()); 121 | BOOST_CHECK_NO_THROW(conn.make_request(req)); 122 | } 123 | 124 | server.exit(); 125 | server_thread.join(); 126 | } 127 | BOOST_AUTO_TEST_SUITE_END() 128 | -------------------------------------------------------------------------------- /tests/Url.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Url.hpp" 3 | 4 | 5 | BOOST_AUTO_TEST_SUITE(TestUrl) 6 | BOOST_AUTO_TEST_CASE(url_decode) 7 | { 8 | BOOST_CHECK_EQUAL("test", http::url_decode("test")); 9 | BOOST_CHECK_EQUAL("test test", http::url_decode("test%20test")); 10 | BOOST_CHECK_EQUAL("test test test", http::url_decode("test%20test%20test")); 11 | BOOST_CHECK_EQUAL("test+test", http::url_decode("test+test")); 12 | BOOST_CHECK_EQUAL("test ", http::url_decode("test%20")); 13 | BOOST_CHECK_EQUAL(" test", http::url_decode("%20test")); 14 | BOOST_CHECK_EQUAL("test%2", http::url_decode("test%2")); 15 | 16 | BOOST_CHECK_EQUAL("test test", http::url_decode_query("test+test")); 17 | 18 | BOOST_CHECK_THROW(http::url_decode("%XX"), std::runtime_error); 19 | } 20 | 21 | BOOST_AUTO_TEST_CASE(url_parse_request) 22 | { 23 | http::Url x; 24 | 25 | x = http::Url::parse_request("/index.html"); 26 | BOOST_CHECK_EQUAL("/index.html", x.path); 27 | BOOST_CHECK(x.query_params.empty()); 28 | 29 | 30 | x = http::Url::parse_request("/shopping%20list.html"); 31 | BOOST_CHECK_EQUAL("/shopping list.html", x.path); 32 | 33 | x = http::Url::parse_request("/index.html?key=value"); 34 | BOOST_CHECK_EQUAL("/index.html", x.path); 35 | BOOST_CHECK_EQUAL("value", x.query_param("key")); 36 | BOOST_CHECK_EQUAL("", x.query_param("not")); 37 | BOOST_CHECK_EQUAL(1, x.query_param_list("key").size()); 38 | BOOST_CHECK_EQUAL(0, x.query_param_list("not").size()); 39 | BOOST_CHECK_EQUAL("value", *x.query_param_list("key").begin()); 40 | 41 | 42 | x = http::Url::parse_request("/index.html?key=value%3D5"); 43 | BOOST_CHECK_EQUAL("value=5", x.query_param("key")); 44 | 45 | x = http::Url::parse_request("/index.html?key=value&key2=val2"); 46 | BOOST_CHECK_EQUAL("value", x.query_param("key")); 47 | BOOST_CHECK_EQUAL("val2", x.query_param("key2")); 48 | 49 | x = http::Url::parse_request("/index.html?key&key2=val2"); 50 | BOOST_CHECK(x.has_query_param("key")); 51 | BOOST_CHECK_EQUAL("", x.query_param("key")); 52 | BOOST_CHECK_EQUAL("val2", x.query_param("key2")); 53 | 54 | x = http::Url::parse_request("/index.html?key%5B%5D=a&key%5B%5D=b&key%5B%5D=c"); 55 | BOOST_CHECK_EQUAL("a", x.query_param("key[]")); 56 | auto y = x.query_param_list("key[]"); 57 | BOOST_CHECK_EQUAL(3, y.size()); 58 | std::string values[] = {"a", "b", "c"}; 59 | BOOST_CHECK_EQUAL_COLLECTIONS(y.begin(), y.end(), values, values + 3); 60 | } 61 | 62 | 63 | 64 | BOOST_AUTO_TEST_CASE(url_encode) 65 | { 66 | http::Url url; 67 | 68 | BOOST_CHECK_THROW(url.encode(), std::runtime_error); 69 | BOOST_CHECK_THROW(url.encode_request(), std::runtime_error); 70 | 71 | url.path = "/example&page.html"; 72 | BOOST_CHECK_EQUAL("/example%26page.html", url.encode()); 73 | BOOST_CHECK_EQUAL("/example%26page.html", url.encode_request()); 74 | 75 | url.port = 80; 76 | BOOST_CHECK_THROW(url.encode(), std::runtime_error); 77 | BOOST_CHECK_EQUAL("/example%26page.html", url.encode_request()); 78 | 79 | url.port = 0; 80 | url.host = "example.com"; 81 | BOOST_CHECK_EQUAL("//example.com/example%26page.html", url.encode()); 82 | BOOST_CHECK_EQUAL("/example%26page.html", url.encode_request()); 83 | 84 | url.port = 80; 85 | BOOST_CHECK_EQUAL(80, url.port_or_default()); 86 | BOOST_CHECK_EQUAL("//example.com:80/example%26page.html", url.encode()); 87 | BOOST_CHECK_EQUAL("/example%26page.html", url.encode_request()); 88 | 89 | url.port = 0; 90 | url.host = ""; 91 | url.protocol = "https"; 92 | BOOST_CHECK_EQUAL(443, url.port_or_default()); 93 | BOOST_CHECK_THROW(url.encode(), std::runtime_error); 94 | BOOST_CHECK_EQUAL("/example%26page.html", url.encode_request()); 95 | 96 | url.host = "example.com"; 97 | BOOST_CHECK_EQUAL("https://example.com/example%26page.html", url.encode()); 98 | BOOST_CHECK_EQUAL("/example%26page.html", url.encode_request()); 99 | 100 | url.query_params["page"] = {"1"}; 101 | BOOST_CHECK_EQUAL("https://example.com/example%26page.html?page=1", url.encode()); 102 | BOOST_CHECK_EQUAL("/example%26page.html?page=1", url.encode_request()); 103 | 104 | url.query_params["page"] = {"1", "10"}; 105 | BOOST_CHECK_EQUAL("https://example.com/example%26page.html?page=1&page=10", url.encode()); 106 | BOOST_CHECK_EQUAL("/example%26page.html?page=1&page=10", url.encode_request()); 107 | } 108 | 109 | BOOST_AUTO_TEST_SUITE_END() 110 | -------------------------------------------------------------------------------- /include/http/Url.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace http 10 | { 11 | /**Errors for invalid URL's being parsed or built.*/ 12 | class UrlError : public std::runtime_error 13 | { 14 | public: 15 | /**Report a descriptive error about a specific URL string.*/ 16 | UrlError(const std::string &url, const std::string &msg) 17 | : std::runtime_error(msg + ": " + url) 18 | {} 19 | }; 20 | 21 | /**Percent decode a URL fragement.*/ 22 | std::string url_decode(const std::string &str); 23 | /**Percent decode a URL query string element, additionally converting '+' to ' '.*/ 24 | std::string url_decode_query(const std::string &str); 25 | 26 | /**Percent encode a URL path segment.*/ 27 | std::string url_encode_path(const std::string &str); 28 | /**Percent encode a URL query string element, additionally converting ' ' to '+'.*/ 29 | std::string url_encode_query(const std::string &str); 30 | 31 | /**A URL split into its components.*/ 32 | class Url 33 | { 34 | public: 35 | /**Container of query parameter values for a specific named parameter.*/ 36 | typedef std::vector QueryParamList; 37 | /**Map container of query parameters.*/ 38 | typedef std::unordered_map QueryParams; 39 | 40 | /**HTTP request string does not include protocol, host, port, or hash. */ 41 | static Url parse_request(const std::string &str); 42 | /**Connection protocol to use. e.g. "http" or "https".*/ 43 | std::string protocol; 44 | /**Host name or IP address.*/ 45 | std::string host; 46 | /**Port number (e.g. 80 or 443). 47 | * If no port number is explicitly specified, the value is zero. 48 | * To get a port number accounting for default protocol ports, use port_or_default. 49 | */ 50 | uint16_t port; 51 | /**Percent encoded URL path.*/ 52 | std::string path; 53 | /**Percent decoded query parameters. 54 | * There may be multiple parameters with the same name. No special meaning is given to 55 | * such parameters. 56 | */ 57 | QueryParams query_params; 58 | 59 | Url(); 60 | 61 | /**True if there is at least one query parameter with a given case sensitive name.*/ 62 | bool has_query_param(const std::string &name)const; 63 | /**Get a query parameter value. 64 | * - If there are multiple parameters with the same name, then the first (left-most) one 65 | * is returned. 66 | * 67 | * If it is required to handle multiple parameters use query_param_list. 68 | * - If there is no parameter with the name, then an empty string is returned. 69 | * 70 | * If it is required to tell existance or valuelessness apart, use has_query_param 71 | * or query_param_list. 72 | */ 73 | const std::string &query_param(const std::string &name)const; 74 | /**Gets the list of query parameters with a given name in order (left to right in the URL). 75 | * If there are no parameters with the name, then an empty list is returned. 76 | */ 77 | const QueryParamList& query_param_list(const std::string &name)const; 78 | 79 | /**Encodes the path onwards for use in a HTTP request message.*/ 80 | std::string encode_request()const; 81 | /**Write encode_request to the stream.*/ 82 | void encode_request(std::ostream &os)const; 83 | 84 | /**Write the full URL string to a stream.*/ 85 | void encode(std::ostream &os)const; 86 | /**Get the full URL string.*/ 87 | std::string encode()const; 88 | 89 | /**If port is non-zero, else return the default port for protocol if known, else throw.*/ 90 | uint16_t port_or_default()const 91 | { 92 | if (port) return port; 93 | if (protocol == "http") return 80; 94 | if (protocol == "https") return 443; 95 | throw std::runtime_error("No known default port for " + protocol); 96 | } 97 | }; 98 | 99 | inline std::string to_string(const Url &url) { return url.encode(); } 100 | inline std::ostream& operator << (std::ostream &os, const Url &url) 101 | { 102 | url.encode(os); 103 | return os; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /include/http/net/Net.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace http 6 | { 7 | /**Initialise networking components. This must be called before first use.*/ 8 | void init_net(); 9 | 10 | /**Get a string description for a C errno code.*/ 11 | std::string errno_string(int err); 12 | #ifdef _WIN32 13 | /**Get a string description for a Win32 error code.*/ 14 | std::string win_error_string(int err); 15 | /**Get a string description for a WinSock error code.*/ 16 | inline std::string wsa_error_string(int err) 17 | { 18 | return win_error_string(err); 19 | } 20 | /**Get a string description for a socket error code.*/ 21 | inline std::string socket_error_string(int err) 22 | { 23 | return wsa_error_string(err); 24 | } 25 | #else 26 | /**Get a string description for a socket error code.*/ 27 | inline std::string socket_error_string(int err) 28 | { 29 | return errno_string(err); 30 | } 31 | #endif 32 | /**Get the most recent error code from the networking library (WSAGetLastError or errno).*/ 33 | int last_net_error(); 34 | /**True if the error code says a non-blocking operation would have blocked. 35 | * WSAEWOULDBLOCK, EAGAIN or EWOULDBLOCK. 36 | */ 37 | bool would_block(int err); 38 | 39 | /**Base exception type for socket or other low level networking (e.g. DNS) errors.*/ 40 | class NetworkError : public std::runtime_error 41 | { 42 | public: 43 | using std::runtime_error::runtime_error; 44 | }; 45 | 46 | /**Base exception type for all socket errors, such as unexpected disconnects.*/ 47 | class SocketError : public NetworkError 48 | { 49 | public: 50 | /**Construct using a descriptive message.*/ 51 | explicit SocketError(const std::string &msg) : NetworkError(msg) {} 52 | /**Construct using socket_error_string.*/ 53 | explicit SocketError(int err) : NetworkError(socket_error_string(err)) {} 54 | /**Construct using a descriptive message plus socket_error_string.*/ 55 | SocketError(const std::string &msg, int err) 56 | : NetworkError(msg + ": " + socket_error_string(err)) 57 | {} 58 | /**Construct using a descriptive message plus socket_error_string.*/ 59 | SocketError(const char *msg, int err) 60 | : NetworkError(std::string(msg) + ": " + socket_error_string(err)) 61 | {} 62 | }; 63 | 64 | /**Errors connecting a socket.*/ 65 | class ConnectionError : public SocketError 66 | { 67 | public: 68 | /**Construct using socket_error_string to get the error reason.*/ 69 | ConnectionError(int err, const std::string &host, int port) 70 | : SocketError(make_message(err, host, port)) 71 | { 72 | } 73 | /**Construct using a specific error message.*/ 74 | ConnectionError(const std::string &err, const std::string &host, int port) 75 | : SocketError(make_message(err, host, port)) 76 | { 77 | } 78 | /**Construct using last_net_error to get the error reason.*/ 79 | ConnectionError(const std::string &host, int port) 80 | : SocketError(make_message(last_net_error(), host, port)) 81 | { 82 | } 83 | private: 84 | static std::string make_message(int err, const std::string &host, int port) 85 | { 86 | return make_message(socket_error_string(err), host, port); 87 | } 88 | static std::string make_message(const std::string &err, const std::string &host, int port) 89 | { 90 | std::string msg = "Failed to connect to " + host + ":" + std::to_string(port); 91 | msg += ". " + err; 92 | return msg; 93 | } 94 | }; 95 | 96 | /**Failed to verify the servers certificate.*/ 97 | class CertificateVerificationError : public ConnectionError 98 | { 99 | public: 100 | CertificateVerificationError(const std::string &host, int port) 101 | : ConnectionError("Certificate verification failed.", host, port) 102 | { 103 | } 104 | }; 105 | 106 | #ifdef _WIN32 107 | /**Windows error not directly related to a socket.*/ 108 | class WinError : public std::runtime_error 109 | { 110 | public: 111 | WinError(const std::string &msg, int code) 112 | : std::runtime_error(msg + " " + win_error_string(code)) 113 | {} 114 | WinError(const char *msg, int code) 115 | : WinError(std::string(msg), code) 116 | {} 117 | explicit WinError(const char *msg); 118 | }; 119 | #endif 120 | } 121 | -------------------------------------------------------------------------------- /source/Time.cpp: -------------------------------------------------------------------------------- 1 | #include "Time.hpp" 2 | #include 3 | #include 4 | 5 | namespace http 6 | { 7 | std::string format_time(time_t utc) 8 | { 9 | char buffer[sizeof("DDD, DD MMM YYYY HH:MM:SS GMT")]; 10 | tm tm; 11 | #ifdef _MSC_VER 12 | gmtime_s(&tm, &utc); 13 | #else 14 | gmtime_r(&utc, &tm); 15 | #endif 16 | size_t len = strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", &tm); 17 | return {buffer, len}; 18 | } 19 | 20 | namespace 21 | { 22 | time_t mkgmtime(tm *tm) 23 | { 24 | tm->tm_isdst = 0; 25 | if (tm->tm_year < 0 || tm->tm_year > 1100) return -1; 26 | if (tm->tm_mon < 0 || tm->tm_mon > 11) return -1; 27 | if (tm->tm_mday < 1 || tm->tm_mday > 31) return -1; 28 | if (tm->tm_hour < 0 || tm->tm_hour > 23) return -1; 29 | if (tm->tm_min < 0 || tm->tm_min > 60) return -1; 30 | if (tm->tm_sec < 0 || tm->tm_sec > 60) return -1; 31 | #ifdef _MSC_VER 32 | return _mkgmtime(tm); 33 | #else 34 | return timegm(tm); 35 | #endif 36 | } 37 | int get_tz_offset(const std::string &str) 38 | { 39 | if (str == "GMT") return 0; 40 | throw std::runtime_error("Failed to parse HTTP date. Unknown timezone '" + str + "'"); 41 | } 42 | int get_month(const std::string &str) 43 | { 44 | if (str == "Jan") return 0; 45 | if (str == "Feb") return 1; 46 | if (str == "Mar") return 2; 47 | if (str == "Apr") return 3; 48 | if (str == "May") return 4; 49 | if (str == "Jun") return 5; 50 | if (str == "Jul") return 6; 51 | if (str == "Aug") return 7; 52 | if (str == "Sep") return 8; 53 | if (str == "Oct") return 9; 54 | if (str == "Nov") return 10; 55 | if (str == "Dec") return 11; 56 | throw std::runtime_error("Failed to parse HTTP date. Invalid month '" + str + "'"); 57 | } 58 | time_t parse_rfc1123(const std::string &str) 59 | { 60 | //Sun, 06 Nov 1994 08:49:37 GMT 61 | std::string ignore, month, tz; 62 | char sep; 63 | tm tm; 64 | time_t t; 65 | std::stringstream ss(str); 66 | 67 | ss >> ignore >> tm.tm_mday >> month >> tm.tm_year; 68 | ss >> tm.tm_hour >> sep >> tm.tm_min >> sep >> tm.tm_sec; 69 | ss >> tz; 70 | 71 | if (ss.fail()) return -1; 72 | 73 | tm.tm_year -= 1900; 74 | tm.tm_mon = get_month(month); 75 | t = mkgmtime(&tm); 76 | if (t >= 0) t -= get_tz_offset(tz); 77 | return t; 78 | } 79 | time_t parse_rfc850(const std::string &str) 80 | { 81 | //Sunday, 06-Nov-94 08:49:37 GMT 82 | std::string ignore, month, tz; 83 | char sep; 84 | tm tm; 85 | time_t t; 86 | int year2; 87 | std::stringstream ss(str); 88 | month.resize(3); 89 | 90 | ss >> ignore; 91 | ss >> tm.tm_mday >> sep; 92 | ss.read(&month[0], 3); 93 | ss >> sep >> year2; 94 | ss >> tm.tm_hour >> sep >> tm.tm_min >> sep >> tm.tm_sec; 95 | ss >> tz; 96 | 97 | if (ss.fail()) return -1; 98 | 99 | tm.tm_year = (2000 + year2) - 1900; 100 | tm.tm_mon = get_month(month); 101 | t = mkgmtime(&tm); 102 | if (t >= 0) t -= get_tz_offset(tz); 103 | return t; 104 | } 105 | time_t parse_asctime(const std::string &str) 106 | { 107 | //Sun Nov 6 08:49:37 1994 108 | std::string ignore, month; 109 | char sep; 110 | tm tm; 111 | time_t t; 112 | std::stringstream ss(str); 113 | 114 | ss >> ignore >> month >> tm.tm_mday; 115 | ss >> tm.tm_hour >> sep >> tm.tm_min >> sep >> tm.tm_sec; 116 | ss >> tm.tm_year; 117 | 118 | if (ss.fail()) return -1; 119 | 120 | tm.tm_year -= 1900; 121 | tm.tm_mon = get_month(month); 122 | t = mkgmtime(&tm); 123 | return t; 124 | } 125 | } 126 | time_t parse_time(const std::string &time) 127 | { 128 | auto t = parse_rfc1123(time); 129 | if (t < 0) t = parse_rfc850(time); 130 | if (t < 0) t = parse_asctime(time); 131 | if (t < 0) throw std::runtime_error("Failed to parse HTTP time '" + time + "'"); 132 | return t; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /include/http/server/Router.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Request.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | namespace http 8 | { 9 | class Response; 10 | class UrlError; 11 | typedef std::unordered_map PathParams; 12 | typedef std::function RequestHandler; 13 | /**A route found by Router for a path and method. 14 | * If true, contains the handler and path parameters. 15 | */ 16 | struct MatchedRoute 17 | { 18 | /**Request handler if a route was matched, else null.*/ 19 | RequestHandler handler; 20 | /**Named path parameters from the matched URL path.*/ 21 | PathParams path_params; 22 | 23 | MatchedRoute() : handler(nullptr), path_params() {} 24 | /**True if a route was found.*/ 25 | explicit operator bool()const { return handler != nullptr; } 26 | }; 27 | 28 | /**Thrown when trying to add a route that is invalid.*/ 29 | class InvalidRouteError : public std::runtime_error 30 | { 31 | public: 32 | /**Construct a message with the method and path being added, and a descriptive failure reason.*/ 33 | InvalidRouteError(const std::string &method, const std::string &path, const std::string &reason) 34 | : std::runtime_error("Invalid route " + method + " " + path + ": " + reason) 35 | {} 36 | }; 37 | 38 | /**Finds handlers for requests by method and uri path. 39 | * Finding handlers is thread safe, but adding them is not. 40 | */ 41 | class Router 42 | { 43 | public: 44 | Router() {} 45 | ~Router() {} 46 | 47 | /**Gets the handler for a request that was added to this router. 48 | * @throws MethodNotAllowed if a handler matched the path, but not the method. 49 | * @return The found route, or a null handler if no route was found. 50 | */ 51 | MatchedRoute get(const std::string &method, const std::string &path)const; 52 | /**Adds a handler for a method and path. 53 | * 54 | * Paths and parameter names are case sensitive, and each segment is URL percent decoded. 55 | * 56 | * Path segments starting with a colon (':') are path parameters, and the text following 57 | * the colon up to the next forward slash is the parameter name. 58 | * 59 | * If the final segment is "/∗", then this is a prefix route, and will match all 60 | * child paths. 61 | * 62 | * Example Paths: 63 | * - "/" Site root page 64 | * - "/assets/∗" All files/items under assets, e.g. "/assets/application.js". 65 | * - "/profiles/:profile_id" A specific profile, with "profile_id" as a path parameter. 66 | * 67 | * @throws InvalidRouteError 68 | * - If a path parameter name does not match existing routes. 69 | * - If the method and path combination has already been added. 70 | * - If the path already exists as a prefix, and this one is not. 71 | * - If adding a prefix path, and the path already exists as a non-prefix path. 72 | */ 73 | void add(const std::string &method, const std::string &path, RequestHandler handler); 74 | private: 75 | typedef std::vector PathParts; 76 | typedef PathParts::const_iterator PathIterator; 77 | /**A node representing a single path segment.*/ 78 | struct Node 79 | { 80 | /**Named path parameter and child node. 81 | * True if a child node is present. 82 | */ 83 | struct Param 84 | { 85 | /**The name of the parameter.*/ 86 | std::string name; 87 | /**The child node containing further path segments or this segments methods.*/ 88 | std::unique_ptr node; 89 | 90 | explicit operator bool()const 91 | { 92 | return (bool)node; 93 | }; 94 | }; 95 | /**Prefix node for e.g. /assets/∗. Otherwise need to match all segments. 96 | * A prefix node can not have child nodes. 97 | */ 98 | bool prefix; 99 | std::unordered_map methods; 100 | /**Named child paths.*/ 101 | std::unordered_map> children; 102 | /**Parameter child node. Note that all such routes must use a common parameter name.*/ 103 | Param param; 104 | 105 | Node() : prefix(false), methods(), children(), param() {} 106 | }; 107 | /**Root of the paths tree ('/').*/ 108 | Node root; 109 | /**Splits a URL path by forward slashes into percent-decoded parts. 110 | * The path is expected to begin with a forward slash, and the empty initial part is not included. 111 | * 112 | * e.g. "/profiles/55" will return `{"profiles", "55"}`. 113 | */ 114 | PathParts get_parts(const std::string &path)const; 115 | }; 116 | } 117 | -------------------------------------------------------------------------------- /include/http/core/ParserUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../Error.hpp" 4 | #include "../Version.hpp" 5 | namespace http 6 | { 7 | 8 | /**Low level HTTP parser components. 9 | * Mostly a set of functions to parse each specific part of the HTTP protocol. 10 | * 11 | * RFC 7230 Appendix B specifies most of the lowest level components such as OWS, Token, etc. 12 | */ 13 | namespace parser 14 | { 15 | /**@return c >= '0' && c <= '9'*/ 16 | inline bool is_digit(char c) 17 | { 18 | return c >= '0' && c <= '9'; 19 | } 20 | 21 | /**@return The first occurance of chr, or end.*/ 22 | inline const char *find_chr(const char *begin, const char *end, char chr) 23 | { 24 | while (begin < end && *begin != chr) ++begin; 25 | return begin; 26 | } 27 | /**Finds the end of the line (CRLF), or null if the newline is not found.*/ 28 | const char *find_newline(const char *begin, const char *end); 29 | 30 | /**Read until end of token.*/ 31 | const char *read_token(const char *begin, const char *end); 32 | 33 | /**Read a quoted string. This has an output parameter due to the need to transfor 34 | * escape sequences. 35 | */ 36 | const char *read_qstring(const char *begin, const char *end, std::string *out); 37 | 38 | /**Reads a token or quoted string (read_token, read_qstring)*/ 39 | const char *read_token_or_qstring(const char *begin, const char *end, std::string *out); 40 | 41 | /**Reads the list seperator as defined by RFC7230 7. 42 | * Note: Using this alone is slightly more strict than RFC7230 says, in that leading 43 | * commas will be considered an error. An additional function could be used if a client 44 | * still exists in practice that generates such malformed lists. 45 | * @return Start or next value, or end if a valid list end is reached. 46 | * @throws ParserError If the seperator is syntactically invalid. 47 | */ 48 | const char *read_list_sep(const char *begin, const char *end); 49 | 50 | /**Reads the HTTP method from the request line. 51 | * @return The end of the method (SP). 52 | * @throws ParserError An invalid octet, or end is encountered. 53 | */ 54 | const char *read_method(const char *begin, const char *end); 55 | /**Reads the URI from the request line. 56 | * Note that the URI is not fully validated, only that it is ASCII, not zero length, and 57 | * does not contain any control codes. 58 | * 59 | * request-target in RFC 7230 5.3 60 | * 61 | * @return The end of the uri (SP). 62 | * @throws ParserError An invalid octet, or end is encountered. 63 | */ 64 | const char *read_uri(const char *begin, const char *end); 65 | /**Reads the HTTP version for either the request or response line. 66 | * This must be in the form "HTTP/a.b", multiple digits are allowed. 67 | * @return The end of the version, generally either SP or CRLF, but not validated. 68 | * @throws ParserError An invalid octet, or end is encountered. 69 | */ 70 | const char *read_version(const char *begin, const char *end, Version *out); 71 | /**read_version and validates end was reached.*/ 72 | const char *read_request_version(const char *begin, const char *end, Version *out); 73 | /**read_version and validates ended on SP.*/ 74 | const char *read_response_version(const char *begin, const char *end, Version *out); 75 | /**Status code read by read_status_code.*/ 76 | struct StatusCode 77 | { 78 | /**HTTP status code.*/ 79 | int code; 80 | /**Extended code used by IIS, or negative.*/ 81 | int extended; 82 | }; 83 | /**Read the status code. For IIS compatibility this does not follow 84 | * RFC7230, and instead both 3 digit, and 3.2 status codes are allowed. 85 | * @return The end of the code (SP). 86 | * @throws ParserError An invalid octet, or end is encountered. 87 | */ 88 | const char *read_status_code(const char *begin, const char *end, StatusCode *out); 89 | /**Read the status phrase. Since the phrase is plain text and must be at the end, this 90 | * effectively is only validation. 91 | * @throws ParserError An invalid octet before end was reached. 92 | */ 93 | void read_status_phrase(const char *begin, const char *end); 94 | 95 | 96 | /**Read the header name. 97 | * @return The ':' after the name. 98 | * @throws ParserError An invalid octet was found. 99 | */ 100 | const char *read_header_name(const char *begin, const char *end); 101 | /**Read to the end of the header value. Does not include OWS before or after. 102 | * @return The first SP or HT, or end. 103 | * @throws ParserError An invalid octet was found. 104 | */ 105 | const char *read_header_value(const char *begin, const char *end); 106 | /**@return The next non SP or HT element, or end.*/ 107 | const char *skip_ows(const char *begin, const char *end); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /include/http/net/SchannelSocket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Socket.hpp" 3 | #include "TcpSocket.hpp" 4 | #include "Os.hpp" 5 | #include "Schannel.hpp" 6 | #include 7 | #include 8 | #include 9 | namespace http 10 | { 11 | class PrivateCert; 12 | /**TLS secure socket using the Window's Secure Channel.*/ 13 | class SchannelSocket : public Socket 14 | { 15 | public: 16 | SchannelSocket(); 17 | SchannelSocket(SchannelSocket &&mv); 18 | /**Establish a client connection to a specific host and port.*/ 19 | SchannelSocket(const std::string &host, uint16_t port); 20 | /**Construct by taking ownership of an existing socket.*/ 21 | SchannelSocket(SOCKET socket, const sockaddr *address); 22 | /**Construct by taking ownership of an existing socket.*/ 23 | explicit SchannelSocket(TcpSocket &&socket) 24 | : SchannelSocket() 25 | { 26 | tcp = std::move(socket); 27 | } 28 | virtual ~SchannelSocket() {} 29 | SchannelSocket& operator = (SchannelSocket &&mv); 30 | 31 | /**Construct by taking ownership of an existing socket.*/ 32 | void set_socket(SOCKET socket, const sockaddr *address); 33 | /**Establish a client connection to a specific host and port.*/ 34 | void connect(const std::string &host, uint16_t port); 35 | 36 | virtual SOCKET get()override { return tcp.get(); } 37 | virtual std::string address_str()const override; 38 | virtual void close()override; 39 | virtual void disconnect()override; 40 | virtual bool check_recv_disconnect()override; 41 | virtual bool recv_pending()const override 42 | { 43 | return !recv_encrypted_buffer.empty() || !recv_decrypted_buffer.empty(); 44 | } 45 | virtual size_t recv(void *buffer, size_t len)override; 46 | virtual size_t send(const void *buffer, size_t len)override; 47 | 48 | virtual void async_disconnect(AsyncIo &aio, 49 | std::function handler, AsyncIo::ErrorHandler error)override; 50 | virtual void async_recv(AsyncIo &aio, void *buffer, size_t len, 51 | AsyncIo::RecvHandler handler, AsyncIo::ErrorHandler error)override; 52 | virtual void async_send(AsyncIo &aio, const void *buffer, size_t len, 53 | AsyncIo::SendHandler handler, AsyncIo::ErrorHandler error)override; 54 | virtual void async_send_all(AsyncIo &aio, const void *buffer, size_t len, 55 | AsyncIo::SendHandler handler, AsyncIo::ErrorHandler error)override; 56 | protected: 57 | struct UniqueCtxtHandle 58 | { 59 | UniqueCtxtHandle(); 60 | UniqueCtxtHandle(UniqueCtxtHandle &&mv); 61 | ~UniqueCtxtHandle(); 62 | UniqueCtxtHandle& operator = (UniqueCtxtHandle &&mv); 63 | explicit operator bool()const; 64 | void reset(); 65 | CtxtHandle handle; 66 | }; 67 | struct UniqueCredHandle 68 | { 69 | UniqueCredHandle(); 70 | UniqueCredHandle(UniqueCredHandle &&mv); 71 | ~UniqueCredHandle(); 72 | UniqueCredHandle& operator = (UniqueCredHandle &&mv); 73 | void reset(); 74 | CredHandle handle; 75 | }; 76 | struct SecBufferSingleAutoFree 77 | { 78 | SecBuffer buffer; 79 | SecBufferDesc desc; 80 | 81 | SecBufferSingleAutoFree() 82 | : buffer{0, SECBUFFER_TOKEN, nullptr} 83 | , desc{SECBUFFER_VERSION, 1, &buffer} 84 | { 85 | } 86 | ~SecBufferSingleAutoFree() 87 | { 88 | free(); 89 | } 90 | void free(); 91 | }; 92 | 93 | TcpSocket tcp; 94 | bool server; 95 | 96 | UniqueCtxtHandle context; 97 | UniqueCredHandle credentials; 98 | std::vector recv_encrypted_buffer; 99 | std::vector recv_decrypted_buffer; 100 | SecPkgContext_StreamSizes sec_sizes; 101 | std::unique_ptr header_buffer; 102 | std::unique_ptr trailer_buffer; 103 | 104 | /**Allocates header_buffer and trailer_buffer according to QueryContextAttributes*/ 105 | void alloc_buffers(); 106 | void init_credentials(); 107 | void client_handshake_loop(bool initial_read); 108 | void client_handshake(); 109 | void send_sec_buffers(const SecBufferDesc &buffers); 110 | /**Read data from local recv_decrypted_buffer if possible.*/ 111 | size_t recv_cached(void *vbytes, size_t len); 112 | /**recv some data into recv_encrypted_buffer*/ 113 | bool recv_encrypted(); 114 | /**Decrypts some data from recv_encrypted_buffer into a specified buffer and overflow into 115 | * recv_decrypted_buffer. 116 | * @return true if data was decrypted, false if more input is needed. 117 | */ 118 | bool decrypt(void *buffer, size_t len, size_t *out_len); 119 | 120 | /**Creates the message for disconnect and async_disconnect.*/ 121 | void disconnect_message(SecBufferSingleAutoFree &buffer); 122 | }; 123 | 124 | /**Server side S-Channel socket. Presents a certificate on connection.*/ 125 | class SchannelServerSocket : public SchannelSocket 126 | { 127 | public: 128 | /**Construct by taking ownership of an existing socket.*/ 129 | SchannelServerSocket() {} 130 | SchannelServerSocket(TcpSocket &&socket, const PrivateCert &cert); 131 | SchannelServerSocket(SchannelServerSocket &&mv) = default; 132 | SchannelServerSocket& operator = (SchannelServerSocket &&mv) = default; 133 | void async_create(AsyncIo &aio, TcpSocket &&socket, const PrivateCert &cert, 134 | std::function complete, AsyncIo::ErrorHandler error); 135 | protected: 136 | void tls_accept(const PrivateCert &cert); 137 | void create_credentials(const PrivateCert &cert); 138 | void server_handshake_loop(); 139 | void async_server_handshake_recv(AsyncIo &aio, std::function complete, AsyncIo::ErrorHandler error); 140 | void async_server_handshake_next(AsyncIo &aio, std::function complete, AsyncIo::ErrorHandler error); 141 | }; 142 | } 143 | -------------------------------------------------------------------------------- /tests/core/ParserUtils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "core/ParserUtils.hpp" 3 | 4 | using namespace http; 5 | using namespace http::parser; 6 | 7 | BOOST_AUTO_TEST_SUITE(TestCoreParserUtils) 8 | 9 | template std::string read_str(std::string str, T f) 10 | { 11 | auto end = str.c_str() + str.size(); 12 | auto p = f(str.c_str(), end); 13 | if (!p) return "null"; 14 | else return std::string(p, end); 15 | } 16 | 17 | BOOST_AUTO_TEST_CASE(test_find_newline) 18 | { 19 | auto f = [](std::string str) { return read_str(str, find_newline); }; 20 | BOOST_CHECK_EQUAL("null", f("")); 21 | BOOST_CHECK_EQUAL("null", f("testing")); 22 | BOOST_CHECK_EQUAL("null", f("\r")); 23 | BOOST_CHECK_EQUAL("null", f("test\r")); 24 | 25 | BOOST_CHECK_EQUAL("\r\n", f("\r\n")); 26 | BOOST_CHECK_EQUAL("\r\n", f(" testing\r\n")); 27 | BOOST_CHECK_EQUAL("\r\n", f(" test\ring\r\n")); 28 | BOOST_CHECK_EQUAL("\r\n", f(" test\ning\r\n")); 29 | BOOST_CHECK_EQUAL("\r\ntest\r\n", f(" testing\r\ntest\r\n")); 30 | BOOST_CHECK_EQUAL("\r\n\r\ntest\r\n", f(" testing\r\n\r\ntest\r\n")); 31 | BOOST_CHECK_EQUAL("\r\n\r\ntest\r\n", f(" testing\r\n\r\ntest\r\n")); 32 | } 33 | 34 | BOOST_AUTO_TEST_CASE(test_read_method) 35 | { 36 | auto f = [](std::string str) { return read_str(str, read_method); }; 37 | BOOST_CHECK_EQUAL(" ", f("GET ")); 38 | BOOST_CHECK_EQUAL(" /icon.png", f("GET /icon.png")); 39 | BOOST_CHECK_EQUAL(" ", f("CUSTOM_METHOD ")); 40 | 41 | BOOST_CHECK_THROW(f(""), ParserError); 42 | BOOST_CHECK_THROW(f("GET"), ParserError); 43 | BOOST_CHECK_THROW(f("GET\r\n"), ParserError); 44 | BOOST_CHECK_THROW(f("INVALID{} "), ParserError); 45 | } 46 | 47 | BOOST_AUTO_TEST_CASE(test_read_uri) 48 | { 49 | auto f = [](std::string str) { return read_str(str, read_uri); }; 50 | BOOST_CHECK_EQUAL(" ", f("/ ")); 51 | BOOST_CHECK_EQUAL(" HTTP/1.1", f("/icon.png HTTP/1.1")); 52 | BOOST_CHECK_EQUAL(" ", f("/icons?types=svg%20png ")); 53 | 54 | BOOST_CHECK_THROW(f(""), ParserError); 55 | BOOST_CHECK_THROW(f(" "), ParserError); 56 | BOOST_CHECK_THROW(f("\r\n"), ParserError); 57 | } 58 | 59 | template std::string read_version_str(std::string str, Version *out, T f) 60 | { 61 | auto end = str.c_str() + str.size(); 62 | auto p = f(str.c_str(), end, out); 63 | if (!p) return "null"; 64 | else return std::string(p, end); 65 | } 66 | BOOST_AUTO_TEST_CASE(test_read_version) 67 | { 68 | Version v; 69 | auto f = [&v](std::string str) { return read_version_str(str, &v, read_version); }; 70 | 71 | BOOST_CHECK_EQUAL("", f("HTTP/1.1")); 72 | BOOST_CHECK_EQUAL(1, v.major); 73 | BOOST_CHECK_EQUAL(1, v.minor); 74 | 75 | BOOST_CHECK_EQUAL("\r\n", f("HTTP/1.0\r\n")); 76 | BOOST_CHECK_EQUAL(1, v.major); 77 | BOOST_CHECK_EQUAL(0, v.minor); 78 | 79 | BOOST_CHECK_EQUAL(" ", f("HTTP/10.11 ")); 80 | BOOST_CHECK_EQUAL(10, v.major); 81 | BOOST_CHECK_EQUAL(11, v.minor); 82 | 83 | BOOST_CHECK_THROW(f(""), ParserError); 84 | BOOST_CHECK_THROW(f("HTTP/"), ParserError); 85 | BOOST_CHECK_THROW(f("HTTP/1"), ParserError); 86 | BOOST_CHECK_THROW(f("HTTP/1."), ParserError); 87 | BOOST_CHECK_THROW(f("HxTP/1.1"), ParserError); 88 | BOOST_CHECK_THROW(f("HTTP/x.1"), ParserError); 89 | BOOST_CHECK_THROW(f("HTTP/1x.1"), ParserError); 90 | BOOST_CHECK_THROW(f("HTTP/1x1"), ParserError); 91 | BOOST_CHECK_THROW(f("HTTP/1.x"), ParserError); 92 | 93 | BOOST_CHECK_THROW(read_version_str("HTTP/1.1 ", &v, read_request_version), ParserError); 94 | BOOST_CHECK_NO_THROW(read_version_str("HTTP/1.1", &v, read_request_version)); 95 | BOOST_CHECK_NO_THROW(read_version_str("HTTP/1.1 ", &v, read_response_version)); 96 | BOOST_CHECK_THROW(read_version_str("HTTP/1.1", &v, read_response_version), ParserError); 97 | } 98 | 99 | BOOST_AUTO_TEST_CASE(test_read_status_code) 100 | { 101 | StatusCode code; 102 | auto f = [&code](std::string str) -> std::string 103 | { 104 | auto end = str.c_str() + str.size(); 105 | auto p = read_status_code(str.c_str(), end, &code); 106 | if (!p) return "null"; 107 | else return std::string(p, end); 108 | }; 109 | 110 | BOOST_CHECK_EQUAL(" ", f("200 ")); 111 | BOOST_CHECK_EQUAL(200, code.code); 112 | BOOST_CHECK_EQUAL(-1, code.extended); 113 | 114 | BOOST_CHECK_EQUAL(" ", f("500 ")); 115 | BOOST_CHECK_EQUAL(500, code.code); 116 | BOOST_CHECK_EQUAL(-1, code.extended); 117 | 118 | BOOST_CHECK_EQUAL(" ", f("500.12 ")); 119 | BOOST_CHECK_EQUAL(500, code.code); 120 | BOOST_CHECK_EQUAL(12, code.extended); 121 | 122 | BOOST_CHECK_THROW(f(""), ParserError); 123 | BOOST_CHECK_THROW(f(" "), ParserError); 124 | BOOST_CHECK_THROW(f("200"), ParserError); 125 | BOOST_CHECK_THROW(f("200."), ParserError); 126 | BOOST_CHECK_THROW(f("200.12"), ParserError); 127 | } 128 | 129 | BOOST_AUTO_TEST_CASE(test_read_status_phrase) 130 | { 131 | auto f = [](std::string str) { read_status_phrase(str.c_str(), str.c_str() + str.size()); }; 132 | BOOST_CHECK_NO_THROW(f("")); 133 | BOOST_CHECK_NO_THROW(f("Not Found")); 134 | 135 | BOOST_CHECK_THROW(f("Not\nFound"), ParserError); 136 | } 137 | 138 | 139 | BOOST_AUTO_TEST_CASE(test_skip_ows) 140 | { 141 | auto f = [](std::string str) { return read_str(str, skip_ows); }; 142 | BOOST_CHECK_EQUAL("", f("")); 143 | BOOST_CHECK_EQUAL("", f(" \t ")); 144 | BOOST_CHECK_EQUAL("value", f("value")); 145 | BOOST_CHECK_EQUAL("value", f(" \tvalue")); 146 | } 147 | 148 | BOOST_AUTO_TEST_CASE(test_read_header_name) 149 | { 150 | auto f = [](std::string str) { return read_str(str, read_header_name); }; 151 | BOOST_CHECK_EQUAL(": value", f("Content-Type: value")); 152 | 153 | BOOST_CHECK_THROW(f("invalid[]: value"), ParserError); 154 | BOOST_CHECK_THROW(f("no-colon"), ParserError); 155 | BOOST_CHECK_THROW(f("invalid-sp : value"), ParserError); 156 | BOOST_CHECK_THROW(f(" invalid-sp:value"), ParserError); 157 | } 158 | 159 | BOOST_AUTO_TEST_CASE(test_read_header_value) 160 | { 161 | auto f = [](std::string str) { return read_str(str, read_header_value); }; 162 | BOOST_CHECK_EQUAL("", f("value")); 163 | BOOST_CHECK_EQUAL(" ", f("value ")); 164 | BOOST_CHECK_EQUAL("\t", f("value\t")); 165 | BOOST_CHECK_EQUAL(" \t ", f("value \t ")); 166 | BOOST_CHECK_EQUAL(" \t ", f("hello world \t ")); 167 | 168 | BOOST_CHECK_THROW(f("invalid\nvalue"), ParserError); 169 | BOOST_CHECK_THROW(f(""), ParserError); 170 | BOOST_CHECK_THROW(f(" "), ParserError); 171 | } 172 | 173 | 174 | BOOST_AUTO_TEST_SUITE_END() 175 | -------------------------------------------------------------------------------- /tests/headers/Accept.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "headers/Accept.hpp" 3 | #include 4 | using namespace http; 5 | 6 | BOOST_AUTO_TEST_SUITE(TestHeadersAccept) 7 | BOOST_AUTO_TEST_CASE(parse_single) 8 | { 9 | Accept accept; 10 | Accept::Type type; 11 | 12 | //media-range 13 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*")); 14 | BOOST_REQUIRE_EQUAL(1, accept.accepts().size()); 15 | type = accept.accepts()[0]; 16 | BOOST_CHECK_EQUAL("*", type.type); 17 | BOOST_CHECK_EQUAL("*", type.subtype); 18 | BOOST_CHECK_EQUAL(1, type.quality); 19 | 20 | 21 | BOOST_REQUIRE_NO_THROW(accept = Accept("text/*")); 22 | type = accept.accepts().at(0); 23 | BOOST_CHECK_EQUAL("text", type.type); 24 | BOOST_CHECK_EQUAL("*", type.subtype); 25 | BOOST_CHECK_EQUAL(1, type.quality); 26 | 27 | 28 | BOOST_REQUIRE_NO_THROW(accept = Accept("text/plain")); 29 | type = accept.accepts().at(0); 30 | BOOST_CHECK_EQUAL("text", type.type); 31 | BOOST_CHECK_EQUAL("plain", type.subtype); 32 | BOOST_CHECK_EQUAL(1, type.quality); 33 | 34 | //quality 35 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*;q=0")); 36 | type = accept.accepts().at(0); 37 | BOOST_CHECK_EQUAL("*", type.type); 38 | BOOST_CHECK_EQUAL("*", type.subtype); 39 | BOOST_CHECK_EQUAL(0, type.quality); 40 | 41 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*;q=0.")); 42 | type = accept.accepts().at(0); 43 | BOOST_CHECK_EQUAL(0, type.quality); 44 | 45 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*;q=0.000")); 46 | type = accept.accepts().at(0); 47 | BOOST_CHECK_EQUAL(0, type.quality); 48 | 49 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*;q=0.123")); 50 | type = accept.accepts().at(0); 51 | BOOST_CHECK_CLOSE(0.123, type.quality, 0.00001); 52 | 53 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*;q=1")); 54 | type = accept.accepts().at(0); 55 | BOOST_CHECK_EQUAL(1, type.quality); 56 | 57 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*;q=1.000")); 58 | type = accept.accepts().at(0); 59 | BOOST_CHECK_EQUAL(1, type.quality); 60 | 61 | //media type parameters (currently ignored) 62 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*;charset=utf-8;q=1")); 63 | type = accept.accepts().at(0); 64 | BOOST_CHECK_EQUAL(1, type.quality); 65 | 66 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*;charset=\"quoted\\\"string\";q=1")); 67 | type = accept.accepts().at(0); 68 | BOOST_CHECK_EQUAL(1, type.quality); 69 | 70 | //accept-ext parameters (currently ignored) 71 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*;q=1;ext=5")); 72 | type = accept.accepts().at(0); 73 | BOOST_CHECK_EQUAL(1, type.quality); 74 | 75 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*;q=1;ext=\"quoted\\\"string\"")); 76 | type = accept.accepts().at(0); 77 | BOOST_CHECK_EQUAL(1, type.quality); 78 | } 79 | 80 | BOOST_AUTO_TEST_CASE(parse_list) 81 | { 82 | Accept accept; 83 | 84 | BOOST_REQUIRE_NO_THROW(accept = Accept("*/*,,text/html , application/json,")); 85 | BOOST_REQUIRE_EQUAL(3, accept.accepts().size()); 86 | BOOST_CHECK_EQUAL("*", accept.accepts()[0].type); 87 | BOOST_CHECK_EQUAL("*", accept.accepts()[0].subtype); 88 | 89 | BOOST_CHECK_EQUAL("text", accept.accepts()[1].type); 90 | BOOST_CHECK_EQUAL("html", accept.accepts()[1].subtype); 91 | 92 | BOOST_CHECK_EQUAL("application", accept.accepts()[2].type); 93 | BOOST_CHECK_EQUAL("json", accept.accepts()[2].subtype); 94 | } 95 | 96 | BOOST_AUTO_TEST_CASE(invalid_media_range) 97 | { 98 | BOOST_CHECK_THROW(Accept("*/plain"), ParserError); 99 | BOOST_CHECK_THROW(Accept("/plain"), ParserError); 100 | BOOST_CHECK_THROW(Accept("text/"), ParserError); 101 | BOOST_CHECK_THROW(Accept("text/*plain"), ParserError); 102 | BOOST_CHECK_THROW(Accept("text-plain"), ParserError); 103 | BOOST_CHECK_THROW(Accept("text-plain"), ParserError); 104 | BOOST_CHECK_THROW(Accept("invalid / space"), ParserError); 105 | BOOST_CHECK_THROW(Accept("invalid@token/plain"), ParserError); 106 | } 107 | 108 | BOOST_AUTO_TEST_CASE(invalid_quality) 109 | { 110 | BOOST_CHECK_THROW(Accept("*/*;q=1.0000"), ParserError); 111 | BOOST_CHECK_THROW(Accept("*/*;q=1.500"), ParserError); 112 | BOOST_CHECK_THROW(Accept("*/*;q=0.0000"), ParserError); 113 | BOOST_CHECK_THROW(Accept("*/*;q=05"), ParserError); 114 | BOOST_CHECK_THROW(Accept("*/*;q=x.0"), ParserError); 115 | BOOST_CHECK_THROW(Accept("*/*;q=1.x"), ParserError); 116 | } 117 | 118 | BOOST_AUTO_TEST_CASE(invalid_media_param) 119 | { 120 | BOOST_CHECK_THROW(Accept("*/*;;charset=utf-8"), ParserError); 121 | BOOST_CHECK_THROW(Accept("*/*;charset="), ParserError); 122 | BOOST_CHECK_THROW(Accept("*/*;charset=\"unmatched"), ParserError); 123 | BOOST_CHECK_THROW(Accept("*/*;charset=unmatched\""), ParserError); 124 | BOOST_CHECK_THROW(Accept("*/*;charset=invalid\ntoken"), ParserError); 125 | BOOST_CHECK_THROW(Accept("*/*;charset= utf-8"), ParserError); 126 | BOOST_CHECK_THROW(Accept("*/*;charset =utf-8"), ParserError); 127 | } 128 | 129 | BOOST_AUTO_TEST_CASE(invalid_accept_ext) 130 | { 131 | BOOST_CHECK_THROW(Accept("*/*;q=1;;ext=1"), ParserError); 132 | BOOST_CHECK_THROW(Accept("*/*;q=1;invalid@token=1"), ParserError); 133 | BOOST_CHECK_THROW(Accept("*/*;q=1;ext=invalid@token"), ParserError); 134 | } 135 | 136 | BOOST_AUTO_TEST_CASE(accepts) 137 | { 138 | BOOST_CHECK(Accept("*/*").accepts("text/plain")); 139 | BOOST_CHECK(Accept("text/*").accepts("text/plain")); 140 | BOOST_CHECK(!Accept("text/*").accepts("application/json")); 141 | BOOST_CHECK(Accept("text/plain").accepts("text/plain")); 142 | BOOST_CHECK(!Accept("text/plain").accepts("text/html")); 143 | 144 | BOOST_CHECK(Accept("text/plain, text/html").accepts("text/plain")); 145 | BOOST_CHECK(Accept("text/plain, text/html").accepts("text/html")); 146 | BOOST_CHECK(!Accept("text/plain, text/html").accepts("text/xml")); 147 | 148 | BOOST_CHECK_NO_THROW(Accept("text/plain").check_accepts("text/plain")); 149 | BOOST_CHECK_THROW(Accept("text/plain").check_accepts("text/html"), NotAcceptable); 150 | } 151 | 152 | BOOST_AUTO_TEST_CASE(preferred) 153 | { 154 | BOOST_CHECK_EQUAL("text/html", Accept("text/html").preferred({ "text/html" })); 155 | BOOST_CHECK_EQUAL("text/html", Accept("text/*").preferred({ "text/html" })); 156 | 157 | BOOST_CHECK_EQUAL("text/html", Accept("text/html, */*").preferred({ "text/plain", "text/html" })); 158 | BOOST_CHECK_EQUAL("text/html", Accept("*/*, text/html").preferred({ "text/plain", "text/html" })); 159 | 160 | BOOST_CHECK_EQUAL("image/png", Accept("*/*, image/*").preferred({ "text/plain", "image/png" })); 161 | 162 | BOOST_CHECK_EQUAL("text/plain", Accept("text/html, */*;q=0.5").preferred({ "text/plain", "image/png" })); 163 | 164 | BOOST_CHECK_EQUAL("text/plain", Accept("text/*, image/*").preferred({ "text/plain", "text/html", "image/png" })); 165 | BOOST_CHECK_EQUAL("image/png", Accept("text/*;q=0.5, image/*").preferred({ "text/plain", "text/html", "image/png" })); 166 | 167 | 168 | BOOST_CHECK_THROW(Accept("text/xml+svg, image/*").preferred({ "text/plain", "text/html" }), NotAcceptable); 169 | } 170 | BOOST_AUTO_TEST_SUITE_END() 171 | -------------------------------------------------------------------------------- /tests/client/AsyncClient.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "client/AsyncClient.hpp" 3 | #include "../TestSocket.hpp" 4 | #include "../TestSocketFactory.hpp" 5 | #include "net/Net.hpp" 6 | #include 7 | using namespace http; 8 | 9 | BOOST_AUTO_TEST_SUITE(TestClient) 10 | 11 | BOOST_AUTO_TEST_CASE(construct) 12 | { 13 | TestSocketFactory socket_factory; 14 | socket_factory.recv_buffer = 15 | "HTTP/1.1 200 OK\r\n" 16 | "Content-Type: text/plain\r\n" 17 | "Content-Length: 10\r\n" 18 | "\r\n" 19 | "0123456789"; 20 | 21 | AsyncClientParams params; 22 | params.host = "localhost"; 23 | params.port = 80; 24 | params.tls = false; 25 | params.max_connections = 5; 26 | params.socket_factory = &socket_factory; 27 | 28 | AsyncClient client(params); 29 | 30 | client.exit(); 31 | client.start(); 32 | client.exit(); 33 | } 34 | 35 | BOOST_AUTO_TEST_CASE(single) 36 | { 37 | TestSocketFactory socket_factory; 38 | socket_factory.recv_buffer = 39 | "HTTP/1.1 200 OK\r\n" 40 | "Content-Type: text/plain\r\n" 41 | "Content-Length: 10\r\n" 42 | "\r\n" 43 | "0123456789"; 44 | 45 | AsyncClientParams params; 46 | params.host = "localhost"; 47 | params.port = 80; 48 | params.tls = false; 49 | params.max_connections = 5; 50 | params.socket_factory = &socket_factory; 51 | 52 | AsyncClient client(params); 53 | 54 | AsyncRequest req; 55 | req.method = GET; 56 | req.raw_url = "/index.html"; 57 | 58 | auto response = client.queue(&req).get(); 59 | BOOST_CHECK_EQUAL(200, response->status.code); 60 | } 61 | 62 | BOOST_AUTO_TEST_CASE(sequential) 63 | { 64 | TestSocketFactory socket_factory; 65 | socket_factory.recv_buffer = 66 | "HTTP/1.1 200 OK\r\n" 67 | "Content-Type: text/plain\r\n" 68 | "Content-Length: 10\r\n" 69 | "\r\n" 70 | "0123456789"; 71 | 72 | AsyncClientParams params; 73 | params.host = "localhost"; 74 | params.port = 80; 75 | params.tls = false; 76 | params.max_connections = 5; 77 | params.socket_factory = &socket_factory; 78 | 79 | AsyncClient client(params); 80 | 81 | AsyncRequest req; 82 | req.method = GET; 83 | req.raw_url = "/index.html"; 84 | 85 | auto response = client.queue(&req).get(); 86 | BOOST_CHECK_EQUAL(200, response->status.code); 87 | 88 | req.reset(); 89 | response = client.queue(&req).get(); 90 | BOOST_CHECK_EQUAL(200, response->status.code); 91 | 92 | req.reset(); 93 | response = client.queue(&req).get(); 94 | BOOST_CHECK_EQUAL(200, response->status.code); 95 | } 96 | 97 | 98 | BOOST_AUTO_TEST_CASE(callback) 99 | { 100 | std::atomic done(false); 101 | 102 | TestSocketFactory socket_factory; 103 | socket_factory.recv_buffer = 104 | "HTTP/1.1 200 OK\r\n" 105 | "Content-Type: text/plain\r\n" 106 | "Content-Length: 10\r\n" 107 | "\r\n" 108 | "0123456789"; 109 | 110 | AsyncClientParams params; 111 | params.host = "localhost"; 112 | params.port = 80; 113 | params.tls = false; 114 | params.max_connections = 5; 115 | params.socket_factory = &socket_factory; 116 | 117 | AsyncClient client(params); 118 | 119 | AsyncRequest req; 120 | req.method = GET; 121 | req.raw_url = "/index.html"; 122 | req.on_completion = [&done](AsyncRequest *, Response &) -> void 123 | { 124 | done = true; 125 | }; 126 | 127 | client.queue(&req); 128 | auto response = req.wait(); 129 | BOOST_CHECK_EQUAL(200, response->status.code); 130 | BOOST_CHECK(done); 131 | } 132 | 133 | BOOST_AUTO_TEST_CASE(async_error) 134 | { 135 | std::atomic done(false); 136 | 137 | class Factory : public SocketFactory 138 | { 139 | public: 140 | virtual std::unique_ptr connect(const std::string &host, uint16_t port, bool)override 141 | { 142 | throw ConnectionError(host, port); 143 | } 144 | }; 145 | Factory socket_factory; 146 | 147 | AsyncClientParams params; 148 | params.host = "localhost"; 149 | params.port = 80; 150 | params.tls = false; 151 | params.max_connections = 5; 152 | params.socket_factory = &socket_factory; 153 | 154 | AsyncClient client(params); 155 | 156 | AsyncRequest req; 157 | req.method = GET; 158 | req.raw_url = "/index.html"; 159 | req.on_exception = [&done](AsyncRequest*) -> void 160 | { 161 | try 162 | { 163 | throw; 164 | } 165 | catch (const ConnectionError &) 166 | { 167 | done = true; 168 | } 169 | }; 170 | 171 | client.queue(&req); 172 | BOOST_CHECK_THROW(req.wait(), ConnectionError); 173 | BOOST_CHECK(done); 174 | } 175 | 176 | BOOST_AUTO_TEST_CASE(parallel) 177 | { 178 | class Factory : public TestSocketFactory 179 | { 180 | public: 181 | using TestSocketFactory::TestSocketFactory; 182 | std::mutex go; 183 | virtual std::unique_ptr connect(const std::string &host, uint16_t port, bool tls)override 184 | { 185 | ++connect_count; 186 | std::unique_lock lock(go); 187 | auto sock = std::unique_ptr(new TestSocket()); 188 | sock->host = host; 189 | sock->port = port; 190 | sock->tls = tls; 191 | sock->recv_buffer = recv_buffer; 192 | last = sock.get(); 193 | return std::move(sock); 194 | } 195 | }; 196 | Factory socket_factory; 197 | socket_factory.recv_buffer = 198 | "HTTP/1.1 200 OK\r\n" 199 | "Content-Type: text/plain\r\n" 200 | "Content-Length: 10\r\n" 201 | "Connection: keep-alive\r\n" 202 | "\r\n" 203 | "0123456789"; 204 | 205 | std::unique_lock lock(socket_factory.go); 206 | 207 | AsyncClientParams params; 208 | params.host = "localhost"; 209 | params.port = 80; 210 | params.tls = false; 211 | params.max_connections = 4; 212 | params.socket_factory = &socket_factory; 213 | 214 | AsyncClient client(params); 215 | 216 | auto req = []() -> AsyncRequest 217 | { 218 | AsyncRequest req; 219 | req.method = GET; 220 | req.raw_url = "/index.html"; 221 | return req; 222 | }; 223 | 224 | auto a = req(); 225 | auto b = req(); 226 | auto c = req(); 227 | auto d = req(); 228 | auto e = req(); 229 | auto f = req(); 230 | 231 | client.queue(&a); 232 | client.queue(&b); 233 | client.queue(&c); 234 | client.queue(&d); 235 | client.queue(&e); 236 | client.queue(&f); 237 | 238 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); 239 | BOOST_CHECK_EQUAL(4U, socket_factory.connect_count.load()); 240 | lock.unlock(); 241 | 242 | BOOST_CHECK_NO_THROW(a.wait()); 243 | BOOST_CHECK_NO_THROW(b.wait()); 244 | BOOST_CHECK_NO_THROW(c.wait()); 245 | BOOST_CHECK_NO_THROW(d.wait()); 246 | BOOST_CHECK_NO_THROW(e.wait()); 247 | BOOST_CHECK_NO_THROW(f.wait()); 248 | 249 | BOOST_CHECK_EQUAL(4U, socket_factory.connect_count.load()); 250 | } 251 | 252 | 253 | BOOST_AUTO_TEST_SUITE_END() 254 | -------------------------------------------------------------------------------- /include/http/core/Parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ParserUtils.hpp" 3 | #include "../Headers.hpp" 4 | #include "../Status.hpp" 5 | #include "../Method.hpp" 6 | #include 7 | 8 | namespace http 9 | { 10 | /**HTTP parser base for server and client side messages. 11 | * Provides the base implementation for RequestParser and ResponseParser, which provide 12 | * some key functionality. 13 | */ 14 | class BaseParser 15 | { 16 | public: 17 | /**Max size of any line. A buffer of this size will be capable of parsing any HTTP component.*/ 18 | static const size_t LINE_SIZE = 8192; 19 | /**Max size of first line.*/ 20 | static const size_t FIRST_LINE_LEN = LINE_SIZE; 21 | /**Max number of headers.*/ 22 | static const size_t MAX_HEADER_COUNT = 100; 23 | /**Max size of a single header line.*/ 24 | static const size_t MAX_HEADER_SIZE = LINE_SIZE; 25 | /**Max combined size of headers.*/ 26 | static const size_t MAX_HEADERS_SIZE = 65536; 27 | /**Max size of a chunk length line.*/ 28 | static const size_t MAX_CHUNK_LINE_SIZE = 10; 29 | 30 | enum State 31 | { 32 | /**Not parsed any content yet, awaiting completion of the first line.*/ 33 | START, 34 | /**Read the first line and now reading headers. 35 | * Already completed headers are available to access. 36 | */ 37 | HEADERS, 38 | /**Reading body data of a known length defined by the Content-Type header.*/ 39 | BODY, 40 | /**Reading chunked encoding body data.*/ 41 | BODY_CHUNK, 42 | /**Next is a chunked encoding chunk length header.*/ 43 | BODY_CHUNK_LEN, 44 | /**Next is a "\r\n" chunked encoding terminator.*/ 45 | BODY_CHUNK_TERMINATOR, 46 | /**Read body data until connection is closed.*/ 47 | BODY_UNTIL_CLOSE, 48 | /**Trailer headers for chunked-encoding.*/ 49 | TRAILER_HEADERS, 50 | /**Message parsing completed.*/ 51 | COMPLETED 52 | }; 53 | 54 | BaseParser(); 55 | /**Reset the parser so it is ready to read another message.*/ 56 | void reset(); 57 | 58 | /**Reading the entire HTTP request or response message is complete.*/ 59 | bool is_completed()const { return _state == COMPLETED; } 60 | 61 | /**Get the current parser progress state.*/ 62 | State state()const { return _state; } 63 | /**Get the HTTP version. Only valid if progressed to HEADERS or beyond.*/ 64 | const Version& version()const { return _version; } 65 | /**Get the parsed headers. The headers container may be safely moved if parsing headers 66 | * is complete. 67 | */ 68 | Headers&& headers() { return std::move(_headers); } 69 | /**Get the parsed headers.*/ 70 | const Headers& headers()const { return _headers; } 71 | /**Get the message body. 72 | * Allthough this is a std::string, it may contain binary data. 73 | * Can be safely moved once reading the entire body is complete. 74 | */ 75 | std::string &&body() { return std::move(_body); } 76 | /**Get the message body. 77 | * Allthough this is a std::string, it may contain binary data. 78 | */ 79 | const std::string &body()const { return _body; } 80 | 81 | /**True if the content length is known (content_length is valid), allthough the message 82 | * body may not have yet been read (is_completed may be false). 83 | */ 84 | bool has_content_length()const 85 | { 86 | return _state == BODY || _state == COMPLETED; 87 | } 88 | /**Get the content body length. Only valid if has_content_length.*/ 89 | size_t content_length()const 90 | { 91 | return _content_length; 92 | } 93 | protected: 94 | 95 | State _state; 96 | Headers _headers; 97 | Version _version; 98 | size_t _content_length; 99 | size_t remaining_content_length; 100 | std::string _body; 101 | 102 | /**Read a header line (state == HEADERS). 103 | * @param str The header to read. 104 | * @param end The end of str, does not include the trailing \r\n. 105 | */ 106 | void read_header(const char *str, const char *end); 107 | /**Start reading the body. 108 | * Transfer-Encoding: chunked is supported, but no others are. 109 | */ 110 | template void start_body(); 111 | /**Checks version is 1.x*/ 112 | void check_version()const 113 | { 114 | if (_version.major != 1) throw ParserError("Unsupported version", 505); 115 | } 116 | template const char *do_read(const char *begin, const char *end); 117 | void process_body(const char *begin, const char *end) 118 | { 119 | _body.insert(_body.end(), begin, end); 120 | } 121 | }; 122 | 123 | /**HTTP server side request parser.*/ 124 | class RequestParser : public BaseParser 125 | { 126 | public: 127 | /**Read input. 128 | * 129 | * May not consume all of the input, if there is remaining input, and the request is not 130 | * complete then additional data must be made available, and read called against starting 131 | * from that point. 132 | * 133 | * If the request is complete, then the input is expected to be for another, pipelined 134 | * request. 135 | * 136 | * @return Pointer to 1 element past the consumed input. 137 | */ 138 | const char *read(const char *begin, const char *end); 139 | 140 | /**Get the HTTP request method.*/ 141 | const std::string &method()const { return _method; } 142 | /**get the request URI. This only includes the path onwards. 143 | * The host and port can be obtained from the "Host" header. 144 | */ 145 | const std::string &uri()const { return _uri; } 146 | private: 147 | friend class BaseParser; 148 | 149 | std::string _method; 150 | std::string _uri; 151 | 152 | void read_first_line(const char *str, const char *end); 153 | void start_body(); 154 | }; 155 | /**HTTP client side response parser.*/ 156 | class ResponseParser : public BaseParser 157 | { 158 | public: 159 | /**Read input. Has the same behaviour as RequestParser::read.*/ 160 | const char *read(const char *begin, const char *end); 161 | /**Reset ready to read a response following a request of a specific method. 162 | * The method is needed for the response to know if it should expect a body or not 163 | * (e.g. a HEAD response will have Content-Length etc., but does not send the body bytes). 164 | */ 165 | void reset(Method method); 166 | /**Gets the response status code and message.*/ 167 | Status status()const { return _status; } 168 | private: 169 | friend class BaseParser; 170 | 171 | Method method; 172 | Status _status; 173 | 174 | void read_first_line(const char *str, const char *end); 175 | void start_body(); 176 | }; 177 | } 178 | -------------------------------------------------------------------------------- /include/http/net/AsyncIo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Os.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | namespace http 11 | { 12 | class AsyncAborted : public std::runtime_error 13 | { 14 | public: 15 | AsyncAborted() : std::runtime_error("Aborted") {} 16 | }; 17 | class TcpSocket; 18 | class AsyncIo 19 | { 20 | public: 21 | typedef std::function ErrorHandler; 22 | typedef std::function AcceptHandler; 23 | typedef std::function RecvHandler; 24 | typedef std::function SendHandler; 25 | 26 | AsyncIo(); 27 | ~AsyncIo(); 28 | 29 | AsyncIo(const AsyncIo&) = delete; 30 | AsyncIo& operator = (const AsyncIo&) = delete; 31 | 32 | void run(); 33 | void exit(); 34 | 35 | void accept(SOCKET sock, AcceptHandler handler, ErrorHandler error); 36 | void recv(SOCKET sock, void *buffer, size_t len, RecvHandler handler, ErrorHandler error); 37 | void send(SOCKET sock, const void *buffer, size_t len, SendHandler handler, ErrorHandler error); 38 | void send_all(SOCKET sock, const void *buffer, size_t len, SendHandler handler, ErrorHandler error); 39 | private: 40 | /**Held by the main run thread to block exit() until run() is done. Much like a thread join 41 | * but allowing the run() thread to return and live on. 42 | */ 43 | std::mutex exit_mutex; 44 | #if defined(HTTP_USE_SELECT) 45 | // TODO: Refactor. On Windows can use events, on Linux can use pipes 46 | class SignalSocket 47 | { 48 | public: 49 | SignalSocket(); 50 | ~SignalSocket(); 51 | void create(); 52 | void destroy(); 53 | void signal(); 54 | void clear(); 55 | SOCKET get(); 56 | private: 57 | SOCKET send; 58 | SOCKET recv; 59 | }; 60 | 61 | struct Accept 62 | { 63 | SOCKET sock; 64 | AcceptHandler handler; 65 | ErrorHandler error; 66 | }; 67 | struct Recv 68 | { 69 | SOCKET sock; 70 | void *buffer; 71 | size_t len; 72 | RecvHandler handler; 73 | ErrorHandler error; 74 | }; 75 | struct Send 76 | { 77 | bool all; 78 | SOCKET sock; 79 | const void *buffer; 80 | size_t len; 81 | size_t sent; 82 | SendHandler handler; 83 | ErrorHandler error; 84 | }; 85 | 86 | SignalSocket signal; 87 | bool exiting; 88 | std::mutex mutex; 89 | /**In-progress operations. 90 | * If a single socket has multiple read or write operations, they are processed in order. 91 | */ 92 | struct 93 | { 94 | std::list accept; 95 | std::unordered_map> recv; 96 | std::unordered_map> send; 97 | }in_progress; 98 | /**New operations added by other threads and no yet included in the select() loop.*/ 99 | struct 100 | { 101 | std::vector accept; 102 | std::vector recv; 103 | std::vector send; 104 | }new_operations; 105 | #elif defined(HTTP_USE_IOCP) 106 | struct CompletionPort 107 | { 108 | HANDLE port; 109 | CompletionPort(); 110 | ~CompletionPort(); 111 | }; 112 | struct Operation 113 | { 114 | enum Type 115 | { 116 | ACCEPT,RECV,SEND,SEND_ALL 117 | }; 118 | struct Deleter 119 | { 120 | void operator()(Operation *op) { op->delete_this(); } 121 | }; 122 | 123 | WSAOVERLAPPED overlapped = { 0 }; 124 | WSABUF buffer; 125 | SOCKET sock; 126 | Type type; 127 | ErrorHandler error; 128 | 129 | typedef std::unique_ptr Ptr; 130 | std::list::iterator it; 131 | 132 | Operation(SOCKET sock, Type type, const void *buffer, size_t len, ErrorHandler error) 133 | : overlapped{ 0 } 134 | , buffer{ (unsigned)len, (char*)buffer } 135 | , sock(sock) 136 | , type(type), error(error) 137 | {} 138 | void delete_this(); 139 | }; 140 | struct Accept : public Operation 141 | { 142 | AcceptHandler handler; 143 | SOCKET client_sock; 144 | static const DWORD addr_len = (DWORD)sizeof(sockaddr_storage) + 16; 145 | char accept_buffer[addr_len + addr_len]; 146 | char remote_addr_buf[sizeof(sockaddr_storage) + 16]; 147 | Accept(SOCKET sock, AcceptHandler handler, ErrorHandler error); 148 | ~Accept(); 149 | }; 150 | struct Recv : public Operation 151 | { 152 | SendHandler handler; 153 | 154 | Recv(SOCKET sock, void *buffer, size_t len, SendHandler handler, ErrorHandler error) 155 | : Operation(sock, RECV, buffer, len, error), handler(handler) 156 | {} 157 | }; 158 | struct Send : public Operation 159 | { 160 | SendHandler handler; 161 | 162 | Send(SOCKET sock, const void *buffer, size_t len, SendHandler handler, ErrorHandler error) 163 | : Operation(sock, SEND, buffer, len, error), handler(handler) 164 | {} 165 | }; 166 | struct SendAll : public Operation 167 | { 168 | SendHandler handler; 169 | size_t len; 170 | size_t sent; 171 | 172 | SendAll(SOCKET sock, const void *buffer, size_t len, SendHandler handler, ErrorHandler error) 173 | : Operation(sock, SEND_ALL, buffer, len, error) 174 | , handler(handler) 175 | , len(len), sent(0) 176 | {} 177 | }; 178 | CompletionPort iocp; 179 | std::mutex mutex; 180 | std::atomic running; 181 | std::list inprogess_operations; 182 | void iocp_loop(); 183 | /**Prepare to start some operation for a socket. 184 | * Makes sure the socket is associated, checks running and updates inprogess_operations. 185 | * If AsyncIo is exiting, invokes error with AsyncAborted, then returns false. 186 | */ 187 | bool start_operation(SOCKET socket, ErrorHandler error); 188 | void send_all_next(std::unique_ptr send, size_t sent); 189 | #else 190 | #error No AsyncIo method defined 191 | #endif 192 | 193 | /**Calls the error handler with an active AsyncAborted exception.*/ 194 | void do_abort(const ErrorHandler &error) 195 | { 196 | try { throw AsyncAborted(); } 197 | catch (const AsyncAborted &e) { call_error(e, error); } 198 | } 199 | /**Calls the error handler with the current active exception.*/ 200 | void call_error(const std::exception &e, const ErrorHandler &handler); 201 | 202 | }; 203 | } 204 | -------------------------------------------------------------------------------- /source/headers/Accept.cpp: -------------------------------------------------------------------------------- 1 | #include "headers/Accept.hpp" 2 | #include "core/ParserUtils.hpp" 3 | namespace http 4 | { 5 | Accept::Accept(const char *begin, const char *end) 6 | : types() 7 | { 8 | parse(begin, end); 9 | } 10 | 11 | std::string Accept::preferred(const std::vector &_types)const 12 | { 13 | const Type *best = nullptr; 14 | const std::string *best_mime = nullptr; 15 | for (auto &type : _types) 16 | { 17 | auto accepted = find(type); 18 | if (accepted) 19 | { 20 | if (!best_mime || best->better_quality(*accepted)) 21 | { 22 | best = accepted; 23 | best_mime = &type; 24 | } 25 | } 26 | } 27 | if (best_mime) return *best_mime; 28 | else throw NotAcceptable(_types); 29 | } 30 | 31 | const Accept::Type *Accept::find(const std::string &type)const 32 | { 33 | const Type *best = nullptr; 34 | auto sep = type.find('/'); 35 | if (sep == std::string::npos) 36 | throw std::invalid_argument("Invalid mime type: " + type); 37 | auto group = type.substr(0, sep); 38 | auto subtype = type.substr(sep + 1); 39 | for (auto &x : types) 40 | { 41 | if (x.matches(group, subtype)) 42 | { 43 | //for equal quality x/y overrides x/* which overrides */* and then left to right 44 | if (!best || best->better_quality(x)) 45 | { 46 | best = &x; 47 | } 48 | } 49 | } 50 | return best; 51 | } 52 | 53 | void Accept::parse(const char *begin, const char *end) 54 | { 55 | // RFC7231 56 | // Accept = #( media-range [ accept-params ] ) 57 | // media-range = ("*/*" 58 | // / (type "/" "*") 59 | // / (type "/" subtype) 60 | // ) *(OWS ";" OWS parameter) 61 | // accept-params = weight *(accept-ext) 62 | // accept-ext = OWS ";" OWS token["=" (token / quoted-string)] 63 | // type = token 64 | // subtype = token 65 | // weight = OWS ";" OWS "q=" qvalue 66 | // qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] ) 67 | // parameter = token "=" ( token / quoted-string ) 68 | // 69 | // NOTE: weight ("q=") must take priority over parameter. 70 | // parameter is part of the media type, but q= and onwards are HTTP. 71 | auto p = begin; 72 | while (p < end) 73 | { 74 | Type type; 75 | type.quality = 1; //default 76 | //type 77 | if (*p == '*') //"*/*" 78 | { 79 | if (end - begin < 3) throw ParserError("Invalid Accept header"); 80 | if (p[1] != '/' || p[2] != '*') throw ParserError("Invalid Accept header"); 81 | p += 3; 82 | type.type = '*'; 83 | type.subtype = '*'; 84 | } 85 | else 86 | { 87 | auto p2 = parser::read_token(p, end); 88 | if (p2 == end || *p2 != '/') throw ParserError("Invalid Accept header"); 89 | type.type.assign(p, p2); 90 | 91 | p = p2 + 1; 92 | if (p == end) throw ParserError("Invalid Accept header"); 93 | if (*p == '*') 94 | { 95 | type.subtype = '*'; 96 | ++p; 97 | } 98 | else 99 | { 100 | p2 = parser::read_token(p, end); 101 | type.subtype.assign(p, p2); 102 | p = p2; 103 | } 104 | } 105 | //params 106 | while (true) 107 | { 108 | p = parser::skip_ows(p, end); 109 | if (p == end || *p == ',') break; 110 | else if (*p != ';' || ++p == end) throw ParserError("Invalid Accept header"); 111 | 112 | if (end - p > 2 && p[0] == 'q' && p[1] == '=') 113 | { 114 | //end of media type params. quality, then accept params. 115 | p += 2; 116 | if (p == end) throw ParserError("Expected quality value"); 117 | if (*p == '0') //0 plus 3 optional decimals 118 | { 119 | auto p2 = p; 120 | type.quality = 0; 121 | if (++p2 != end && *p2 == '.') 122 | { 123 | if (++p2 != end && parser::is_digit(*p2)) 124 | { 125 | type.quality += (*p2 - '0') * 0.1f; 126 | if (++p2 != end && parser::is_digit(*p2)) 127 | { 128 | type.quality += (*p2 - '0') * 0.01f; 129 | if (++p2 != end && parser::is_digit(*p2)) 130 | { 131 | type.quality += (*p2 - '0') * 0.001f; 132 | ++p2; 133 | } 134 | } 135 | } 136 | } 137 | 138 | p = p2; 139 | } 140 | else if (*p == '1') //1 plus 3 optional zeros 141 | { 142 | type.quality = 1; 143 | if (++p != end && *p == '.') 144 | { 145 | if (++p != end && *p == '0') 146 | { 147 | if (++p != end && *p == '0') 148 | { 149 | if (++p != end && *p == '0') 150 | { 151 | ++p; 152 | } 153 | } 154 | } 155 | } 156 | } 157 | else throw ParserError("Invalid quality value"); 158 | 159 | while (true) 160 | { 161 | p = parser::skip_ows(p, end); 162 | if (p == end || *p == ',') break; 163 | else if (*p != ';' || ++p == end) throw ParserError("Invalid Accept header"); 164 | 165 | p = parser::skip_ows(p, end); 166 | std::string name, value; 167 | auto p2 = parser::read_token(p, end); 168 | if (p2 == end || *p2 != '=') throw ParserError("Invalid media type accept-ext name"); 169 | name.assign(p, p2); 170 | p = parser::read_token_or_qstring(p2 + 1, end, &value); 171 | // TODO: Store param 172 | } 173 | break; 174 | } 175 | else 176 | { 177 | std::string name, value; 178 | auto p2 = parser::read_token(p, end); 179 | if (p2 == end || *p2 != '=') throw ParserError("Invalid media type parameter name"); 180 | name.assign(p, p2); 181 | p = parser::read_token_or_qstring(p2 + 1, end, &value); 182 | // TODO: Store param 183 | } 184 | } 185 | types.push_back(std::move(type)); 186 | p = parser::read_list_sep(p, end); 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /source/Url.cpp: -------------------------------------------------------------------------------- 1 | #include "Url.hpp" 2 | #include 3 | namespace http 4 | { 5 | namespace 6 | { 7 | static const std::string EMPTY_STRING; 8 | static const Url::QueryParamList EMPTY_LIST; 9 | 10 | char hex_value(char c) 11 | { 12 | if (c >= '0' && c <= '9') return (char)(c - '0'); 13 | if (c >= 'a' && c <= 'f') return (char)(c - 'a' + 10); 14 | if (c >= 'A' && c <= 'F') return (char)(c - 'A' + 10); 15 | throw std::runtime_error("Invalid percent encoding hex value"); 16 | } 17 | char hex_value(char c1, char c2) 18 | { 19 | return (char)((hex_value(c1) << 4) | hex_value(c2)); 20 | } 21 | char hex_chr(char c) 22 | { 23 | if (c < 10) return (char)('0' + c); 24 | else return (char)('A' + c - 10); 25 | } 26 | } 27 | 28 | std::string url_decode(const std::string &str) 29 | { 30 | std::string out; 31 | out.reserve(str.size()); 32 | for (size_t i = 0; i < str.size();) 33 | { 34 | if (str[i] == '%' && i + 2 < str.size()) 35 | { 36 | out += hex_value(str[i + 1], str[i + 2]); 37 | i += 3; 38 | } 39 | else 40 | { 41 | out += str[i]; 42 | ++i; 43 | } 44 | } 45 | return out; 46 | } 47 | 48 | std::string url_decode_query(const std::string &str) 49 | { 50 | std::string out; 51 | out.reserve(str.size()); 52 | for (size_t i = 0; i < str.size();) 53 | { 54 | if (str[i] == '+') 55 | { 56 | out += ' '; 57 | ++i; 58 | } 59 | else if (str[i] == '%' && i + 2 < str.size()) 60 | { 61 | out += hex_value(str[i + 1], str[i + 2]); 62 | i += 3; 63 | } 64 | else 65 | { 66 | out += str[i]; 67 | ++i; 68 | } 69 | } 70 | return out; 71 | } 72 | 73 | bool uri_unreserved_chr(char c) 74 | { 75 | if (c >= 'A' && c <= 'Z') return true; 76 | if (c >= 'a' && c <= 'z') return true; 77 | if (c >= '0' && c <= '9') return true; 78 | return c == '-' || c == '_' || c == '.' || c == '~'; 79 | } 80 | std::string url_encode_path(const std::string &str) 81 | { 82 | std::string out; 83 | out.reserve(str.size()); 84 | for (auto c : str) 85 | { 86 | if (c == '/') out += '/'; 87 | else if (uri_unreserved_chr(c)) out += c; 88 | else 89 | { 90 | out += '%'; 91 | out += hex_chr((char)(((unsigned char)c) >> 4)); 92 | out += hex_chr((char)(c & 0x0F)); 93 | } 94 | } 95 | return out; 96 | } 97 | 98 | std::string url_encode_query(const std::string &str) 99 | { 100 | std::string out; 101 | out.reserve(str.size()); 102 | for (auto c : str) 103 | { 104 | if (c == ' ') out += '+'; 105 | else if (uri_unreserved_chr(c)) out += c; 106 | else 107 | { 108 | out += '%'; 109 | out += hex_chr((char)(c >> 4)); 110 | out += hex_chr((char)(c & 0x0F)); 111 | } 112 | } 113 | return out; 114 | } 115 | 116 | Url Url::parse_request(const std::string &str) 117 | { 118 | Url url; 119 | // /path/string[?query=param[&another=param]] 120 | if (str.empty() || str[0] != '/') throw UrlError(str, "Request URL must start with a '/'"); 121 | 122 | auto query_start = str.find('?'); 123 | if (query_start == std::string::npos) 124 | { 125 | url.path = url_decode(str); 126 | } 127 | else 128 | { 129 | url.path = url_decode(str.substr(0, query_start)); 130 | auto i = query_start + 1; 131 | //split up the query string 132 | while (i < str.size()) 133 | { 134 | //each key-value pair 135 | auto j = i; 136 | std::string key, value; 137 | for (;; ++j) 138 | { 139 | if (j == str.size() || str[j] == '&') 140 | { //valueless param (e.g. "?cache&id=-5" 141 | key = str.substr(i, j - i); 142 | break; 143 | } 144 | else if (str[j] == '=') 145 | { //parse value part 146 | key = str.substr(i, j - i); 147 | auto k = ++j; 148 | for (; k != str.size() && str[k] != '&'; ++k); 149 | value = str.substr(j, k - j); 150 | j = k; 151 | break; 152 | } 153 | } 154 | 155 | url.query_params[url_decode_query(key)].push_back(url_decode_query(value)); 156 | i = j + 1; 157 | } 158 | } 159 | 160 | return url; 161 | } 162 | 163 | Url::Url() 164 | : protocol(), host(), port(0), path(), query_params() 165 | {} 166 | 167 | bool Url::has_query_param(const std::string &name)const 168 | { 169 | return query_params.find(name) != query_params.end(); 170 | } 171 | 172 | const std::string &Url::query_param(const std::string &name)const 173 | { 174 | auto it = query_params.find(name); 175 | if (it != query_params.end()) return it->second.front(); 176 | else return EMPTY_STRING; 177 | } 178 | 179 | const Url::QueryParamList& Url::query_param_list(const std::string &name)const 180 | { 181 | auto it = query_params.find(name); 182 | if (it != query_params.end()) return it->second; 183 | else return EMPTY_LIST; 184 | } 185 | 186 | void Url::encode_request(std::ostream &os)const 187 | { 188 | if (path.empty() || path[0] != '/') throw std::runtime_error("URL path must start with '/'"); 189 | os << url_encode_path(path); 190 | bool first_param = true; 191 | for (auto ¶m : query_params) 192 | { 193 | for (auto &value : param.second) 194 | { 195 | if (first_param) 196 | { 197 | os << '?'; 198 | first_param = false; 199 | } 200 | else os << '&'; 201 | os << url_encode_query(param.first); 202 | if (!param.second.empty()) 203 | { 204 | os << '='; 205 | os << url_decode_query(value); 206 | } 207 | } 208 | } 209 | } 210 | 211 | std::string Url::encode_request()const 212 | { 213 | std::stringstream ss; 214 | encode_request(ss); 215 | return ss.str(); 216 | } 217 | 218 | void Url::encode(std::ostream &os)const 219 | { 220 | if (!protocol.empty()) 221 | { 222 | os << protocol << ':'; 223 | if (host.empty()) throw std::runtime_error("URL cant have protocol without host"); 224 | } 225 | if (!host.empty()) 226 | { 227 | os << "//" << host; 228 | } 229 | if (port) 230 | { 231 | if (host.empty()) throw std::runtime_error("URL can not have port without host"); 232 | os << ':' << port; 233 | } 234 | encode_request(os); 235 | } 236 | 237 | std::string Url::encode()const 238 | { 239 | std::stringstream ss; 240 | encode(ss); 241 | return ss.str(); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /source/net/TcpSocket.cpp: -------------------------------------------------------------------------------- 1 | #include "net/TcpSocket.hpp" 2 | #include "net/Os.hpp" 3 | #include "net/Net.hpp" 4 | #include "net/SocketUtils.hpp" 5 | #include 6 | #include 7 | namespace http 8 | { 9 | TcpSocket::TcpSocket() 10 | : socket(INVALID_SOCKET), _host(), _port(0) 11 | { 12 | } 13 | TcpSocket::TcpSocket(const std::string & host, uint16_t port) 14 | : socket(INVALID_SOCKET), _host(), _port(0) 15 | { 16 | connect(host, port); 17 | } 18 | TcpSocket::TcpSocket(SOCKET socket, const sockaddr *address) 19 | : socket(INVALID_SOCKET), _host(), _port(0) 20 | { 21 | set_socket(socket, address); 22 | } 23 | TcpSocket::~TcpSocket() 24 | { 25 | close(); 26 | } 27 | 28 | TcpSocket::TcpSocket(TcpSocket &&mv) 29 | : socket(), _host(std::move(mv._host)), _port(mv._port) 30 | { 31 | socket = mv.socket; 32 | mv.socket = INVALID_SOCKET; 33 | } 34 | TcpSocket& TcpSocket::operator = (TcpSocket &&mv) 35 | { 36 | socket = mv.socket; 37 | mv.socket = INVALID_SOCKET; 38 | _host = std::move(mv._host); 39 | _port = mv._port; 40 | return *this; 41 | } 42 | 43 | void TcpSocket::set_socket(SOCKET _socket, const sockaddr *address) 44 | { 45 | if (socket != INVALID_SOCKET) throw std::runtime_error("Already connected"); 46 | if (address->sa_family == AF_INET) 47 | { 48 | auto addr4 = (sockaddr_in*)address; 49 | char ipstr[INET_ADDRSTRLEN]; 50 | inet_ntop(AF_INET, &addr4->sin_addr, ipstr, sizeof ipstr); 51 | _port = ntohs(addr4->sin_port); 52 | _host = ipstr; 53 | } 54 | else if (address->sa_family == AF_INET6) 55 | { 56 | auto addr6 = (sockaddr_in6*)address; 57 | char ipstr[INET6_ADDRSTRLEN]; 58 | inet_ntop(AF_INET6, &addr6->sin6_addr, ipstr, sizeof ipstr); 59 | _port = ntohs(addr6->sin6_port); 60 | _host = ipstr; 61 | } 62 | else throw std::runtime_error("Unknown socket family"); 63 | socket = _socket; 64 | } 65 | void TcpSocket::connect(const std::string & host, uint16_t port) 66 | { 67 | struct AddrInfoPtr 68 | { 69 | addrinfo *p; 70 | AddrInfoPtr() : p(nullptr) {} 71 | ~AddrInfoPtr() { freeaddrinfo (p); } 72 | }; 73 | 74 | if (socket != INVALID_SOCKET) throw std::runtime_error("Already connected"); 75 | 76 | auto port_str = std::to_string(port); 77 | AddrInfoPtr result; 78 | 79 | addrinfo hints = {}; 80 | hints.ai_family = AF_INET; 81 | hints.ai_socktype = SOCK_STREAM; 82 | hints.ai_protocol = IPPROTO_TCP; 83 | 84 | auto ret = getaddrinfo(host.c_str(), port_str.c_str(), &hints, &result.p); 85 | if (ret) throw ConnectionError(host, port); 86 | assert(result.p); 87 | 88 | int last_error = 0; 89 | for (auto p = result.p; p; p = p->ai_next) 90 | { 91 | socket = ::socket(p->ai_family, p->ai_socktype, p->ai_protocol); 92 | if (socket == INVALID_SOCKET) continue; 93 | 94 | if (::connect(socket, p->ai_addr, (int)p->ai_addrlen) != SOCKET_ERROR) 95 | { 96 | this->_host = host; 97 | this->_port = port; 98 | return; 99 | } 100 | else 101 | { 102 | last_error = last_net_error(); 103 | closesocket(socket); 104 | continue; 105 | } 106 | 107 | } 108 | //TODO: Better error report if there were multiple possible address 109 | throw ConnectionError(last_error, host, port); 110 | } 111 | void TcpSocket::set_non_blocking(bool non_blocking) 112 | { 113 | http::set_non_blocking(socket, non_blocking); 114 | } 115 | std::string TcpSocket::address_str() const 116 | { 117 | if (socket != INVALID_SOCKET) return _host + ":" + std::to_string(_port); 118 | else return "Not connected"; 119 | } 120 | void TcpSocket::close() 121 | { 122 | if (socket != INVALID_SOCKET) 123 | { 124 | closesocket(socket); 125 | socket = INVALID_SOCKET; 126 | } 127 | } 128 | void TcpSocket::disconnect() 129 | { 130 | if (socket != INVALID_SOCKET) 131 | { 132 | shutdown(socket, SD_SEND); 133 | fd_set set; 134 | FD_ZERO(&set); 135 | FD_SET(socket, &set); 136 | timeval timeout = {1, 0}; 137 | // Give the remote a chance to send its FIN response 138 | select((int)socket + 1, &set, nullptr, nullptr, &timeout); 139 | //char buffer[1]; 140 | //FD_ISSET(socket, &set) ::recv(socket, buffer, 1, 0) == 0) 141 | 142 | closesocket(socket); 143 | socket = INVALID_SOCKET; 144 | } 145 | } 146 | size_t TcpSocket::recv(void * buffer, size_t len) 147 | { 148 | if (len > std::numeric_limits::max()) len = std::numeric_limits::max(); 149 | auto ret = ::recv(socket, (char*)buffer, (int)len, 0); 150 | if (ret < 0) 151 | { 152 | auto err = last_net_error(); 153 | throw SocketError(err); 154 | } 155 | return (size_t)ret; 156 | } 157 | size_t TcpSocket::send(const void * buffer, size_t len) 158 | { 159 | if (len > std::numeric_limits::max()) len = std::numeric_limits::max(); 160 | auto ret = ::send(socket, (const char*)buffer, (int)len, 0); 161 | if (ret < 0) 162 | { 163 | auto err = last_net_error(); 164 | throw SocketError(err); 165 | } 166 | return (size_t)ret; 167 | } 168 | 169 | bool TcpSocket::check_recv_disconnect() 170 | { 171 | fd_set set; 172 | FD_ZERO(&set); 173 | FD_SET(socket, &set); 174 | timeval timeout = {0, 0}; 175 | auto ret = ::select((int)socket + 1, &set, nullptr, nullptr, &timeout); 176 | if (ret == 0) return false; 177 | else if (ret < 0) 178 | { 179 | auto err = last_net_error(); 180 | throw SocketError(err); 181 | } 182 | assert(ret == 1); 183 | 184 | char buffer[1]; 185 | if (recv(buffer, sizeof(buffer)) == 0) return true; 186 | else throw std::runtime_error("Received unexpected data."); 187 | } 188 | void TcpSocket::async_disconnect(AsyncIo &aio, 189 | std::function handler, AsyncIo::ErrorHandler error) 190 | { 191 | assert(socket != INVALID_SOCKET); 192 | shutdown(socket, SD_SEND); 193 | 194 | static char buffer[1]; 195 | aio.recv(socket, buffer, 1, 196 | [this, handler](size_t) 197 | { 198 | closesocket(socket); 199 | socket = INVALID_SOCKET; 200 | handler(); 201 | }, 202 | [this, error]() 203 | { 204 | closesocket(socket); 205 | socket = INVALID_SOCKET; 206 | error(); 207 | }); 208 | } 209 | void TcpSocket::async_recv(AsyncIo &aio, void *buffer, size_t len, 210 | AsyncIo::RecvHandler handler, AsyncIo::ErrorHandler error) 211 | { 212 | aio.recv(socket, buffer, len, handler, error); 213 | } 214 | void TcpSocket::async_send(AsyncIo &aio, const void *buffer, size_t len, 215 | AsyncIo::SendHandler handler, AsyncIo::ErrorHandler error) 216 | { 217 | aio.send(socket, buffer, len, handler, error); 218 | } 219 | void TcpSocket::async_send_all(AsyncIo &aio, const void *buffer, size_t len, 220 | AsyncIo::SendHandler handler, AsyncIo::ErrorHandler error) 221 | { 222 | aio.send_all(socket, buffer, len, handler, error); 223 | } 224 | } 225 | --------------------------------------------------------------------------------