0
\$\begingroup\$

I'm writing a HTTP server and client in C++20. It started with a simple API, string call(string url) which worked great for another project. The API uses implicit conversions a lot . Conversions from x-www-form-urlencoded are done automatically when assigning the message a map<string,string>.

The part I want to ask feedback about, is how to handle large bodies. Large bodies cannot be kept in entirety in a vector for example, without consuming large amounts of memory and multiple slow copies.

The API now supports both iostream-based message bodies and implicitly-converted in-memory bodies. The implementation always downloads a single 32k chunk first, which is enough for the caller to inspect the message and decide wether the message should be handled using a stream, or by having the message entirely in-memory.

I share only the API and some examples of usage. I can post the entire library on a public git repo if needed. The code is complete however partially untested as I am still working out the API.

The examples show both streamed messages as well as having the entire message in a variable. The examples demonstrate the implicit conversions of the Message object to obtain the message body in map, json or other form.

(PS. For JSON, the nlohmann::json library is used).

#include <string>
#include <vector>
#include <map>
#include <iostream>
#include <fstream>

#include "nlohmann/json.hpp"

using namespace std;
using namespace nlohmann;

namespace HTTPS{
    const int OK = 200;
    const int BAD_REQUEST = 400;
    
    const map<int, string> STATUS_MESSAGE = {
        {OK, "OK"},
        {BAD_REQUEST, "Bad Request"} // etc.
    };

    enum ContentType{
        HTML,
        JPEG,
        Form // etc.
    };

    const map<ContentType, string> CONTENT_TYPE_VALUES = {
        {JPEG, "image/jpeg"},
        {HTML, "text/html"} // etc..
    };

    class Message{

    public:
        // Message metadata
        string host;
        int port = 433;
        int status = OK;
        string method;
        map<string, string> headers;
        vector<string> path;
        map<string,string> query;
        ContentType contentType = HTML;

        // Message body
        vector<char> body;
        iostream* bodyStream;  // When populated, this stream will be used instead of the body vector

        // Constructors
        ~Message();
        Message();
        Message(string input, ContentType pContentType = HTML);
        Message(vector<char> input, ContentType pContentType = JPEG);
        Message(const char* input);
        Message(json input); // json from nlohmann::json library
        Message(map<string,string> input, ContentType pContentType = Form);

        // Operators
        operator string();
        operator json();
        operator vector<char>();
        operator map<string, string>();

        void readRemainder();

        // Any methods and fields not used by caller are not shown here. There are some public methods and fields used by the Client and Server implementations
    };
    
    namespace Server{
        void init(int port, map<string, Message (*)(Message&)> pResourceHandlers);
    }
    namespace Client{
        Message call(Message& request);
    }
    
    // Library usage examples
    extern "C" int main(){
        {
            // 1. Upload file, download JSON response
            Message request;
            request.host = "google.com";
            request.path = {"api", "upload-file"};
            ifstream file("bigfile.zip");
            request.bodyStream = (iostream*) &file;
            auto response = Client::call(request);
            if(response.status != 200){
                cerr << "Not good! Status: " << response.status << endl;
            }
            json body = response;
            cout << body["result"]["status"] << endl;
        }
        {
            // 2. POST www-form-data, download file
            map<string,string> body = {
                {"name", "Johnny"}
            };
            Message request = body;
            request.host = "google.com";
            request.path = {"api", "download"};
            auto response = Client::call(request);
            ofstream file("mybigfile.tar.gz");
            response.bodyStream = (iostream*) &file;
            response.readRemainder();
        }
        {
            // 3. Serve file after recieve JSON post
            Server::init(443, {
                {"api-download-file", [](Message& request) -> Message{      
                    json params = request;
                    Message response;
                    if(params["filename"] == "something"){
                        response = "This is not ok.";
                        response.status = BAD_REQUEST;
                        return response;
                    }
                    ifstream file(params["filename"]); // Input would sanitized in actual code of course. Just an example of the API.
                    response.bodyStream = (iostream*) &file;
                    return response;
                }}
            });
        }
        return 1 + 1 == 2 ? 0 : 1;
    }

}

Note: If I need to post the entire library source, please let me know.

I am mostly interested in feedback about the API.

\$\endgroup\$
4
  • \$\begingroup\$ Welcome to the Code Review Community. Please post more of the code, we only review code that is working as intended, there are other sites that will help you design your API. Please read Where can I get help? and How do I ask a good question?. \$\endgroup\$
    – pacmaninbw
    Jul 24 at 12:57
  • \$\begingroup\$ Sorry to be blunt, but any library that includes a using namespace std into the global namespace will be instantly nuked form my harddrive. Please read here why you shouldn't do it, especially not in a library \$\endgroup\$ Jul 28 at 9:35
  • \$\begingroup\$ @pacmaninbw Ok, I will post the complete library, but it will take a month or two since it is developed alongside the main project. Shall I delete this question, or edit it once I have all the code ready? \$\endgroup\$
    – LePain
    Jul 30 at 9:51
  • \$\begingroup\$ @infinitezero Good point, I agree. I will adjust the headers once I post the full code here to avoid the nuking. \$\endgroup\$
    – LePain
    Jul 30 at 9:51

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.