// file : web/module -*- C++ -*- // copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC // license : MIT; see accompanying LICENSE file #ifndef WEB_MODULE #define WEB_MODULE #include // move() #include // runtime_error #include #include #include #include #include // uint16_t namespace web { // HTTP status code. // // @@ Define some commonly used constants? // using status_code = std::uint16_t; // This exception is used to signal that the request is invalid // (4XX codes) rather than that it could not be processed (5XX). // By default 400 is returned, which means the request is malformed. // Non empty description of a caught by the module implementation exception // can be sent to client in http response body with // Content-Type:text/html;charset=utf-8 header. // struct invalid_request { status_code status; std::string description; //@@ Maybe optional "try again" link? // invalid_request (status_code s = 400, std::string d = "") : status (s), description (std::move (d)) {} }; // Exception indicating HTTP request/response sequencing error. // For example, trying to change the status code after some // content has already been written. // struct sequence_error: std::runtime_error { sequence_error (std::string d) : std::runtime_error (d) {} }; struct name_value { // These should eventually become string_view's. // std::string name; std::string value; name_value () {} name_value (std::string n, std::string v) : name (std::move (n)), value (std::move (v)) {} }; using name_values = std::vector; class request { public: //@@ Why not pass parameters directly? Lazy parsing? //@@ Why not have something like operator[] for lookup? Probably // in name_values. //@@ Maybe parameter_list() and parameter_map()? // // Throw invalid_request if mime url decode of name or value fail. // virtual const name_values& parameters () = 0; // Throw invalid_request if Cookie header is malformed. // virtual const name_values& cookies () = 0; // Get stream to read request body data. // Throw sequence_error if some unbuffered content is already written. // virtual std::istream& data () = 0; }; class response { public: // Set status code, content type, and get the stream to write // the content to. If the buffer argument is true (default), // then buffer the entire content before sending it as a // response. This allows us to change the status code in // case of an error. // // Specifically, if there is already content in the buffer // and the status code is changed, then the old content is // discarded. If the content was not buffered and the status // is changed, then the sequence_error exception is thrown. // If this exception leaves module::handle(), then the // implementation shall terminate the response in a suitable // but unspecified manner. In particular, there is no guarantee // that the user will be notified of an error or observe the // new status. // virtual std::ostream& content (status_code, const std::string& type, bool buffer = true) = 0; // Set status code without writing any content. // On status change discards buffered output and throw sequence_error // if output were not buffered. // virtual void status (status_code) = 0; // Throw sequence_error if some unbuffered content is already written as // will not be able to send Set-Cookie header. // virtual void cookie (const char* name, const char* value, const std::chrono::seconds* max_age = 0, const char* path = 0, const char* domain = 0, bool secure = false) = 0; }; // A web server logging backend. The module can use it to log // diagnostics that is meant for the web server operator rather // than the user. // // The module can cast this basic interface to the web server's // specific implementation that may provide a richer interface. // class log { public: virtual void write (const char* msg) = 0; }; // The web server creates a new module instance for each request // by copy-initializing it with the module exemplar. This way we // achieve two things: we can freely use module data members // without worrying about multi-threading issues and we // automatically get started with the initial state for each // request. If you really need to share some rw-data between // all the modules, use static data members with appropriate // locking. See the header in one of the web server // directories (e.g., apache/) if you need to see the code that // does this. // class module { public: virtual void handle (request&, response&, log&) = 0; }; } #endif // WEB_MODULE