cannam@62: // Copyright (c) 2017 Sandstorm Development Group, Inc. and contributors cannam@62: // Licensed under the MIT License: cannam@62: // cannam@62: // Permission is hereby granted, free of charge, to any person obtaining a copy cannam@62: // of this software and associated documentation files (the "Software"), to deal cannam@62: // in the Software without restriction, including without limitation the rights cannam@62: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell cannam@62: // copies of the Software, and to permit persons to whom the Software is cannam@62: // furnished to do so, subject to the following conditions: cannam@62: // cannam@62: // The above copyright notice and this permission notice shall be included in cannam@62: // all copies or substantial portions of the Software. cannam@62: // cannam@62: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR cannam@62: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, cannam@62: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE cannam@62: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER cannam@62: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, cannam@62: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN cannam@62: // THE SOFTWARE. cannam@62: cannam@62: #ifndef KJ_COMPAT_HTTP_H_ cannam@62: #define KJ_COMPAT_HTTP_H_ cannam@62: // The KJ HTTP client/server library. cannam@62: // cannam@62: // This is a simple library which can be used to implement an HTTP client or server. Properties cannam@62: // of this library include: cannam@62: // - Uses KJ async framework. cannam@62: // - Agnostic to transport layer -- you can provide your own. cannam@62: // - Header parsing is zero-copy -- it results in strings that point directly into the buffer cannam@62: // received off the wire. cannam@62: // - Application code which reads and writes headers refers to headers by symbolic names, not by cannam@62: // string literals, with lookups being array-index-based, not map-based. To make this possible, cannam@62: // the application announces what headers it cares about in advance, in order to assign numeric cannam@62: // values to them. cannam@62: // - Methods are identified by an enum. cannam@62: cannam@62: #include cannam@62: #include cannam@62: #include cannam@62: #include cannam@62: #include cannam@62: cannam@62: namespace kj { cannam@62: cannam@62: #define KJ_HTTP_FOR_EACH_METHOD(MACRO) \ cannam@62: MACRO(GET) \ cannam@62: MACRO(HEAD) \ cannam@62: MACRO(POST) \ cannam@62: MACRO(PUT) \ cannam@62: MACRO(DELETE) \ cannam@62: MACRO(PATCH) \ cannam@62: MACRO(PURGE) \ cannam@62: MACRO(OPTIONS) \ cannam@62: MACRO(TRACE) \ cannam@62: /* standard methods */ \ cannam@62: /* */ \ cannam@62: /* (CONNECT is intentionally omitted since it is handled specially in HttpHandler) */ \ cannam@62: \ cannam@62: MACRO(COPY) \ cannam@62: MACRO(LOCK) \ cannam@62: MACRO(MKCOL) \ cannam@62: MACRO(MOVE) \ cannam@62: MACRO(PROPFIND) \ cannam@62: MACRO(PROPPATCH) \ cannam@62: MACRO(SEARCH) \ cannam@62: MACRO(UNLOCK) \ cannam@62: /* WebDAV */ \ cannam@62: \ cannam@62: MACRO(REPORT) \ cannam@62: MACRO(MKACTIVITY) \ cannam@62: MACRO(CHECKOUT) \ cannam@62: MACRO(MERGE) \ cannam@62: /* Subversion */ \ cannam@62: \ cannam@62: MACRO(MSEARCH) \ cannam@62: MACRO(NOTIFY) \ cannam@62: MACRO(SUBSCRIBE) \ cannam@62: MACRO(UNSUBSCRIBE) cannam@62: /* UPnP */ cannam@62: cannam@62: #define KJ_HTTP_FOR_EACH_CONNECTION_HEADER(MACRO) \ cannam@62: MACRO(connection, "Connection") \ cannam@62: MACRO(contentLength, "Content-Length") \ cannam@62: MACRO(keepAlive, "Keep-Alive") \ cannam@62: MACRO(te, "TE") \ cannam@62: MACRO(trailer, "Trailer") \ cannam@62: MACRO(transferEncoding, "Transfer-Encoding") \ cannam@62: MACRO(upgrade, "Upgrade") cannam@62: cannam@62: enum class HttpMethod { cannam@62: // Enum of known HTTP methods. cannam@62: // cannam@62: // We use an enum rather than a string to allow for faster parsing and switching and to reduce cannam@62: // ambiguity. cannam@62: cannam@62: #define DECLARE_METHOD(id) id, cannam@62: KJ_HTTP_FOR_EACH_METHOD(DECLARE_METHOD) cannam@62: #undef DECALRE_METHOD cannam@62: }; cannam@62: cannam@62: kj::StringPtr KJ_STRINGIFY(HttpMethod method); cannam@62: kj::Maybe tryParseHttpMethod(kj::StringPtr name); cannam@62: cannam@62: class HttpHeaderTable; cannam@62: cannam@62: class HttpHeaderId { cannam@62: // Identifies an HTTP header by numeric ID that indexes into an HttpHeaderTable. cannam@62: // cannam@62: // The KJ HTTP API prefers that headers be identified by these IDs for a few reasons: cannam@62: // - Integer lookups are much more efficient than string lookups. cannam@62: // - Case-insensitivity is awkward to deal with when const strings are being passed to the lookup cannam@62: // method. cannam@62: // - Writing out strings less often means fewer typos. cannam@62: // cannam@62: // See HttpHeaderTable for usage hints. cannam@62: cannam@62: public: cannam@62: HttpHeaderId() = default; cannam@62: cannam@62: inline bool operator==(const HttpHeaderId& other) const { return id == other.id; } cannam@62: inline bool operator!=(const HttpHeaderId& other) const { return id != other.id; } cannam@62: inline bool operator< (const HttpHeaderId& other) const { return id < other.id; } cannam@62: inline bool operator> (const HttpHeaderId& other) const { return id > other.id; } cannam@62: inline bool operator<=(const HttpHeaderId& other) const { return id <= other.id; } cannam@62: inline bool operator>=(const HttpHeaderId& other) const { return id >= other.id; } cannam@62: cannam@62: inline size_t hashCode() const { return id; } cannam@62: cannam@62: kj::StringPtr toString() const; cannam@62: cannam@62: void requireFrom(HttpHeaderTable& table) const; cannam@62: // In debug mode, throws an exception if the HttpHeaderId is not from the given table. cannam@62: // cannam@62: // In opt mode, no-op. cannam@62: cannam@62: #define KJ_HTTP_FOR_EACH_BUILTIN_HEADER(MACRO) \ cannam@62: MACRO(HOST, "Host") \ cannam@62: MACRO(DATE, "Date") \ cannam@62: MACRO(LOCATION, "Location") \ cannam@62: MACRO(CONTENT_TYPE, "Content-Type") cannam@62: // For convenience, these very-common headers are valid for all HttpHeaderTables. You can refer cannam@62: // to them like: cannam@62: // cannam@62: // HttpHeaderId::HOST cannam@62: // cannam@62: // TODO(0.7): Fill this out with more common headers. cannam@62: cannam@62: #define DECLARE_HEADER(id, name) \ cannam@62: static const HttpHeaderId id; cannam@62: // Declare a constant for each builtin header, e.g.: HttpHeaderId::CONNECTION cannam@62: cannam@62: KJ_HTTP_FOR_EACH_BUILTIN_HEADER(DECLARE_HEADER); cannam@62: #undef DECLARE_HEADER cannam@62: cannam@62: private: cannam@62: HttpHeaderTable* table; cannam@62: uint id; cannam@62: cannam@62: inline explicit constexpr HttpHeaderId(HttpHeaderTable* table, uint id): table(table), id(id) {} cannam@62: friend class HttpHeaderTable; cannam@62: friend class HttpHeaders; cannam@62: }; cannam@62: cannam@62: class HttpHeaderTable { cannam@62: // Construct an HttpHeaderTable to declare which headers you'll be interested in later on, and cannam@62: // to manufacture IDs for them. cannam@62: // cannam@62: // Example: cannam@62: // cannam@62: // // Build a header table with the headers we are interested in. cannam@62: // kj::HttpHeaderTable::Builder builder; cannam@62: // const HttpHeaderId accept = builder.add("Accept"); cannam@62: // const HttpHeaderId contentType = builder.add("Content-Type"); cannam@62: // kj::HttpHeaderTable table(kj::mv(builder)); cannam@62: // cannam@62: // // Create an HTTP client. cannam@62: // auto client = kj::newHttpClient(table, network); cannam@62: // cannam@62: // // Get http://example.com. cannam@62: // HttpHeaders headers(table); cannam@62: // headers.set(accept, "text/html"); cannam@62: // auto response = client->send(kj::HttpMethod::GET, "http://example.com", headers) cannam@62: // .wait(waitScope); cannam@62: // auto msg = kj::str("Response content type: ", response.headers.get(contentType)); cannam@62: cannam@62: struct IdsByNameMap; cannam@62: cannam@62: public: cannam@62: HttpHeaderTable(); cannam@62: // Constructs a table that only contains the builtin headers. cannam@62: cannam@62: class Builder { cannam@62: public: cannam@62: Builder(); cannam@62: HttpHeaderId add(kj::StringPtr name); cannam@62: Own build(); cannam@62: cannam@62: HttpHeaderTable& getFutureTable(); cannam@62: // Get the still-unbuilt header table. You cannot actually use it until build() has been cannam@62: // called. cannam@62: // cannam@62: // This method exists to help when building a shared header table -- the Builder may be passed cannam@62: // to several components, each of which will register the headers they need and get a reference cannam@62: // to the future table. cannam@62: cannam@62: private: cannam@62: kj::Own table; cannam@62: }; cannam@62: cannam@62: KJ_DISALLOW_COPY(HttpHeaderTable); // Can't copy because HttpHeaderId points to the table. cannam@62: ~HttpHeaderTable() noexcept(false); cannam@62: cannam@62: uint idCount(); cannam@62: // Return the number of IDs in the table. cannam@62: cannam@62: kj::Maybe stringToId(kj::StringPtr name); cannam@62: // Try to find an ID for the given name. The matching is case-insensitive, per the HTTP spec. cannam@62: // cannam@62: // Note: if `name` contains characters that aren't allowed in HTTP header names, this may return cannam@62: // a bogus value rather than null, due to optimizations used in case-insensitive matching. cannam@62: cannam@62: kj::StringPtr idToString(HttpHeaderId id); cannam@62: // Get the canonical string name for the given ID. cannam@62: cannam@62: private: cannam@62: kj::Vector namesById; cannam@62: kj::Own idsByName; cannam@62: }; cannam@62: cannam@62: class HttpHeaders { cannam@62: // Represents a set of HTTP headers. cannam@62: // cannam@62: // This class guards against basic HTTP header injection attacks: Trying to set a header name or cannam@62: // value containing a newline, carriage return, or other invalid character will throw an cannam@62: // exception. cannam@62: cannam@62: public: cannam@62: explicit HttpHeaders(HttpHeaderTable& table); cannam@62: cannam@62: KJ_DISALLOW_COPY(HttpHeaders); cannam@62: HttpHeaders(HttpHeaders&&) = default; cannam@62: HttpHeaders& operator=(HttpHeaders&&) = default; cannam@62: cannam@62: void clear(); cannam@62: // Clears all contents, as if the object was freshly-allocated. However, calling this rather cannam@62: // than actually re-allocating the object may avoid re-allocation of internal objects. cannam@62: cannam@62: HttpHeaders clone() const; cannam@62: // Creates a deep clone of the HttpHeaders. The returned object owns all strings it references. cannam@62: cannam@62: HttpHeaders cloneShallow() const; cannam@62: // Creates a shallow clone of the HttpHeaders. The returned object references the same strings cannam@62: // as the original, owning none of them. cannam@62: cannam@62: kj::Maybe get(HttpHeaderId id) const; cannam@62: // Read a header. cannam@62: cannam@62: template cannam@62: void forEach(Func&& func) const; cannam@62: // Calls `func(name, value)` for each header in the set -- including headers that aren't mapped cannam@62: // to IDs in the header table. Both inputs are of type kj::StringPtr. cannam@62: cannam@62: void set(HttpHeaderId id, kj::StringPtr value); cannam@62: void set(HttpHeaderId id, kj::String&& value); cannam@62: // Sets a header value, overwriting the existing value. cannam@62: // cannam@62: // The String&& version is equivalent to calling the other version followed by takeOwnership(). cannam@62: // cannam@62: // WARNING: It is the caller's responsibility to ensure that `value` remains valid until the cannam@62: // HttpHeaders object is destroyed. This allows string literals to be passed without making a cannam@62: // copy, but complicates the use of dynamic values. Hint: Consider using `takeOwnership()`. cannam@62: cannam@62: void add(kj::StringPtr name, kj::StringPtr value); cannam@62: void add(kj::StringPtr name, kj::String&& value); cannam@62: void add(kj::String&& name, kj::String&& value); cannam@62: // Append a header. `name` will be looked up in the header table, but if it's not mapped, the cannam@62: // header will be added to the list of unmapped headers. cannam@62: // cannam@62: // The String&& versions are equivalent to calling the other version followed by takeOwnership(). cannam@62: // cannam@62: // WARNING: It is the caller's responsibility to ensure that `name` and `value` remain valid cannam@62: // until the HttpHeaders object is destroyed. This allows string literals to be passed without cannam@62: // making a copy, but complicates the use of dynamic values. Hint: Consider using cannam@62: // `takeOwnership()`. cannam@62: cannam@62: void unset(HttpHeaderId id); cannam@62: // Removes a header. cannam@62: // cannam@62: // It's not possible to remove a header by string name because non-indexed headers would take cannam@62: // O(n) time to remove. Instead, construct a new HttpHeaders object and copy contents. cannam@62: cannam@62: void takeOwnership(kj::String&& string); cannam@62: void takeOwnership(kj::Array&& chars); cannam@62: void takeOwnership(HttpHeaders&& otherHeaders); cannam@62: // Takes overship of a string so that it lives until the HttpHeaders object is destroyed. Useful cannam@62: // when you've passed a dynamic value to set() or add() or parse*(). cannam@62: cannam@62: struct ConnectionHeaders { cannam@62: // These headers govern details of the specific HTTP connection or framing of the content. cannam@62: // Hence, they are managed internally within the HTTP library, and never appear in an cannam@62: // HttpHeaders structure. cannam@62: cannam@62: #define DECLARE_HEADER(id, name) \ cannam@62: kj::StringPtr id; cannam@62: KJ_HTTP_FOR_EACH_CONNECTION_HEADER(DECLARE_HEADER) cannam@62: #undef DECLARE_HEADER cannam@62: }; cannam@62: cannam@62: struct Request { cannam@62: HttpMethod method; cannam@62: kj::StringPtr url; cannam@62: ConnectionHeaders connectionHeaders; cannam@62: }; cannam@62: struct Response { cannam@62: uint statusCode; cannam@62: kj::StringPtr statusText; cannam@62: ConnectionHeaders connectionHeaders; cannam@62: }; cannam@62: cannam@62: kj::Maybe tryParseRequest(kj::ArrayPtr content); cannam@62: kj::Maybe tryParseResponse(kj::ArrayPtr content); cannam@62: // Parse an HTTP header blob and add all the headers to this object. cannam@62: // cannam@62: // `content` should be all text from the start of the request to the first occurrance of two cannam@62: // newlines in a row -- including the first of these two newlines, but excluding the second. cannam@62: // cannam@62: // The parse is performed with zero copies: The callee clobbers `content` with '\0' characters cannam@62: // to split it into a bunch of shorter strings. The caller must keep `content` valid until the cannam@62: // `HttpHeaders` is destroyed, or pass it to `takeOwnership()`. cannam@62: cannam@62: kj::String serializeRequest(HttpMethod method, kj::StringPtr url, cannam@62: const ConnectionHeaders& connectionHeaders) const; cannam@62: kj::String serializeResponse(uint statusCode, kj::StringPtr statusText, cannam@62: const ConnectionHeaders& connectionHeaders) const; cannam@62: // Serialize the headers as a complete request or response blob. The blob uses '\r\n' newlines cannam@62: // and includes the double-newline to indicate the end of the headers. cannam@62: cannam@62: kj::String toString() const; cannam@62: cannam@62: private: cannam@62: HttpHeaderTable* table; cannam@62: cannam@62: kj::Array indexedHeaders; cannam@62: // Size is always table->idCount(). cannam@62: cannam@62: struct Header { cannam@62: kj::StringPtr name; cannam@62: kj::StringPtr value; cannam@62: }; cannam@62: kj::Vector
unindexedHeaders; cannam@62: cannam@62: kj::Vector> ownedStrings; cannam@62: cannam@62: kj::Maybe addNoCheck(kj::StringPtr name, kj::StringPtr value); cannam@62: cannam@62: kj::StringPtr cloneToOwn(kj::StringPtr str); cannam@62: cannam@62: kj::String serialize(kj::ArrayPtr word1, cannam@62: kj::ArrayPtr word2, cannam@62: kj::ArrayPtr word3, cannam@62: const ConnectionHeaders& connectionHeaders) const; cannam@62: cannam@62: bool parseHeaders(char* ptr, char* end, ConnectionHeaders& connectionHeaders); cannam@62: cannam@62: // TODO(perf): Arguably we should store a map, but header sets are never very long cannam@62: // TODO(perf): We could optimize for common headers by storing them directly as fields. We could cannam@62: // also add direct accessors for those headers. cannam@62: }; cannam@62: cannam@62: class WebSocket { cannam@62: public: cannam@62: WebSocket(kj::Own stream); cannam@62: // Create a WebSocket wrapping the given I/O stream. cannam@62: cannam@62: kj::Promise send(kj::ArrayPtr message); cannam@62: kj::Promise send(kj::ArrayPtr message); cannam@62: }; cannam@62: cannam@62: class HttpClient { cannam@62: // Interface to the client end of an HTTP connection. cannam@62: // cannam@62: // There are two kinds of clients: cannam@62: // * Host clients are used when talking to a specific host. The `url` specified in a request cannam@62: // is actually just a path. (A `Host` header is still required in all requests.) cannam@62: // * Proxy clients are used when the target could be any arbitrary host on the internet. cannam@62: // The `url` specified in a request is a full URL including protocol and hostname. cannam@62: cannam@62: public: cannam@62: struct Response { cannam@62: uint statusCode; cannam@62: kj::StringPtr statusText; cannam@62: const HttpHeaders* headers; cannam@62: kj::Own body; cannam@62: // `statusText` and `headers` remain valid until `body` is dropped. cannam@62: }; cannam@62: cannam@62: struct Request { cannam@62: kj::Own body; cannam@62: // Write the request entity body to this stream, then drop it when done. cannam@62: // cannam@62: // May be null for GET and HEAD requests (which have no body) and requests that have cannam@62: // Content-Length: 0. cannam@62: cannam@62: kj::Promise response; cannam@62: // Promise for the eventual respnose. cannam@62: }; cannam@62: cannam@62: virtual Request request(HttpMethod method, kj::StringPtr url, const HttpHeaders& headers, cannam@62: kj::Maybe expectedBodySize = nullptr) = 0; cannam@62: // Perform an HTTP request. cannam@62: // cannam@62: // `url` may be a full URL (with protocol and host) or it may be only the path part of the URL, cannam@62: // depending on whether the client is a proxy client or a host client. cannam@62: // cannam@62: // `url` and `headers` need only remain valid until `request()` returns (they can be cannam@62: // stack-allocated). cannam@62: // cannam@62: // `expectedBodySize`, if provided, must be exactly the number of bytes that will be written to cannam@62: // the body. This will trigger use of the `Content-Length` connection header. Otherwise, cannam@62: // `Transfer-Encoding: chunked` will be used. cannam@62: cannam@62: struct WebSocketResponse { cannam@62: uint statusCode; cannam@62: kj::StringPtr statusText; cannam@62: const HttpHeaders* headers; cannam@62: kj::OneOf, kj::Own> upstreamOrBody; cannam@62: // `statusText` and `headers` remain valid until `upstreamOrBody` is dropped. cannam@62: }; cannam@62: virtual kj::Promise openWebSocket( cannam@62: kj::StringPtr url, const HttpHeaders& headers, kj::Own downstream); cannam@62: // Tries to open a WebSocket. Default implementation calls send() and never returns a WebSocket. cannam@62: // cannam@62: // `url` and `headers` are invalidated when the returned promise resolves. cannam@62: cannam@62: virtual kj::Promise> connect(kj::String host); cannam@62: // Handles CONNECT requests. Only relevant for proxy clients. Default implementation throws cannam@62: // UNIMPLEMENTED. cannam@62: }; cannam@62: cannam@62: class HttpService { cannam@62: // Interface which HTTP services should implement. cannam@62: // cannam@62: // This interface is functionally equivalent to HttpClient, but is intended for applications to cannam@62: // implement rather than call. The ergonomics and performance of the method signatures are cannam@62: // optimized for the serving end. cannam@62: // cannam@62: // As with clients, there are two kinds of services: cannam@62: // * Host services are used when talking to a specific host. The `url` specified in a request cannam@62: // is actually just a path. (A `Host` header is still required in all requests, and the service cannam@62: // may in fact serve multiple origins via this header.) cannam@62: // * Proxy services are used when the target could be any arbitrary host on the internet, i.e. to cannam@62: // implement an HTTP proxy. The `url` specified in a request is a full URL including protocol cannam@62: // and hostname. cannam@62: cannam@62: public: cannam@62: class Response { cannam@62: public: cannam@62: virtual kj::Own send( cannam@62: uint statusCode, kj::StringPtr statusText, const HttpHeaders& headers, cannam@62: kj::Maybe expectedBodySize = nullptr) = 0; cannam@62: // Begin the response. cannam@62: // cannam@62: // `statusText` and `headers` need only remain valid until send() returns (they can be cannam@62: // stack-allocated). cannam@62: }; cannam@62: cannam@62: virtual kj::Promise request( cannam@62: HttpMethod method, kj::StringPtr url, const HttpHeaders& headers, cannam@62: kj::AsyncInputStream& requestBody, Response& response) = 0; cannam@62: // Perform an HTTP request. cannam@62: // cannam@62: // `url` may be a full URL (with protocol and host) or it may be only the path part of the URL, cannam@62: // depending on whether the service is a proxy service or a host service. cannam@62: // cannam@62: // `url` and `headers` are invalidated on the first read from `requestBody` or when the returned cannam@62: // promise resolves, whichever comes first. cannam@62: cannam@62: class WebSocketResponse: public Response { cannam@62: public: cannam@62: kj::Own startWebSocket( cannam@62: uint statusCode, kj::StringPtr statusText, const HttpHeaders& headers, cannam@62: WebSocket& upstream); cannam@62: // Begin the response. cannam@62: // cannam@62: // `statusText` and `headers` need only remain valid until startWebSocket() returns (they can cannam@62: // be stack-allocated). cannam@62: }; cannam@62: cannam@62: virtual kj::Promise openWebSocket( cannam@62: kj::StringPtr url, const HttpHeaders& headers, WebSocketResponse& response); cannam@62: // Tries to open a WebSocket. Default implementation calls request() and never returns a cannam@62: // WebSocket. cannam@62: // cannam@62: // `url` and `headers` are invalidated when the returned promise resolves. cannam@62: cannam@62: virtual kj::Promise> connect(kj::String host); cannam@62: // Handles CONNECT requests. Only relevant for proxy services. Default implementation throws cannam@62: // UNIMPLEMENTED. cannam@62: }; cannam@62: cannam@62: kj::Own newHttpClient(HttpHeaderTable& responseHeaderTable, kj::Network& network, cannam@62: kj::Maybe tlsNetwork = nullptr); cannam@62: // Creates a proxy HttpClient that connects to hosts over the given network. cannam@62: // cannam@62: // `responseHeaderTable` is used when parsing HTTP responses. Requests can use any header table. cannam@62: // cannam@62: // `tlsNetwork` is required to support HTTPS destination URLs. Otherwise, only HTTP URLs can be cannam@62: // fetched. cannam@62: cannam@62: kj::Own newHttpClient(HttpHeaderTable& responseHeaderTable, kj::AsyncIoStream& stream); cannam@62: // Creates an HttpClient that speaks over the given pre-established connection. The client may cannam@62: // be used as a proxy client or a host client depending on whether the peer is operating as cannam@62: // a proxy. cannam@62: // cannam@62: // Note that since this client has only one stream to work with, it will try to pipeline all cannam@62: // requests on this stream. If one request or response has an I/O failure, all subsequent requests cannam@62: // fail as well. If the destination server chooses to close the connection after a response, cannam@62: // subsequent requests will fail. If a response takes a long time, it blocks subsequent responses. cannam@62: // If a WebSocket is opened successfully, all subsequent requests fail. cannam@62: cannam@62: kj::Own newHttpClient(HttpService& service); cannam@62: kj::Own newHttpService(HttpClient& client); cannam@62: // Adapts an HttpClient to an HttpService and vice versa. cannam@62: cannam@62: struct HttpServerSettings { cannam@62: kj::Duration headerTimeout = 15 * kj::SECONDS; cannam@62: // After initial connection open, or after receiving the first byte of a pipelined request, cannam@62: // the client must send the complete request within this time. cannam@62: cannam@62: kj::Duration pipelineTimeout = 5 * kj::SECONDS; cannam@62: // After one request/response completes, we'll wait up to this long for a pipelined request to cannam@62: // arrive. cannam@62: }; cannam@62: cannam@62: class HttpServer: private kj::TaskSet::ErrorHandler { cannam@62: // Class which listens for requests on ports or connections and sends them to an HttpService. cannam@62: cannam@62: public: cannam@62: typedef HttpServerSettings Settings; cannam@62: cannam@62: HttpServer(kj::Timer& timer, HttpHeaderTable& requestHeaderTable, HttpService& service, cannam@62: Settings settings = Settings()); cannam@62: // Set up an HttpServer that directs incoming connections to the given service. The service cannam@62: // may be a host service or a proxy service depending on whether you are intending to implement cannam@62: // an HTTP server or an HTTP proxy. cannam@62: cannam@62: kj::Promise drain(); cannam@62: // Stop accepting new connections or new requests on existing connections. Finish any requests cannam@62: // that are already executing, then close the connections. Returns once no more requests are cannam@62: // in-flight. cannam@62: cannam@62: kj::Promise listenHttp(kj::ConnectionReceiver& port); cannam@62: // Accepts HTTP connections on the given port and directs them to the handler. cannam@62: // cannam@62: // The returned promise never completes normally. It may throw if port.accept() throws. Dropping cannam@62: // the returned promise will cause the server to stop listening on the port, but already-open cannam@62: // connections will continue to be served. Destroy the whole HttpServer to cancel all I/O. cannam@62: cannam@62: kj::Promise listenHttp(kj::Own connection); cannam@62: // Reads HTTP requests from the given connection and directs them to the handler. A successful cannam@62: // completion of the promise indicates that all requests received on the connection resulted in cannam@62: // a complete response, and the client closed the connection gracefully or drain() was called. cannam@62: // The promise throws if an unparseable request is received or if some I/O error occurs. Dropping cannam@62: // the returned promise will cancel all I/O on the connection and cancel any in-flight requests. cannam@62: cannam@62: private: cannam@62: class Connection; cannam@62: cannam@62: kj::Timer& timer; cannam@62: HttpHeaderTable& requestHeaderTable; cannam@62: HttpService& service; cannam@62: Settings settings; cannam@62: cannam@62: bool draining = false; cannam@62: kj::ForkedPromise onDrain; cannam@62: kj::Own> drainFulfiller; cannam@62: cannam@62: uint connectionCount = 0; cannam@62: kj::Maybe>> zeroConnectionsFulfiller; cannam@62: cannam@62: kj::TaskSet tasks; cannam@62: cannam@62: HttpServer(kj::Timer& timer, HttpHeaderTable& requestHeaderTable, HttpService& service, cannam@62: Settings settings, kj::PromiseFulfillerPair paf); cannam@62: cannam@62: kj::Promise listenLoop(kj::ConnectionReceiver& port); cannam@62: cannam@62: void taskFailed(kj::Exception&& exception) override; cannam@62: }; cannam@62: cannam@62: // ======================================================================================= cannam@62: // inline implementation cannam@62: cannam@62: inline void HttpHeaderId::requireFrom(HttpHeaderTable& table) const { cannam@62: KJ_IREQUIRE(this->table == nullptr || this->table == &table, cannam@62: "the provided HttpHeaderId is from the wrong HttpHeaderTable"); cannam@62: } cannam@62: cannam@62: inline kj::Own HttpHeaderTable::Builder::build() { return kj::mv(table); } cannam@62: inline HttpHeaderTable& HttpHeaderTable::Builder::getFutureTable() { return *table; } cannam@62: cannam@62: inline uint HttpHeaderTable::idCount() { return namesById.size(); } cannam@62: cannam@62: inline kj::StringPtr HttpHeaderTable::idToString(HttpHeaderId id) { cannam@62: id.requireFrom(*this); cannam@62: return namesById[id.id]; cannam@62: } cannam@62: cannam@62: inline kj::Maybe HttpHeaders::get(HttpHeaderId id) const { cannam@62: id.requireFrom(*table); cannam@62: auto result = indexedHeaders[id.id]; cannam@62: return result == nullptr ? kj::Maybe(nullptr) : result; cannam@62: } cannam@62: cannam@62: inline void HttpHeaders::unset(HttpHeaderId id) { cannam@62: id.requireFrom(*table); cannam@62: indexedHeaders[id.id] = nullptr; cannam@62: } cannam@62: cannam@62: template cannam@62: inline void HttpHeaders::forEach(Func&& func) const { cannam@62: for (auto i: kj::indices(indexedHeaders)) { cannam@62: if (indexedHeaders[i] != nullptr) { cannam@62: func(table->idToString(HttpHeaderId(table, i)), indexedHeaders[i]); cannam@62: } cannam@62: } cannam@62: cannam@62: for (auto& header: unindexedHeaders) { cannam@62: func(header.name, header.value); cannam@62: } cannam@62: } cannam@62: cannam@62: } // namespace kj cannam@62: cannam@62: #endif // KJ_COMPAT_HTTP_H_