// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #ifndef HTTP_CONNECTION_H #define HTTP_CONNECTION_H #include #include #include #include #include #include #include #include #include #include #include namespace isc { namespace http { /// @brief Generic error reported within @ref HttpConnection class. class HttpConnectionError : public Exception { public: HttpConnectionError(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief Forward declaration to the @ref HttpConnectionPool. /// /// This declaration is needed because we don't include the header file /// declaring @ref HttpConnectionPool to avoid circular inclusion. class HttpConnectionPool; class HttpConnection; /// @brief Pointer to the @ref HttpConnection. typedef boost::shared_ptr HttpConnectionPtr; /// @brief Accepts and handles a single HTTP connection. class HttpConnection : public boost::enable_shared_from_this { private: /// @brief Type of the function implementing a callback invoked by the /// @c SocketCallback functor. typedef std::function SocketCallbackFunction; /// @brief Functor associated with the socket object. /// /// This functor calls a callback function specified in the constructor. class SocketCallback { public: /// @brief Constructor. /// /// @param socket_callback Callback to be invoked by the functor upon /// an event associated with the socket. SocketCallback(SocketCallbackFunction socket_callback) : callback_(socket_callback) { } /// @brief Operator called when event associated with a socket occurs. /// /// This operator returns immediately when received error code is /// @c boost::system::error_code is equal to /// @c boost::asio::error::operation_aborted, i.e. the callback is not /// invoked. /// /// @param ec Error code. /// @param length Data length. void operator()(boost::system::error_code ec, size_t length = 0); private: /// @brief Supplied callback. SocketCallbackFunction callback_; }; protected: class Transaction; /// @brief Shared pointer to the @c Transaction. typedef boost::shared_ptr TransactionPtr; /// @brief Represents a single exchange of the HTTP messages. /// /// In HTTP/1.1 multiple HTTP message exchanges may be conducted /// over the same persistent connection before the connection is /// closed. Since ASIO handlers for these exchanges may be sometimes /// executed out of order, there is a need to associate the states of /// the exchanges with the appropriate ASIO handlers. This object /// represents such state and includes: received request, request /// parser (being in the particular state of parsing), input buffer /// and the output buffer. /// /// The new @c Transaction instance is created when the connection /// is established and the server starts receiving the HTTP request. /// The shared pointer to the created transaction is passed between /// the asynchronous handlers. Therefore, as long as the asynchronous /// communication is conducted the instance of the transaction is /// held by the IO service which runs the handlers. The transaction /// instance exists as long as the asynchronous handlers for the /// given request/response exchange are executed. When the server /// responds to the client and all corresponding IO handlers are /// invoked the transaction is automatically destroyed. /// /// The timeout may occur anytime during the transaction. In such /// cases, a new transaction instance is created to send the /// HTTP 408 (timeout) response to the client. Creation of the /// new transaction for the timeout response is necessary because /// there may be some asynchronous handlers scheduled by the /// original transaction which rely on the original transaction's /// state. The timeout response's state is held within the new /// transaction spawned from the original transaction. class Transaction { public: /// @brief Constructor. /// /// @param response_creator Pointer to the response creator being /// used for generating a response from the request. /// @param request Pointer to the HTTP request. If the request is /// null, the constructor creates new request instance using the /// provided response creator. Transaction(const HttpResponseCreatorPtr& response_creator, const HttpRequestPtr& request = HttpRequestPtr()); /// @brief Creates new transaction instance. /// /// It is called when the HTTP server has just scheduled asynchronous /// reading of the HTTP message. /// /// @param response_creator Pointer to the response creator to be passed /// to the transaction's constructor. /// /// @return Pointer to the created transaction instance. static TransactionPtr create(const HttpResponseCreatorPtr& response_creator); /// @brief Creates new transaction from the current transaction. /// /// This method creates new transaction and inherits the request /// from the existing transaction. This is used when the timeout /// occurs during the messages exchange. The server creates the new /// transaction to handle the timeout but this new transaction must /// include the request instance so as HTTP version information can /// be inferred from it while sending the timeout response. The /// HTTP version information should match between the request and /// the response. /// /// @param response_creator Pointer to the response creator. /// @param transaction Existing transaction from which the request /// should be inherited. If the transaction is null, the new (dummy) /// request is created for the new transaction. static TransactionPtr spawn(const HttpResponseCreatorPtr& response_creator, const TransactionPtr& transaction); /// @brief Returns request instance associated with the transaction. HttpRequestPtr getRequest() const { return (request_); } /// @brief Returns parser instance associated with the transaction. HttpRequestParserPtr getParser() const { return (parser_); } /// @brief Returns pointer to the first byte of the input buffer. char* getInputBufData() { return (input_buf_.data()); } /// @brief Returns input buffer size. size_t getInputBufSize() const { return (input_buf_.size()); } /// @brief Checks if the output buffer contains some data to be /// sent. /// /// @return true if the output buffer contains data to be sent, /// false otherwise. bool outputDataAvail() const { return (!output_buf_.empty()); } /// @brief Returns pointer to the first byte of the output buffer. const char* getOutputBufData() const { return (output_buf_.data()); } /// @brief Returns size of the output buffer. size_t getOutputBufSize() const { return (output_buf_.size()); } /// @brief Replaces output buffer contents with new contents. /// /// @param response New contents for the output buffer. void setOutputBuf(const std::string& response) { output_buf_ = response; } /// @brief Erases n bytes from the beginning of the output buffer. /// /// @param length Number of bytes to be erased. void consumeOutputBuf(const size_t length) { output_buf_.erase(0, length); } private: /// @brief Pointer to the request received over this connection. HttpRequestPtr request_; /// @brief Pointer to the HTTP request parser. HttpRequestParserPtr parser_; /// @brief Buffer for received data. std::array input_buf_; /// @brief Buffer used for outbound data. std::string output_buf_; }; public: /// @brief Constructor. /// /// @param io_service IO service to be used by the connection. /// @param acceptor Pointer to the TCP acceptor object used to listen for /// new HTTP connections. /// @param tls_context TLS context. /// @param connection_pool Connection pool in which this connection is /// stored. /// @param response_creator Pointer to the response creator object used to /// create HTTP response from the HTTP request received. /// @param callback Callback invoked when new connection is accepted. /// @param request_timeout Configured timeout for a HTTP request. /// @param idle_timeout Timeout after which persistent HTTP connection is /// closed by the server. HttpConnection(asiolink::IOService& io_service, const HttpAcceptorPtr& acceptor, const asiolink::TlsContextPtr& tls_context, HttpConnectionPool& connection_pool, const HttpResponseCreatorPtr& response_creator, const HttpAcceptorCallback& callback, const long request_timeout, const long idle_timeout); /// @brief Destructor. /// /// Closes current connection. virtual ~HttpConnection(); /// @brief Asynchronously accepts new connection. /// /// When the connection is established successfully, the timeout timer is /// setup and the asynchronous handshake with client is performed. void asyncAccept(); /// @brief Shutdown the socket. void shutdown(); /// @brief Closes the socket. void close(); /// @brief Records connection parameters into the HTTP request. /// /// @param request Pointer to the HTTP request. void recordParameters(const HttpRequestPtr& request) const; /// @brief Asynchronously performs TLS handshake. /// /// When the handshake is performed successfully or skipped because TLS /// was not enabled, the asynchronous read from the socket is started. void doHandshake(); /// @brief Starts asynchronous read from the socket. /// /// The data received over the socket are supplied to the HTTP parser until /// the parser signals that the entire request has been received or until /// the parser signals an error. In the former case the server creates an /// HTTP response using supplied response creator object. /// /// In case of error the connection is stopped. /// /// @param transaction Pointer to the transaction for which the read /// operation should be performed. It defaults to null pointer which /// indicates that this function should create new transaction. void doRead(TransactionPtr transaction = TransactionPtr()); protected: /// @brief Starts asynchronous write to the socket. /// /// The @c output_buf_ must contain the data to be sent. /// /// In case of error the connection is stopped. /// /// @param transaction Pointer to the transaction for which the write /// operation should be performed. void doWrite(TransactionPtr transaction); /// @brief Sends HTTP response asynchronously. /// /// Internally it calls @ref HttpConnection::doWrite to send the data. /// /// @param response Pointer to the HTTP response to be sent. /// @param transaction Pointer to the transaction. void asyncSendResponse(const ConstHttpResponsePtr& response, TransactionPtr transaction); /// @brief Local callback invoked when new connection is accepted. /// /// It invokes external (supplied via constructor) acceptor callback. If /// the acceptor is not opened it returns immediately. If the connection /// is accepted successfully the @ref HttpConnection::doRead or /// @ref HttpConnection::doHandshake is called. /// /// @param ec Error code. void acceptorCallback(const boost::system::error_code& ec); /// @brief Local callback invoked when TLS handshake is performed. /// /// If the handshake is performed successfully the @ref /// HttpConnection::doRead is called. /// /// @param ec Error code. void handshakeCallback(const boost::system::error_code& ec); /// @brief Callback invoked when new data is received over the socket. /// /// This callback supplies the data to the HTTP parser and continues /// parsing. When the parser signals end of the HTTP request the callback /// prepares a response and starts asynchronous send over the socket. /// /// @param transaction Pointer to the transaction for which the callback /// is invoked. /// @param ec Error code. /// @param length Length of the received data. void socketReadCallback(TransactionPtr transaction, boost::system::error_code ec, size_t length); /// @brief Callback invoked when data is sent over the socket. /// /// @param transaction Pointer to the transaction for which the callback /// is invoked. /// @param ec Error code. /// @param length Length of the data sent. virtual void socketWriteCallback(TransactionPtr transaction, boost::system::error_code ec, size_t length); /// @brief Callback invoked when TLS shutdown is performed. /// /// The TLS socket is unconditionally closed but the callback is called /// only when the peer has answered so the connection should be /// explicitly closed in all cases, i.e. do not rely on this handler. /// /// @param ec Error code (ignored). void shutdownCallback(const boost::system::error_code& ec); /// @brief Reset timer for detecting request timeouts. /// /// @param transaction Pointer to the transaction to be guarded by the timeout. void setupRequestTimer(TransactionPtr transaction = TransactionPtr()); /// @brief Reset timer for detecting idle timeout in persistent connections. void setupIdleTimer(); /// @brief Callback invoked when the HTTP Request Timeout occurs. /// /// This callback creates HTTP response with Request Timeout error code /// and sends it to the client. /// /// @param transaction Pointer to the transaction for which timeout occurs. void requestTimeoutCallback(TransactionPtr transaction); void idleTimeoutCallback(); /// @brief Shuts down current connection. /// /// Copied from the next method @ref stopThisConnection void shutdownConnection(); /// @brief Stops current connection. void stopThisConnection(); /// @brief returns remote address in textual form std::string getRemoteEndpointAddressAsText() const; /// @brief Timer used to detect Request Timeout. asiolink::IntervalTimer request_timer_; /// @brief Configured Request Timeout in milliseconds. long request_timeout_; /// @brief TLS context. asiolink::TlsContextPtr tls_context_; /// @brief Timeout after which the persistent HTTP connection is shut /// down by the server. long idle_timeout_; /// @brief TCP socket used by this connection. std::unique_ptr > tcp_socket_; /// @brief TLS socket used by this connection. std::unique_ptr > tls_socket_; /// @brief Pointer to the TCP acceptor used to accept new connections. HttpAcceptorPtr acceptor_; /// @brief Connection pool holding this connection. HttpConnectionPool& connection_pool_; /// @brief Pointer to the @ref HttpResponseCreator object used to create /// HTTP responses. HttpResponseCreatorPtr response_creator_; /// @brief External TCP acceptor callback. HttpAcceptorCallback acceptor_callback_; }; } // end of namespace isc::http } // end of namespace isc #endif