// file : web/module.hxx -*- C++ -*- // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef WEB_MODULE_HXX #define WEB_MODULE_HXX #include #include #include #include #include #include // uint16_t #include // size_t #include // move() #include // runtime_error #include #include 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; struct name_value { // These should eventually become string_view's. // std::string name; optional value; name_value () {} name_value (std::string n, optional v) : name (std::move (n)), value (std::move (v)) {} }; using name_values = std::vector; using butl::path; class request { public: using path_type = web::path; virtual ~request () = default; // 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. If the limit argument // is zero, then the content limit is left unchanged (unlimited initially). // Otherwise the requested limit is set, and the invalid_request exception // with the code 413 (payload too large) will be thrown when the specified // limit is reached while reading from the stream. If the buffer argument // is zero, then the buffer size is left unchanged (zero initially). If it // is impossible to increase the buffer size (because, for example, some // content is already read unbuffered), then the sequence_error is thrown. // // Note that unread input content is discarded when any unbuffered content // is written, and any attempt to read it will result in the // sequence_error exception being thrown. // virtual std::istream& content (size_t limit = 0, size_t buffer = 0) = 0; }; class response { public: virtual ~response () = default; // 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 = nullptr, const char* path = nullptr, const char* domain = nullptr, bool secure = false, bool buffer = true) = 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 ~log () = default; 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 ~module () = default; // 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. // // Throw retry if need to retry handling the request. The retry will // happen on the same instance of the module and the implementation is // expected to "rewind" the request and response objects to their initial // state. This is only guaranteed to be possible if the relevant functions // in the request and response objects were called in buffered mode (the // buffer argument was true). // // Any exception other than retry and 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. // struct retry {}; virtual bool handle (request&, response&, log&) = 0; }; } #endif // WEB_MODULE_HXX