From 401b9bbc699d74abfa74bbc093651f98076abca2 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 12 Jul 2014 21:19:03 +0200 Subject: [PATCH] added HTTPS support --- main.cpp => main_http.cpp | 4 +- main_https.cpp | 125 ++++++++++++++++++++++++++++++++++ server.hpp => server_http.hpp | 58 +++++++++------- server_https.hpp | 52 ++++++++++++++ 4 files changed, 214 insertions(+), 25 deletions(-) rename main.cpp => main_http.cpp (98%) create mode 100644 main_https.cpp rename server.hpp => server_http.hpp (91%) create mode 100644 server_https.hpp diff --git a/main.cpp b/main_http.cpp similarity index 98% rename from main.cpp rename to main_http.cpp index 187305f..e143c35 100644 --- a/main.cpp +++ b/main_http.cpp @@ -1,4 +1,4 @@ -#include "server.hpp" +#include "server_http.hpp" //Added for the json-example: #include @@ -12,7 +12,7 @@ using namespace SimpleWeb; using namespace boost::property_tree; int main() { - //HTTP-server at port 8080 using 4 threads + //HTTPS-server at port 8080 using 4 threads Server httpserver(8080, 4); //Add resources using regular expression for path, a method-string, and an anonymous function diff --git a/main_https.cpp b/main_https.cpp new file mode 100644 index 0000000..6c6787e --- /dev/null +++ b/main_https.cpp @@ -0,0 +1,125 @@ +#include "server_https.hpp" + +//Added for the json-example: +#include +#include + +#include + +using namespace std; +using namespace SimpleWeb; +//Added for the json-example: +using namespace boost::property_tree; + +int main() { + //HTTPS-server at port 8080 using 4 threads + Server httpserver(8080, 4, "server.crt", "server.key"); + + //Add resources using regular expression for path, a method-string, and an anonymous function + //POST-example for the path /string, responds the posted string + httpserver.resources["^/string/?$"]["POST"]=[](ostream& response, const Request& request, const smatch& path_match) { + //Retrieve string from istream (*request.content) + stringstream ss; + *request.content >> ss.rdbuf(); + string content=ss.str(); + + response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content; + }; + + //POST-example for the path /json, responds firstName+" "+lastName from the posted json + //Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing + //Example posted json: + //{ + // "firstName": "John", + // "lastName": "Smith", + // "age": 25 + //} + httpserver.resources["^/json/?$"]["POST"]=[](ostream& response, const Request& request, const smatch& path_match) { + try { + ptree pt; + read_json(*request.content, pt); + + string name=pt.get("firstName")+" "+pt.get("lastName"); + + response << "HTTP/1.1 200 OK\r\nContent-Length: " << name.length() << "\r\n\r\n" << name; + } + catch(exception& e) { + response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what(); + } + }; + + //GET-example for the path /info + //Responds with request-information + httpserver.resources["^/info/?$"]["GET"]=[](ostream& response, const Request& request, const smatch& path_match) { + stringstream content_stream; + content_stream << "

Request:

