diff --git a/client_http.hpp b/client_http.hpp new file mode 100644 index 0000000..eb4506b --- /dev/null +++ b/client_http.hpp @@ -0,0 +1,198 @@ +#ifndef CLIENT_WS_HPP +#define CLIENT_WS_HPP + +#include + +#include +#include +#include +#include + +namespace SimpleWeb { + template + class ClientBase { + public: + class Response { + friend class ClientBase; + + public: + std::string http_version, status_code; + + std::istream content; + + std::unordered_map header; + + private: + boost::asio::streambuf streambuf; + + Response(): content(&streambuf) {}; + }; + + //TODO add header parameters + std::shared_ptr request(const std::string& request_type, const std::string& path="/") { + std::stringstream empty_ss; + return request(request_type, path, empty_ss); + } + + std::shared_ptr request(const std::string& request_type, const std::string& path, std::ostream& content) { + std::string corrected_path=path; + if(corrected_path=="") + corrected_path="/"; + + content.seekp(0, std::ios::end); + size_t content_length=content.tellp(); + content.seekp(0, std::ios::beg); + + boost::asio::streambuf write_buffer; + std::ostream write_stream(&write_buffer); + write_stream << request_type << " " << corrected_path << " HTTP/1.1\r\n"; + write_stream << "Host: " << host << "\r\n"; + if(content_length>0) + write_stream << "Content-Length: " << std::to_string(content_length) << "\r\n"; + write_stream << "\r\n"; + if(content_length>0) + write_stream << content.rdbuf(); + + std::shared_ptr response(new Response()); + + try { + connect(); + + boost::asio::write(*socket, write_buffer); + + size_t bytes_transferred = boost::asio::read_until(*socket, response->streambuf, "\r\n\r\n"); + + size_t num_additional_bytes=response->streambuf.size()-bytes_transferred; + + parse_response_header(response, response->content); + + if(response->header.count("Content-Length")>0) { + boost::asio::read(*socket, response->streambuf, + boost::asio::transfer_exactly(stoull(response->header["Content-Length"])-num_additional_bytes)); + } + else if(response->header.count("Transfer-Encoding")>0 && response->header["Transfer-Encoding"]=="chunked") { + boost::asio::streambuf streambuf; + std::ostream content(&streambuf); + + size_t length; + do { + size_t bytes_transferred = boost::asio::read_until(*socket, response->streambuf, "\r\n"); + std::string line; + getline(response->content, line); + bytes_transferred-=line.size()+1; + line.pop_back(); + length=stoull(line, 0, 16); + + size_t num_additional_bytes=response->streambuf.size()-bytes_transferred; + + if((2+length)>num_additional_bytes) { + boost::asio::read(*socket, response->streambuf, + boost::asio::transfer_exactly(2+length-num_additional_bytes)); + } + + std::string buffer; + buffer.resize(length); + response->content.read(&buffer[0], length); + content.write(&buffer[0], length); + + //Remove "\r\n" + response->content.get(); + response->content.get(); + } while(length>0); + + std::ostream response_content_output_stream(&response->streambuf); + response_content_output_stream << content.rdbuf(); + } + } + catch(const std::exception& e) { + socket_error=true; + throw std::invalid_argument(e.what()); + } + + return response; + } + + protected: + boost::asio::io_service asio_io_service; + boost::asio::ip::tcp::endpoint asio_endpoint; + boost::asio::ip::tcp::resolver asio_resolver; + + std::shared_ptr socket; + bool socket_error; + + std::string host; + unsigned short port; + + ClientBase(const std::string& host_port, unsigned short default_port) : + asio_resolver(asio_io_service), socket_error(false) { + std::regex e("^([^:/]+):?([0-9]*)$"); + + std::smatch sm; + + if(std::regex_match(host_port, sm, e)) { + host=sm[1]; + port=default_port; + if(sm[2]!="") + port=(unsigned short)std::stoul(sm[2]); + asio_endpoint=boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port); + } + else { + throw std::invalid_argument("Error parsing host_port"); + } + } + + virtual void connect()=0; + + void parse_response_header(std::shared_ptr response, std::istream& stream) const { + std::smatch sm; + + //Parse the first line + std::string line; + getline(stream, line); + line.pop_back(); + + std::regex e("^HTTP/([^ ]*) (.*)$"); + if(std::regex_match(line, sm, e)) { + response->http_version=sm[1]; + response->status_code=sm[2]; + + e="^([^:]*): ?(.*)$"; + //Parse the rest of the header + bool matched; + do { + getline(stream, line); + line.pop_back(); + matched=std::regex_match(line, sm, e); + if(matched) { + response->header[sm[1]]=sm[2]; + } + + } while(matched==true); + } + } + }; + + template + class Client : public ClientBase {}; + + typedef boost::asio::ip::tcp::socket HTTP; + + template<> + class Client : public ClientBase { + public: + Client(const std::string& server_port_path) : ClientBase::ClientBase(server_port_path, 80) { + socket=std::make_shared(asio_io_service); + }; + + private: + void connect() { + if(socket_error || !socket->is_open()) { + boost::asio::ip::tcp::resolver::query query(host, std::to_string(port)); + boost::asio::connect(*socket, asio_resolver.resolve(query)); + socket_error=false; + } + } + }; +} + +#endif /* CLIENT_WS_HPP */ \ No newline at end of file diff --git a/client_https.hpp b/client_https.hpp new file mode 100644 index 0000000..522d396 --- /dev/null +++ b/client_https.hpp @@ -0,0 +1,39 @@ +#ifndef CLIENT_WSS_HPP +#define CLIENT_WSS_HPP + +#include "client_http.hpp" +#include + +namespace SimpleWeb { + typedef boost::asio::ssl::stream HTTPS; + + template<> + class Client : public ClientBase { + public: + Client(const std::string& server_port_path, bool verify_certificate=true) : ClientBase::ClientBase(server_port_path, 443), + asio_context(boost::asio::ssl::context::sslv23) { + if(verify_certificate) + asio_context.set_verify_mode(boost::asio::ssl::verify_peer); + else + asio_context.set_verify_mode(boost::asio::ssl::verify_none); + + socket=std::make_shared(asio_io_service, asio_context); + }; + + private: + boost::asio::ssl::context asio_context; + + void connect() { + if(socket_error || !socket->lowest_layer().is_open()) { + boost::asio::ip::tcp::resolver::query query(host, std::to_string(port)); + boost::asio::connect(socket->lowest_layer(), asio_resolver.resolve(query)); + + socket->handshake(boost::asio::ssl::stream_base::client); + + socket_error=false; + } + } + }; +} + +#endif /* CLIENT_WSS_HPP */ \ No newline at end of file