// file : web/module -*- C++ -*- // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef WEB_MODULE #define WEB_MODULE #include <map> #include <string> #include <vector> #include <iosfwd> #include <chrono> #include <cstdint> // uint16_t #include <utility> // move() #include <stdexcept> // runtime_error #include <butl/path> #include <butl/optional> namespace web { using butl::optional; // 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. // // If caught by the web server implementation, it will try to return // the specified status and content to the client, if possible. // It is, however, may not be possible if some unbuffered content has // already been written. The behavior in this case is implementation- // specific and may result in no indication of an error being sent to // the client. // struct invalid_request { status_code status; std::string content; std::string type; //@@ Maybe optional "try again" link? // invalid_request (status_code s = 400, std::string c = "", std::string t = "text/plain;charset=utf-8") : status (s), content (std::move (c)), type (std::move (t)) {} }; // 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 (std::move (d)) {} }; // Map of module configuration option names to the boolean flag indicating // whether the value is expected for the option. // using option_descriptions = std::map<std::string, bool>; struct name_value { // These should eventually become string_view's. // std::string name; optional<std::string> value; name_value () {} name_value (std::string n, optional<std::string> v) : name (std::move (n)), value (std::move (v)) {} }; using name_values = std::vector<name_value>; using butl::path; class request { public: using path_type = web::path; // Corresponds to abs_path portion of HTTP URL as described in // "3.2.2 HTTP URL" of http://tools.ietf.org/html/rfc2616. // Returns '/' if no abs_path is present in URL. // virtual const path_type& path () = 0; //@@ 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 decoding of any name or value fails. // virtual const name_values& parameters () = 0; // Throw invalid_request if cookies are malformed. // virtual const name_values& cookies () = 0; // Get the stream to read the request content from. Note that // reading content after any unbuffered content has been written // is undefined behavior. The implementation may detect it and // throw sequence_error but is not required to do so. // virtual std::istream& content () = 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 code = 200, const std::string& type = "application/xhtml+xml;charset=utf-8", bool buffer = true) = 0; // Set status code without writing any content. On status change, // discard buffered content or throw sequence_error if content was // not buffered. // virtual void status (status_code) = 0; // Throw sequence_error if some unbuffered content has already // been written. // 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 <service> header in one of the web server // directories (e.g., apache/) if you need to see the code that // does this. // class module { public: // Description of configuration options supported by this module. Note: // should be callable during static initialization. // virtual option_descriptions options () = 0; // During startup the web server calls this function on the module // exemplar to log the module version information. It is up to the web // server whether to call this function once per module implementation // type. Therefore, it is expected that this function will log the same // information for all the module exemplars. // virtual void version (log&) = 0; // During startup the web server calls this function on the module // exemplar passing a list of configuration options. The place these // configuration options come from is implementation-specific (normally // a configuration file). The web server guarantees that only options // listed in the map returned by the options() function above can be // present. Any exception thrown by this function terminates the web // server. // virtual void init (const name_values&, log&) = 0; // Return false if decline to handle the request. If handling have been // declined after any unbuffered content has been written, then the // implementation shall terminate the response in a suitable but // unspecified manner. Any exception other than invalid_request described // above that leaves this function is treated by the web server // implementation as an internal server error (500). Similar to // invalid_request, it will try to return the status and description // (obtained by calling what() on std::exception) to the client, if // possible. The description is assume to be encoded in UTF-8. The // implementation may provide a configuration option to omit the // description from the response, for security/privacy reasons. // virtual bool handle (request&, response&, log&) = 0; }; } #endif // WEB_MODULE