"; + content_stream << request.method << " " << request.path << " HTTP/" << request.http_version << "
"; + for(auto& header: request.header) { + content_stream << header.first << ": " << header.second << "
"; + } + + //find length of content_stream (length received using content_stream.tellp()) + content_stream.seekp(0, ios::end); + + response << "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n" << content_stream.rdbuf(); + }; + + //GET-example for the path /match/[number], responds with the matched string in path (number) + //For instance a request GET /match/123 will receive: 123 + httpserver.resources["^/match/([0-9]+)/?$"]["GET"]=[](ostream& response, const Request& request, const smatch& path_match) { + string number=path_match[1]; + response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number; + }; + + //Default GET-example. If no other matches, this anonymous function will be called. + //Will respond with content in the web/-directory, and its subdirectories. + //Default file: index.html + //Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server + httpserver.default_resource["^/?(.*)$"]["GET"]=[](ostream& response, const Request& request, const smatch& path_match) { + string filename="web/"; + + string path=path_match[1]; + + //Remove all but the last '.' (so we can't leave the web-directory) + size_t last_pos=path.rfind("."); + size_t current_pos=0; + size_t pos; + while((pos=path.find('.', current_pos))!=string::npos && pos!=last_pos) { + cout << pos << endl; + current_pos=pos; + path.erase(pos, 1); + last_pos--; + } + + filename+=path; + ifstream ifs; + //A simple platform-independent file-or-directory check do not exist, but this works in most of the cases: + if(filename.find('.')==string::npos) { + if(filename[filename.length()-1]!='/') + filename+='/'; + filename+="index.html"; + } + ifs.open(filename, ifstream::in); + + if(ifs) { + ifs.seekg(0, ios::end); + size_t length=ifs.tellg(); + + ifs.seekg(0, ios::beg); + + //The file-content is copied to the response-stream. Should not be used for very large files. + response << "HTTP/1.1 200 OK\r\nContent-Length: " << length << "\r\n\r\n" << ifs.rdbuf(); + + ifs.close(); + } + else { + string content="Could not open file "+filename; + response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << content.length() << "\r\n\r\n" << content; + } + }; + + //Start HTTP-server + httpserver.start(); + + return 0; +} diff --git a/server.hpp b/server_http.hpp similarity index 91% rename from server.hpp rename to server_http.hpp index 7db6e94..2328ef4 100644 --- a/server.hpp +++ b/server_http.hpp @@ -10,9 +10,7 @@ using namespace std; using namespace boost::asio; -namespace SimpleWeb { - typedef ip::tcp::socket HTTP; - +namespace SimpleWeb { struct Request { string method, path, http_version; @@ -24,14 +22,14 @@ namespace SimpleWeb { typedef map > > resource_type; template - class Server { + class ServerBase { public: resource_type resources; resource_type default_resource; - Server(unsigned short port, size_t num_threads=1) : endpoint(ip::tcp::v4(), port), - acceptor(m_io_service, endpoint), num_threads(num_threads) {} + ServerBase(unsigned short port, size_t num_threads=1) : endpoint(ip::tcp::v4(), port), + acceptor(m_io_service, endpoint), num_threads(num_threads) {} void start() { //All resources with default_resource at the end of vector @@ -61,10 +59,11 @@ namespace SimpleWeb { } } - private: + protected: io_service m_io_service; ip::tcp::endpoint endpoint; ip::tcp::acceptor acceptor; + //shared_ptr context; size_t num_threads; vector threads; @@ -72,20 +71,7 @@ namespace SimpleWeb { //Created in start() vector all_resources; - void accept() { - //Create new socket for this connection - //Shared_ptr is used to pass temporary objects to the asynchronous functions - shared_ptr socket(new socket_type(m_io_service)); - - acceptor.async_accept(*socket, [this, socket](const boost::system::error_code& ec) { - //Immediately start accepting a new connection - accept(); - - if(!ec) { - process_request_and_respond(socket); - } - }); - } + virtual void accept() {} void process_request_and_respond(shared_ptr socket) { //Create new read_buffer for async_read_until() @@ -184,6 +170,32 @@ namespace SimpleWeb { } } }; -} -#endif /* SERVER_HPP */ + + template + class Server : public ServerBase {}; + + typedef ip::tcp::socket HTTP; + + template<> + class Server : public ServerBase { + public: + Server(unsigned short port, size_t num_threads=1) : ServerBase::ServerBase(port, num_threads) {}; + + private: + void accept() { + //Create new socket for this connection + //Shared_ptr is used to pass temporary objects to the asynchronous functions + shared_ptr socket(new HTTP(m_io_service)); + acceptor.async_accept(*socket, [this, socket](const boost::system::error_code& ec) { + //Immediately start accepting a new connection + accept(); + + if(!ec) { + process_request_and_respond(socket); + } + }); + } + }; +} +#endif /* SERVER_HPP */ \ No newline at end of file diff --git a/server_https.hpp b/server_https.hpp new file mode 100644 index 0000000..dad46d1 --- /dev/null +++ b/server_https.hpp @@ -0,0 +1,52 @@ +/* + * File: httpsserver.hpp + * Author: eidheim + * + * Created on July 12, 2014, 7:18 PM + */ + +#ifndef HTTPSSERVER_HPP +#define HTTPSSERVER_HPP + +#include "server_http.hpp" +#include + +namespace SimpleWeb { + typedef ssl::stream HTTPS; + + template<> + class Server : public ServerBase { + public: + Server(unsigned short port, size_t num_threads, const string& cert_file, const string& private_key_file) : + ServerBase::ServerBase(port, num_threads), context(ssl::context::sslv23) { + context.use_certificate_chain_file(cert_file); + context.use_private_key_file(private_key_file, ssl::context::pem); + } + + private: + ssl::context context; + + void accept() { + //Create new socket for this connection + //Shared_ptr is used to pass temporary objects to the asynchronous functions + shared_ptr socket(new HTTPS(m_io_service, context)); + + acceptor.async_accept((*socket).lowest_layer(), [this, socket](const boost::system::error_code& ec) { + //Immediately start accepting a new connection + accept(); + + if(!ec) { + (*socket).async_handshake(ssl::stream_base::server, [this, socket](const boost::system::error_code& ec) { + if(!ec) { + process_request_and_respond(socket); + } + }); + } + }); + } + }; +} + + +#endif /* HTTPSSERVER_HPP */ +