jpayne@69: // Copyright (c) 2017 Sandstorm Development Group, Inc. and contributors jpayne@69: // Licensed under the MIT License: jpayne@69: // jpayne@69: // Permission is hereby granted, free of charge, to any person obtaining a copy jpayne@69: // of this software and associated documentation files (the "Software"), to deal jpayne@69: // in the Software without restriction, including without limitation the rights jpayne@69: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell jpayne@69: // copies of the Software, and to permit persons to whom the Software is jpayne@69: // furnished to do so, subject to the following conditions: jpayne@69: // jpayne@69: // The above copyright notice and this permission notice shall be included in jpayne@69: // all copies or substantial portions of the Software. jpayne@69: // jpayne@69: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR jpayne@69: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, jpayne@69: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE jpayne@69: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER jpayne@69: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, jpayne@69: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN jpayne@69: // THE SOFTWARE. jpayne@69: jpayne@69: #pragma once jpayne@69: // The KJ HTTP client/server library. jpayne@69: // jpayne@69: // This is a simple library which can be used to implement an HTTP client or server. Properties jpayne@69: // of this library include: jpayne@69: // - Uses KJ async framework. jpayne@69: // - Agnostic to transport layer -- you can provide your own. jpayne@69: // - Header parsing is zero-copy -- it results in strings that point directly into the buffer jpayne@69: // received off the wire. jpayne@69: // - Application code which reads and writes headers refers to headers by symbolic names, not by jpayne@69: // string literals, with lookups being array-index-based, not map-based. To make this possible, jpayne@69: // the application announces what headers it cares about in advance, in order to assign numeric jpayne@69: // values to them. jpayne@69: // - Methods are identified by an enum. jpayne@69: jpayne@69: #include jpayne@69: #include jpayne@69: #include jpayne@69: #include jpayne@69: #include jpayne@69: #include jpayne@69: jpayne@69: KJ_BEGIN_HEADER jpayne@69: jpayne@69: namespace kj { jpayne@69: jpayne@69: #define KJ_HTTP_FOR_EACH_METHOD(MACRO) \ jpayne@69: MACRO(GET) \ jpayne@69: MACRO(HEAD) \ jpayne@69: MACRO(POST) \ jpayne@69: MACRO(PUT) \ jpayne@69: MACRO(DELETE) \ jpayne@69: MACRO(PATCH) \ jpayne@69: MACRO(PURGE) \ jpayne@69: MACRO(OPTIONS) \ jpayne@69: MACRO(TRACE) \ jpayne@69: /* standard methods */ \ jpayne@69: /* */ \ jpayne@69: /* (CONNECT is intentionally omitted since it should be handled specially in HttpServer) */ \ jpayne@69: \ jpayne@69: MACRO(COPY) \ jpayne@69: MACRO(LOCK) \ jpayne@69: MACRO(MKCOL) \ jpayne@69: MACRO(MOVE) \ jpayne@69: MACRO(PROPFIND) \ jpayne@69: MACRO(PROPPATCH) \ jpayne@69: MACRO(SEARCH) \ jpayne@69: MACRO(UNLOCK) \ jpayne@69: MACRO(ACL) \ jpayne@69: /* WebDAV */ \ jpayne@69: \ jpayne@69: MACRO(REPORT) \ jpayne@69: MACRO(MKACTIVITY) \ jpayne@69: MACRO(CHECKOUT) \ jpayne@69: MACRO(MERGE) \ jpayne@69: /* Subversion */ \ jpayne@69: \ jpayne@69: MACRO(MSEARCH) \ jpayne@69: MACRO(NOTIFY) \ jpayne@69: MACRO(SUBSCRIBE) \ jpayne@69: MACRO(UNSUBSCRIBE) jpayne@69: /* UPnP */ jpayne@69: jpayne@69: enum class HttpMethod { jpayne@69: // Enum of known HTTP methods. jpayne@69: // jpayne@69: // We use an enum rather than a string to allow for faster parsing and switching and to reduce jpayne@69: // ambiguity. jpayne@69: jpayne@69: #define DECLARE_METHOD(id) id, jpayne@69: KJ_HTTP_FOR_EACH_METHOD(DECLARE_METHOD) jpayne@69: #undef DECLARE_METHOD jpayne@69: }; jpayne@69: jpayne@69: struct HttpConnectMethod {}; jpayne@69: // CONNECT is handled specially and separately from the other HttpMethods. jpayne@69: jpayne@69: kj::StringPtr KJ_STRINGIFY(HttpMethod method); jpayne@69: kj::StringPtr KJ_STRINGIFY(HttpConnectMethod method); jpayne@69: kj::Maybe tryParseHttpMethod(kj::StringPtr name); jpayne@69: kj::Maybe> tryParseHttpMethodAllowingConnect( jpayne@69: kj::StringPtr name); jpayne@69: // Like tryParseHttpMethod but, as the name suggests, explicitly allows for the CONNECT jpayne@69: // method. Added as a separate function instead of modifying tryParseHttpMethod to avoid jpayne@69: // breaking API changes in existing uses of tryParseHttpMethod. jpayne@69: jpayne@69: class HttpHeaderTable; jpayne@69: jpayne@69: class HttpHeaderId { jpayne@69: // Identifies an HTTP header by numeric ID that indexes into an HttpHeaderTable. jpayne@69: // jpayne@69: // The KJ HTTP API prefers that headers be identified by these IDs for a few reasons: jpayne@69: // - Integer lookups are much more efficient than string lookups. jpayne@69: // - Case-insensitivity is awkward to deal with when const strings are being passed to the lookup jpayne@69: // method. jpayne@69: // - Writing out strings less often means fewer typos. jpayne@69: // jpayne@69: // See HttpHeaderTable for usage hints. jpayne@69: jpayne@69: public: jpayne@69: HttpHeaderId() = default; jpayne@69: jpayne@69: inline bool operator==(const HttpHeaderId& other) const { return id == other.id; } jpayne@69: inline bool operator!=(const HttpHeaderId& other) const { return id != other.id; } jpayne@69: inline bool operator< (const HttpHeaderId& other) const { return id < other.id; } jpayne@69: inline bool operator> (const HttpHeaderId& other) const { return id > other.id; } jpayne@69: inline bool operator<=(const HttpHeaderId& other) const { return id <= other.id; } jpayne@69: inline bool operator>=(const HttpHeaderId& other) const { return id >= other.id; } jpayne@69: jpayne@69: inline size_t hashCode() const { return id; } jpayne@69: // Returned value is guaranteed to be small and never collide with other headers on the same jpayne@69: // table. jpayne@69: jpayne@69: kj::StringPtr toString() const; jpayne@69: jpayne@69: void requireFrom(const HttpHeaderTable& table) const; jpayne@69: // In debug mode, throws an exception if the HttpHeaderId is not from the given table. jpayne@69: // jpayne@69: // In opt mode, no-op. jpayne@69: jpayne@69: #define KJ_HTTP_FOR_EACH_BUILTIN_HEADER(MACRO) \ jpayne@69: /* Headers that are always read-only. */ \ jpayne@69: MACRO(CONNECTION, "Connection") \ jpayne@69: MACRO(KEEP_ALIVE, "Keep-Alive") \ jpayne@69: MACRO(TE, "TE") \ jpayne@69: MACRO(TRAILER, "Trailer") \ jpayne@69: MACRO(UPGRADE, "Upgrade") \ jpayne@69: \ jpayne@69: /* Headers that are read-only except in the case of a response to a HEAD request. */ \ jpayne@69: MACRO(CONTENT_LENGTH, "Content-Length") \ jpayne@69: MACRO(TRANSFER_ENCODING, "Transfer-Encoding") \ jpayne@69: \ jpayne@69: /* Headers that are read-only for WebSocket handshakes. */ \ jpayne@69: MACRO(SEC_WEBSOCKET_KEY, "Sec-WebSocket-Key") \ jpayne@69: MACRO(SEC_WEBSOCKET_VERSION, "Sec-WebSocket-Version") \ jpayne@69: MACRO(SEC_WEBSOCKET_ACCEPT, "Sec-WebSocket-Accept") \ jpayne@69: MACRO(SEC_WEBSOCKET_EXTENSIONS, "Sec-WebSocket-Extensions") \ jpayne@69: \ jpayne@69: /* Headers that you can write. */ \ jpayne@69: MACRO(HOST, "Host") \ jpayne@69: MACRO(DATE, "Date") \ jpayne@69: MACRO(LOCATION, "Location") \ jpayne@69: MACRO(CONTENT_TYPE, "Content-Type") jpayne@69: // For convenience, these headers are valid for all HttpHeaderTables. You can refer to them like: jpayne@69: // jpayne@69: // HttpHeaderId::HOST jpayne@69: // jpayne@69: // TODO(someday): Fill this out with more common headers. jpayne@69: jpayne@69: #define DECLARE_HEADER(id, name) \ jpayne@69: static const HttpHeaderId id; jpayne@69: // Declare a constant for each builtin header, e.g.: HttpHeaderId::CONNECTION jpayne@69: jpayne@69: KJ_HTTP_FOR_EACH_BUILTIN_HEADER(DECLARE_HEADER); jpayne@69: #undef DECLARE_HEADER jpayne@69: jpayne@69: private: jpayne@69: const HttpHeaderTable* table; jpayne@69: uint id; jpayne@69: jpayne@69: inline explicit constexpr HttpHeaderId(const HttpHeaderTable* table, uint id) jpayne@69: : table(table), id(id) {} jpayne@69: friend class HttpHeaderTable; jpayne@69: friend class HttpHeaders; jpayne@69: }; jpayne@69: jpayne@69: class HttpHeaderTable { jpayne@69: // Construct an HttpHeaderTable to declare which headers you'll be interested in later on, and jpayne@69: // to manufacture IDs for them. jpayne@69: // jpayne@69: // Example: jpayne@69: // jpayne@69: // // Build a header table with the headers we are interested in. jpayne@69: // kj::HttpHeaderTable::Builder builder; jpayne@69: // const HttpHeaderId accept = builder.add("Accept"); jpayne@69: // const HttpHeaderId contentType = builder.add("Content-Type"); jpayne@69: // kj::HttpHeaderTable table(kj::mv(builder)); jpayne@69: // jpayne@69: // // Create an HTTP client. jpayne@69: // auto client = kj::newHttpClient(table, network); jpayne@69: // jpayne@69: // // Get http://example.com. jpayne@69: // HttpHeaders headers(table); jpayne@69: // headers.set(accept, "text/html"); jpayne@69: // auto response = client->send(kj::HttpMethod::GET, "http://example.com", headers) jpayne@69: // .wait(waitScope); jpayne@69: // auto msg = kj::str("Response content type: ", response.headers.get(contentType)); jpayne@69: jpayne@69: struct IdsByNameMap; jpayne@69: jpayne@69: public: jpayne@69: HttpHeaderTable(); jpayne@69: // Constructs a table that only contains the builtin headers. jpayne@69: jpayne@69: class Builder { jpayne@69: public: jpayne@69: Builder(); jpayne@69: HttpHeaderId add(kj::StringPtr name); jpayne@69: Own build(); jpayne@69: jpayne@69: HttpHeaderTable& getFutureTable(); jpayne@69: // Get the still-unbuilt header table. You cannot actually use it until build() has been jpayne@69: // called. jpayne@69: // jpayne@69: // This method exists to help when building a shared header table -- the Builder may be passed jpayne@69: // to several components, each of which will register the headers they need and get a reference jpayne@69: // to the future table. jpayne@69: jpayne@69: private: jpayne@69: kj::Own table; jpayne@69: }; jpayne@69: jpayne@69: KJ_DISALLOW_COPY_AND_MOVE(HttpHeaderTable); // Can't copy because HttpHeaderId points to the table. jpayne@69: ~HttpHeaderTable() noexcept(false); jpayne@69: jpayne@69: uint idCount() const; jpayne@69: // Return the number of IDs in the table. jpayne@69: jpayne@69: kj::Maybe stringToId(kj::StringPtr name) const; jpayne@69: // Try to find an ID for the given name. The matching is case-insensitive, per the HTTP spec. jpayne@69: // jpayne@69: // Note: if `name` contains characters that aren't allowed in HTTP header names, this may return jpayne@69: // a bogus value rather than null, due to optimizations used in case-insensitive matching. jpayne@69: jpayne@69: kj::StringPtr idToString(HttpHeaderId id) const; jpayne@69: // Get the canonical string name for the given ID. jpayne@69: jpayne@69: bool isReady() const; jpayne@69: // Returns true if this HttpHeaderTable either was default constructed or its Builder has jpayne@69: // invoked `build()` and released it. jpayne@69: jpayne@69: private: jpayne@69: kj::Vector namesById; jpayne@69: kj::Own idsByName; jpayne@69: jpayne@69: enum class BuildStatus { jpayne@69: UNSTARTED = 0, jpayne@69: BUILDING = 1, jpayne@69: FINISHED = 2, jpayne@69: }; jpayne@69: BuildStatus buildStatus = BuildStatus::UNSTARTED; jpayne@69: }; jpayne@69: jpayne@69: class HttpHeaders { jpayne@69: // Represents a set of HTTP headers. jpayne@69: // jpayne@69: // This class guards against basic HTTP header injection attacks: Trying to set a header name or jpayne@69: // value containing a newline, carriage return, or other invalid character will throw an jpayne@69: // exception. jpayne@69: jpayne@69: public: jpayne@69: explicit HttpHeaders(const HttpHeaderTable& table); jpayne@69: jpayne@69: static bool isValidHeaderValue(kj::StringPtr value); jpayne@69: // This returns whether the value is a valid parameter to the set call. While the HTTP spec jpayne@69: // suggests that only printable ASCII characters are allowed in header values, in practice that jpayne@69: // turns out to not be the case. We follow the browser's lead in disallowing \r and \n. jpayne@69: // https://github.com/httpwg/http11bis/issues/19 jpayne@69: // Use this if you want to validate the value before supplying it to set() if you want to avoid jpayne@69: // an exception being thrown (e.g. you have custom error reporting). NOTE that set will still jpayne@69: // validate the value. If performance is a problem this API needs to be adjusted to a jpayne@69: // `validateHeaderValue` function that returns a special type that set can be confident has jpayne@69: // already passed through the validation routine. jpayne@69: jpayne@69: KJ_DISALLOW_COPY(HttpHeaders); jpayne@69: HttpHeaders(HttpHeaders&&) = default; jpayne@69: HttpHeaders& operator=(HttpHeaders&&) = default; jpayne@69: jpayne@69: size_t size() const; jpayne@69: // Returns the number of headers that forEach() would iterate over. jpayne@69: jpayne@69: void clear(); jpayne@69: // Clears all contents, as if the object was freshly-allocated. However, calling this rather jpayne@69: // than actually re-allocating the object may avoid re-allocation of internal objects. jpayne@69: jpayne@69: HttpHeaders clone() const; jpayne@69: // Creates a deep clone of the HttpHeaders. The returned object owns all strings it references. jpayne@69: jpayne@69: HttpHeaders cloneShallow() const; jpayne@69: // Creates a shallow clone of the HttpHeaders. The returned object references the same strings jpayne@69: // as the original, owning none of them. jpayne@69: jpayne@69: bool isWebSocket() const; jpayne@69: // Convenience method that checks for the presence of the header `Upgrade: websocket`. jpayne@69: // jpayne@69: // Note that this does not actually validate that the request is a complete WebSocket handshake jpayne@69: // with the correct version number -- such validation will occur if and when you call jpayne@69: // acceptWebSocket(). jpayne@69: jpayne@69: kj::Maybe get(HttpHeaderId id) const; jpayne@69: // Read a header. jpayne@69: // jpayne@69: // Note that there is intentionally no method to look up a header by string name rather than jpayne@69: // header ID. The intent is that you should always allocate a header ID for any header that you jpayne@69: // care about, so that you can get() it by ID. Headers with registered IDs are stored in an array jpayne@69: // indexed by ID, making lookup fast. Headers without registered IDs are stored in a separate list jpayne@69: // that is optimized for re-transmission of the whole list, but not for lookup. jpayne@69: jpayne@69: template jpayne@69: void forEach(Func&& func) const; jpayne@69: // Calls `func(name, value)` for each header in the set -- including headers that aren't mapped jpayne@69: // to IDs in the header table. Both inputs are of type kj::StringPtr. jpayne@69: jpayne@69: template jpayne@69: void forEach(Func1&& func1, Func2&& func2) const; jpayne@69: // Calls `func1(id, value)` for each header in the set that has a registered HttpHeaderId, and jpayne@69: // `func2(name, value)` for each header that does not. All calls to func1() precede all calls to jpayne@69: // func2(). jpayne@69: jpayne@69: void set(HttpHeaderId id, kj::StringPtr value); jpayne@69: void set(HttpHeaderId id, kj::String&& value); jpayne@69: // Sets a header value, overwriting the existing value. jpayne@69: // jpayne@69: // The String&& version is equivalent to calling the other version followed by takeOwnership(). jpayne@69: // jpayne@69: // WARNING: It is the caller's responsibility to ensure that `value` remains valid until the jpayne@69: // HttpHeaders object is destroyed. This allows string literals to be passed without making a jpayne@69: // copy, but complicates the use of dynamic values. Hint: Consider using `takeOwnership()`. jpayne@69: jpayne@69: void add(kj::StringPtr name, kj::StringPtr value); jpayne@69: void add(kj::StringPtr name, kj::String&& value); jpayne@69: void add(kj::String&& name, kj::String&& value); jpayne@69: // Append a header. `name` will be looked up in the header table, but if it's not mapped, the jpayne@69: // header will be added to the list of unmapped headers. jpayne@69: // jpayne@69: // The String&& versions are equivalent to calling the other version followed by takeOwnership(). jpayne@69: // jpayne@69: // WARNING: It is the caller's responsibility to ensure that `name` and `value` remain valid jpayne@69: // until the HttpHeaders object is destroyed. This allows string literals to be passed without jpayne@69: // making a copy, but complicates the use of dynamic values. Hint: Consider using jpayne@69: // `takeOwnership()`. jpayne@69: jpayne@69: void unset(HttpHeaderId id); jpayne@69: // Removes a header. jpayne@69: // jpayne@69: // It's not possible to remove a header by string name because non-indexed headers would take jpayne@69: // O(n) time to remove. Instead, construct a new HttpHeaders object and copy contents. jpayne@69: jpayne@69: void takeOwnership(kj::String&& string); jpayne@69: void takeOwnership(kj::Array&& chars); jpayne@69: void takeOwnership(HttpHeaders&& otherHeaders); jpayne@69: // Takes ownership of a string so that it lives until the HttpHeaders object is destroyed. Useful jpayne@69: // when you've passed a dynamic value to set() or add() or parse*(). jpayne@69: jpayne@69: struct Request { jpayne@69: HttpMethod method; jpayne@69: kj::StringPtr url; jpayne@69: }; jpayne@69: struct ConnectRequest { jpayne@69: kj::StringPtr authority; jpayne@69: }; jpayne@69: struct Response { jpayne@69: uint statusCode; jpayne@69: kj::StringPtr statusText; jpayne@69: }; jpayne@69: jpayne@69: struct ProtocolError { jpayne@69: // Represents a protocol error, such as a bad request method or invalid headers. Debugging such jpayne@69: // errors is difficult without a copy of the data which we tried to parse, but this data is jpayne@69: // sensitive, so we can't just lump it into the error description directly. ProtocolError jpayne@69: // provides this sensitive data separate from the error description. jpayne@69: // jpayne@69: // TODO(cleanup): Should maybe not live in HttpHeaders? HttpServerErrorHandler::ProtocolError? jpayne@69: // Or HttpProtocolError? Or maybe we need a more general way of attaching sensitive context to jpayne@69: // kj::Exceptions? jpayne@69: jpayne@69: uint statusCode; jpayne@69: // Suggested HTTP status code that should be used when returning an error to the client. jpayne@69: // jpayne@69: // Most errors are 400. An unrecognized method will be 501. jpayne@69: jpayne@69: kj::StringPtr statusMessage; jpayne@69: // HTTP status message to go with `statusCode`, e.g. "Bad Request". jpayne@69: jpayne@69: kj::StringPtr description; jpayne@69: // An error description safe for all the world to see. jpayne@69: jpayne@69: kj::ArrayPtr rawContent; jpayne@69: // Unredacted data which led to the error condition. This may contain anything transported over jpayne@69: // HTTP, to include sensitive PII, so you must take care to sanitize this before using it in any jpayne@69: // error report that may leak to unprivileged eyes. jpayne@69: // jpayne@69: // This ArrayPtr is merely a copy of the `content` parameter passed to `tryParseRequest()` / jpayne@69: // `tryParseResponse()`, thus it remains valid for as long as a successfully-parsed HttpHeaders jpayne@69: // object would remain valid. jpayne@69: }; jpayne@69: jpayne@69: using RequestOrProtocolError = kj::OneOf; jpayne@69: using ResponseOrProtocolError = kj::OneOf; jpayne@69: using RequestConnectOrProtocolError = kj::OneOf; jpayne@69: jpayne@69: RequestOrProtocolError tryParseRequest(kj::ArrayPtr content); jpayne@69: RequestConnectOrProtocolError tryParseRequestOrConnect(kj::ArrayPtr content); jpayne@69: ResponseOrProtocolError tryParseResponse(kj::ArrayPtr content); jpayne@69: jpayne@69: // Parse an HTTP header blob and add all the headers to this object. jpayne@69: // jpayne@69: // `content` should be all text from the start of the request to the first occurrence of two jpayne@69: // newlines in a row -- including the first of these two newlines, but excluding the second. jpayne@69: // jpayne@69: // The parse is performed with zero copies: The callee clobbers `content` with '\0' characters jpayne@69: // to split it into a bunch of shorter strings. The caller must keep `content` valid until the jpayne@69: // `HttpHeaders` is destroyed, or pass it to `takeOwnership()`. jpayne@69: jpayne@69: bool tryParse(kj::ArrayPtr content); jpayne@69: // Like tryParseRequest()/tryParseResponse(), but don't expect any request/response line. jpayne@69: jpayne@69: kj::String serializeRequest(HttpMethod method, kj::StringPtr url, jpayne@69: kj::ArrayPtr connectionHeaders = nullptr) const; jpayne@69: kj::String serializeConnectRequest(kj::StringPtr authority, jpayne@69: kj::ArrayPtr connectionHeaders = nullptr) const; jpayne@69: kj::String serializeResponse(uint statusCode, kj::StringPtr statusText, jpayne@69: kj::ArrayPtr connectionHeaders = nullptr) const; jpayne@69: // **Most applications will not use these methods; they are called by the HTTP client and server jpayne@69: // implementations.** jpayne@69: // jpayne@69: // Serialize the headers as a complete request or response blob. The blob uses '\r\n' newlines jpayne@69: // and includes the double-newline to indicate the end of the headers. jpayne@69: // jpayne@69: // `connectionHeaders`, if provided, contains connection-level headers supplied by the HTTP jpayne@69: // implementation, in the order specified by the KJ_HTTP_FOR_EACH_BUILTIN_HEADER macro. These jpayne@69: // headers values override any corresponding header value in the HttpHeaders object. The jpayne@69: // CONNECTION_HEADERS_COUNT constants below can help you construct this `connectionHeaders` array. jpayne@69: jpayne@69: enum class BuiltinIndicesEnum { jpayne@69: #define HEADER_ID(id, name) id, jpayne@69: KJ_HTTP_FOR_EACH_BUILTIN_HEADER(HEADER_ID) jpayne@69: #undef HEADER_ID jpayne@69: }; jpayne@69: jpayne@69: struct BuiltinIndices { jpayne@69: #define HEADER_ID(id, name) static constexpr uint id = static_cast(BuiltinIndicesEnum::id); jpayne@69: KJ_HTTP_FOR_EACH_BUILTIN_HEADER(HEADER_ID) jpayne@69: #undef HEADER_ID jpayne@69: }; jpayne@69: jpayne@69: static constexpr uint HEAD_RESPONSE_CONNECTION_HEADERS_COUNT = BuiltinIndices::CONTENT_LENGTH; jpayne@69: static constexpr uint CONNECTION_HEADERS_COUNT = BuiltinIndices::SEC_WEBSOCKET_KEY; jpayne@69: static constexpr uint WEBSOCKET_CONNECTION_HEADERS_COUNT = BuiltinIndices::HOST; jpayne@69: // Constants for use with HttpHeaders::serialize*(). jpayne@69: jpayne@69: kj::String toString() const; jpayne@69: jpayne@69: private: jpayne@69: const HttpHeaderTable* table; jpayne@69: jpayne@69: kj::Array indexedHeaders; jpayne@69: // Size is always table->idCount(). jpayne@69: jpayne@69: struct Header { jpayne@69: kj::StringPtr name; jpayne@69: kj::StringPtr value; jpayne@69: }; jpayne@69: kj::Vector
unindexedHeaders; jpayne@69: jpayne@69: kj::Vector> ownedStrings; jpayne@69: jpayne@69: void addNoCheck(kj::StringPtr name, kj::StringPtr value); jpayne@69: jpayne@69: kj::StringPtr cloneToOwn(kj::StringPtr str); jpayne@69: jpayne@69: kj::String serialize(kj::ArrayPtr word1, jpayne@69: kj::ArrayPtr word2, jpayne@69: kj::ArrayPtr word3, jpayne@69: kj::ArrayPtr connectionHeaders) const; jpayne@69: jpayne@69: bool parseHeaders(char* ptr, char* end); jpayne@69: jpayne@69: // TODO(perf): Arguably we should store a map, but header sets are never very long jpayne@69: // TODO(perf): We could optimize for common headers by storing them directly as fields. We could jpayne@69: // also add direct accessors for those headers. jpayne@69: }; jpayne@69: jpayne@69: class HttpInputStream { jpayne@69: // Low-level interface to receive HTTP-formatted messages (headers followed by body) from an jpayne@69: // input stream, without a paired output stream. jpayne@69: // jpayne@69: // Most applications will not use this. Regular HTTP clients and servers don't need this. This jpayne@69: // is mainly useful for apps implementing various protocols that look like HTTP but aren't jpayne@69: // really. jpayne@69: jpayne@69: public: jpayne@69: struct Request { jpayne@69: HttpMethod method; jpayne@69: kj::StringPtr url; jpayne@69: const HttpHeaders& headers; jpayne@69: kj::Own body; jpayne@69: }; jpayne@69: virtual kj::Promise readRequest() = 0; jpayne@69: // Reads one HTTP request from the input stream. jpayne@69: // jpayne@69: // The returned struct contains pointers directly into a buffer that is invalidated on the next jpayne@69: // message read. jpayne@69: jpayne@69: struct Connect { jpayne@69: kj::StringPtr authority; jpayne@69: const HttpHeaders& headers; jpayne@69: kj::Own body; jpayne@69: }; jpayne@69: virtual kj::Promise> readRequestAllowingConnect() = 0; jpayne@69: // Reads one HTTP request from the input stream. jpayne@69: // jpayne@69: // The returned struct contains pointers directly into a buffer that is invalidated on the next jpayne@69: // message read. jpayne@69: jpayne@69: struct Response { jpayne@69: uint statusCode; jpayne@69: kj::StringPtr statusText; jpayne@69: const HttpHeaders& headers; jpayne@69: kj::Own body; jpayne@69: }; jpayne@69: virtual kj::Promise readResponse(HttpMethod requestMethod) = 0; jpayne@69: // Reads one HTTP response from the input stream. jpayne@69: // jpayne@69: // You must provide the request method because responses to HEAD requests require special jpayne@69: // treatment. jpayne@69: // jpayne@69: // The returned struct contains pointers directly into a buffer that is invalidated on the next jpayne@69: // message read. jpayne@69: jpayne@69: struct Message { jpayne@69: const HttpHeaders& headers; jpayne@69: kj::Own body; jpayne@69: }; jpayne@69: virtual kj::Promise readMessage() = 0; jpayne@69: // Reads an HTTP header set followed by a body, with no request or response line. This is not jpayne@69: // useful for HTTP but may be useful for other protocols that make the unfortunate choice to jpayne@69: // mimic HTTP message format, such as Visual Studio Code's JSON-RPC transport. jpayne@69: // jpayne@69: // The returned struct contains pointers directly into a buffer that is invalidated on the next jpayne@69: // message read. jpayne@69: jpayne@69: virtual kj::Promise awaitNextMessage() = 0; jpayne@69: // Waits until more data is available, but doesn't consume it. Returns false on EOF. jpayne@69: }; jpayne@69: jpayne@69: class EntropySource { jpayne@69: // Interface for an object that generates entropy. Typically, cryptographically-random entropy jpayne@69: // is expected. jpayne@69: // jpayne@69: // TODO(cleanup): Put this somewhere more general. jpayne@69: jpayne@69: public: jpayne@69: virtual void generate(kj::ArrayPtr buffer) = 0; jpayne@69: }; jpayne@69: jpayne@69: struct CompressionParameters { jpayne@69: // These are the parameters for `Sec-WebSocket-Extensions` permessage-deflate extension. jpayne@69: // Since we cannot distinguish the client/server in `upgradeToWebSocket`, we use the prefixes jpayne@69: // `inbound` and `outbound` instead. jpayne@69: bool outboundNoContextTakeover = false; jpayne@69: bool inboundNoContextTakeover = false; jpayne@69: kj::Maybe outboundMaxWindowBits = nullptr; jpayne@69: kj::Maybe inboundMaxWindowBits = nullptr; jpayne@69: }; jpayne@69: jpayne@69: class WebSocket { jpayne@69: // Interface representincg an open WebSocket session. jpayne@69: // jpayne@69: // Each side can send and receive data and "close" messages. jpayne@69: // jpayne@69: // Ping/Pong and message fragmentation are not exposed through this interface. These features of jpayne@69: // the underlying WebSocket protocol are not exposed by the browser-level JavaScript API either, jpayne@69: // and thus applications typically need to implement these features at the application protocol jpayne@69: // level instead. The implementation is, however, expected to reply to Ping messages it receives. jpayne@69: jpayne@69: public: jpayne@69: virtual kj::Promise send(kj::ArrayPtr message) = 0; jpayne@69: virtual kj::Promise send(kj::ArrayPtr message) = 0; jpayne@69: // Send a message (binary or text). The underlying buffer must remain valid, and you must not jpayne@69: // call send() again, until the returned promise resolves. jpayne@69: jpayne@69: virtual kj::Promise close(uint16_t code, kj::StringPtr reason) = 0; jpayne@69: // Send a Close message. jpayne@69: // jpayne@69: // Note that the returned Promise resolves once the message has been sent -- it does NOT wait jpayne@69: // for the other end to send a Close reply. The application should await a reply before dropping jpayne@69: // the WebSocket object. jpayne@69: jpayne@69: virtual kj::Promise disconnect() = 0; jpayne@69: // Sends EOF on the underlying connection without sending a "close" message. This is NOT a clean jpayne@69: // shutdown, but is sometimes useful when you want the other end to trigger whatever behavior jpayne@69: // it normally triggers when a connection is dropped. jpayne@69: jpayne@69: virtual void abort() = 0; jpayne@69: // Forcefully close this WebSocket, such that the remote end should get a DISCONNECTED error if jpayne@69: // it continues to write. This differs from disconnect(), which only closes the sending jpayne@69: // direction, but still allows receives. jpayne@69: jpayne@69: virtual kj::Promise whenAborted() = 0; jpayne@69: // Resolves when the remote side aborts the connection such that send() would throw DISCONNECTED, jpayne@69: // if this can be detected without actually writing a message. (If not, this promise never jpayne@69: // resolves, but send() or receive() will throw DISCONNECTED when appropriate. See also jpayne@69: // kj::AsyncOutputStream::whenWriteDisconnected().) jpayne@69: jpayne@69: struct ProtocolError { jpayne@69: // Represents a protocol error, such as a bad opcode or oversize message. jpayne@69: jpayne@69: uint statusCode; jpayne@69: // Suggested WebSocket status code that should be used when returning an error to the client. jpayne@69: // jpayne@69: // Most errors are 1002; an oversize message will be 1009. jpayne@69: jpayne@69: kj::StringPtr description; jpayne@69: // An error description safe for all the world to see. This should be at most 123 bytes so that jpayne@69: // it can be used as the body of a Close frame (RFC 6455 sections 5.5 and 5.5.1). jpayne@69: }; jpayne@69: jpayne@69: struct Close { jpayne@69: uint16_t code; jpayne@69: kj::String reason; jpayne@69: }; jpayne@69: jpayne@69: typedef kj::OneOf, Close> Message; jpayne@69: jpayne@69: static constexpr size_t SUGGESTED_MAX_MESSAGE_SIZE = 1u << 20; // 1MB jpayne@69: jpayne@69: virtual kj::Promise receive(size_t maxSize = SUGGESTED_MAX_MESSAGE_SIZE) = 0; jpayne@69: // Read one message from the WebSocket and return it. Can only call once at a time. Do not call jpayne@69: // again after Close is received. jpayne@69: jpayne@69: virtual kj::Promise pumpTo(WebSocket& other); jpayne@69: // Continuously receives messages from this WebSocket and send them to `other`. jpayne@69: // jpayne@69: // On EOF, calls other.disconnect(), then resolves. jpayne@69: // jpayne@69: // On other read errors, calls other.close() with the error, then resolves. jpayne@69: // jpayne@69: // On write error, rejects with the error. jpayne@69: jpayne@69: virtual kj::Maybe> tryPumpFrom(WebSocket& other); jpayne@69: // Either returns null, or performs the equivalent of other.pumpTo(*this). Only returns non-null jpayne@69: // if this WebSocket implementation is able to perform the pump in an optimized way, better than jpayne@69: // the default implementation of pumpTo(). The default implementation of pumpTo() always tries jpayne@69: // calling this first, and the default implementation of tryPumpFrom() always returns null. jpayne@69: jpayne@69: virtual uint64_t sentByteCount() = 0; jpayne@69: virtual uint64_t receivedByteCount() = 0; jpayne@69: jpayne@69: enum ExtensionsContext { jpayne@69: // Indicate whether a Sec-WebSocket-Extension header should be rendered for use in request jpayne@69: // headers or response headers. jpayne@69: REQUEST, jpayne@69: RESPONSE jpayne@69: }; jpayne@69: virtual kj::Maybe getPreferredExtensions(ExtensionsContext ctx) { return nullptr; } jpayne@69: // If pumpTo() / tryPumpFrom() is able to be optimized only if the other WebSocket is using jpayne@69: // certain extensions (e.g. compression settings), then this method returns what those extensions jpayne@69: // are. For example, matching extensions between standard WebSockets allows pumping to be jpayne@69: // implemented by pumping raw bytes between network connections, without reading individual frames. jpayne@69: // jpayne@69: // A null return value indicates that there is no preference. A non-null return value containing jpayne@69: // an empty string indicates a preference for no extensions to be applied. jpayne@69: }; jpayne@69: jpayne@69: using TlsStarterCallback = kj::Maybe(kj::StringPtr)>>; jpayne@69: struct HttpConnectSettings { jpayne@69: bool useTls = false; jpayne@69: // Requests to automatically establish a TLS session over the connection. The remote party jpayne@69: // will be expected to present a valid certificate matching the requested hostname. jpayne@69: kj::Maybe tlsStarter; jpayne@69: // This is an output parameter. It doesn't need to be set. But if it is set, then it may get jpayne@69: // filled with a callback function. It will get filled with `nullptr` if any of the following jpayne@69: // are true: jpayne@69: // jpayne@69: // * kj is not built with TLS support jpayne@69: // * the underlying HttpClient does not support the startTls mechanism jpayne@69: // * `useTls` has been set to `true` and so TLS has already been started jpayne@69: // jpayne@69: // The callback function itself can be called to initiate a TLS handshake on the connection in jpayne@69: // between write() operations. It is not allowed to initiate a TLS handshake while a write jpayne@69: // operation or a pump operation to the connection exists. Read operations are not subject to jpayne@69: // the same constraint, however: implementations are required to be able to handle TLS jpayne@69: // initiation while a read operation or pump operation from the connection exists. Once the jpayne@69: // promise returned from the callback is fulfilled, the connection has become a secure stream, jpayne@69: // and write operations are once again permitted. The StringPtr parameter to the callback, jpayne@69: // expectedServerHostname may be dropped after the function synchronously returns. jpayne@69: // jpayne@69: // The PausableReadAsyncIoStream class defined below can be used to ensure that read operations jpayne@69: // are not pending when the tlsStarter is invoked. jpayne@69: // jpayne@69: // This mechanism is required for certain protocols, more info can be found on jpayne@69: // https://en.wikipedia.org/wiki/Opportunistic_TLS. jpayne@69: }; jpayne@69: jpayne@69: jpayne@69: class PausableReadAsyncIoStream final: public kj::AsyncIoStream { jpayne@69: // A custom AsyncIoStream which can pause pending reads. This is used by startTls to pause a jpayne@69: // a read before TLS is initiated. jpayne@69: // jpayne@69: // TODO(cleanup): this class should be rewritten to use a CRTP mixin approach so that pumps jpayne@69: // can be optimised once startTls is invoked. jpayne@69: class PausableRead; jpayne@69: public: jpayne@69: PausableReadAsyncIoStream(kj::Own stream) jpayne@69: : inner(kj::mv(stream)), currentlyWriting(false), currentlyReading(false) {} jpayne@69: jpayne@69: _::Deferred> trackRead(); jpayne@69: jpayne@69: _::Deferred> trackWrite(); jpayne@69: jpayne@69: kj::Promise tryRead(void* buffer, size_t minBytes, size_t maxBytes) override; jpayne@69: jpayne@69: kj::Promise tryReadImpl(void* buffer, size_t minBytes, size_t maxBytes); jpayne@69: jpayne@69: kj::Maybe tryGetLength() override; jpayne@69: jpayne@69: kj::Promise pumpTo(kj::AsyncOutputStream& output, uint64_t amount) override; jpayne@69: jpayne@69: kj::Promise write(const void* buffer, size_t size) override; jpayne@69: jpayne@69: kj::Promise write(kj::ArrayPtr> pieces) override; jpayne@69: jpayne@69: kj::Maybe> tryPumpFrom( jpayne@69: kj::AsyncInputStream& input, uint64_t amount = kj::maxValue) override; jpayne@69: jpayne@69: kj::Promise whenWriteDisconnected() override; jpayne@69: jpayne@69: void shutdownWrite() override; jpayne@69: jpayne@69: void abortRead() override; jpayne@69: jpayne@69: kj::Maybe getFd() const override; jpayne@69: jpayne@69: void pause(); jpayne@69: jpayne@69: void unpause(); jpayne@69: jpayne@69: bool getCurrentlyReading(); jpayne@69: jpayne@69: bool getCurrentlyWriting(); jpayne@69: jpayne@69: kj::Own takeStream(); jpayne@69: jpayne@69: void replaceStream(kj::Own stream); jpayne@69: jpayne@69: void reject(kj::Exception&& exc); jpayne@69: jpayne@69: private: jpayne@69: kj::Own inner; jpayne@69: kj::Maybe maybePausableRead; jpayne@69: bool currentlyWriting; jpayne@69: bool currentlyReading; jpayne@69: }; jpayne@69: jpayne@69: class HttpClient { jpayne@69: // Interface to the client end of an HTTP connection. jpayne@69: // jpayne@69: // There are two kinds of clients: jpayne@69: // * Host clients are used when talking to a specific host. The `url` specified in a request jpayne@69: // is actually just a path. (A `Host` header is still required in all requests.) jpayne@69: // * Proxy clients are used when the target could be any arbitrary host on the internet. jpayne@69: // The `url` specified in a request is a full URL including protocol and hostname. jpayne@69: jpayne@69: public: jpayne@69: struct Response { jpayne@69: uint statusCode; jpayne@69: kj::StringPtr statusText; jpayne@69: const HttpHeaders* headers; jpayne@69: kj::Own body; jpayne@69: // `statusText` and `headers` remain valid until `body` is dropped or read from. jpayne@69: }; jpayne@69: jpayne@69: struct Request { jpayne@69: kj::Own body; jpayne@69: // Write the request entity body to this stream, then drop it when done. jpayne@69: // jpayne@69: // May be null for GET and HEAD requests (which have no body) and requests that have jpayne@69: // Content-Length: 0. jpayne@69: jpayne@69: kj::Promise response; jpayne@69: // Promise for the eventual response. jpayne@69: }; jpayne@69: jpayne@69: virtual Request request(HttpMethod method, kj::StringPtr url, const HttpHeaders& headers, jpayne@69: kj::Maybe expectedBodySize = nullptr) = 0; jpayne@69: // Perform an HTTP request. jpayne@69: // jpayne@69: // `url` may be a full URL (with protocol and host) or it may be only the path part of the URL, jpayne@69: // depending on whether the client is a proxy client or a host client. jpayne@69: // jpayne@69: // `url` and `headers` need only remain valid until `request()` returns (they can be jpayne@69: // stack-allocated). jpayne@69: // jpayne@69: // `expectedBodySize`, if provided, must be exactly the number of bytes that will be written to jpayne@69: // the body. This will trigger use of the `Content-Length` connection header. Otherwise, jpayne@69: // `Transfer-Encoding: chunked` will be used. jpayne@69: jpayne@69: struct WebSocketResponse { jpayne@69: uint statusCode; jpayne@69: kj::StringPtr statusText; jpayne@69: const HttpHeaders* headers; jpayne@69: kj::OneOf, kj::Own> webSocketOrBody; jpayne@69: // `statusText` and `headers` remain valid until `webSocketOrBody` is dropped or read from. jpayne@69: }; jpayne@69: virtual kj::Promise openWebSocket( jpayne@69: kj::StringPtr url, const HttpHeaders& headers); jpayne@69: // Tries to open a WebSocket. Default implementation calls send() and never returns a WebSocket. jpayne@69: // jpayne@69: // `url` and `headers` need only remain valid until `openWebSocket()` returns (they can be jpayne@69: // stack-allocated). jpayne@69: jpayne@69: struct ConnectRequest { jpayne@69: struct Status { jpayne@69: uint statusCode; jpayne@69: kj::String statusText; jpayne@69: kj::Own headers; jpayne@69: kj::Maybe> errorBody; jpayne@69: // If the connect request is rejected, the statusCode can be any HTTP status code jpayne@69: // outside the 200-299 range and errorBody *may* be specified if there is a rejection jpayne@69: // payload. jpayne@69: jpayne@69: // TODO(perf): Having Status own the statusText and headers is a bit unfortunate. jpayne@69: // Ideally we could have these be non-owned so that the headers object could just jpayne@69: // point directly into HttpOutputStream's buffer and not be copied. That's a bit jpayne@69: // more difficult to with CONNECT since the lifetimes of the buffers are a little jpayne@69: // different than with regular HTTP requests. It should still be possible but for jpayne@69: // now copying and owning the status text and headers is easier. jpayne@69: jpayne@69: Status(uint statusCode, jpayne@69: kj::String statusText, jpayne@69: kj::Own headers, jpayne@69: kj::Maybe> errorBody = nullptr) jpayne@69: : statusCode(statusCode), jpayne@69: statusText(kj::mv(statusText)), jpayne@69: headers(kj::mv(headers)), jpayne@69: errorBody(kj::mv(errorBody)) {} jpayne@69: }; jpayne@69: jpayne@69: kj::Promise status; jpayne@69: kj::Own connection; jpayne@69: }; jpayne@69: jpayne@69: virtual ConnectRequest connect( jpayne@69: kj::StringPtr host, const HttpHeaders& headers, HttpConnectSettings settings); jpayne@69: // Handles CONNECT requests. jpayne@69: // jpayne@69: // `host` must specify both the host and port (e.g. "example.org:1234"). jpayne@69: // jpayne@69: // The `host` and `headers` need only remain valid until `connect()` returns (it can be jpayne@69: // stack-allocated). jpayne@69: }; jpayne@69: jpayne@69: class HttpService { jpayne@69: // Interface which HTTP services should implement. jpayne@69: // jpayne@69: // This interface is functionally equivalent to HttpClient, but is intended for applications to jpayne@69: // implement rather than call. The ergonomics and performance of the method signatures are jpayne@69: // optimized for the serving end. jpayne@69: // jpayne@69: // As with clients, there are two kinds of services: jpayne@69: // * Host services are used when talking to a specific host. The `url` specified in a request jpayne@69: // is actually just a path. (A `Host` header is still required in all requests, and the service jpayne@69: // may in fact serve multiple origins via this header.) jpayne@69: // * Proxy services are used when the target could be any arbitrary host on the internet, i.e. to jpayne@69: // implement an HTTP proxy. The `url` specified in a request is a full URL including protocol jpayne@69: // and hostname. jpayne@69: jpayne@69: public: jpayne@69: class Response { jpayne@69: public: jpayne@69: virtual kj::Own send( jpayne@69: uint statusCode, kj::StringPtr statusText, const HttpHeaders& headers, jpayne@69: kj::Maybe expectedBodySize = nullptr) = 0; jpayne@69: // Begin the response. jpayne@69: // jpayne@69: // `statusText` and `headers` need only remain valid until send() returns (they can be jpayne@69: // stack-allocated). jpayne@69: // jpayne@69: // `send()` may only be called a single time. Calling it a second time will cause an exception jpayne@69: // to be thrown. jpayne@69: jpayne@69: virtual kj::Own acceptWebSocket(const HttpHeaders& headers) = 0; jpayne@69: // If headers.isWebSocket() is true then you can call acceptWebSocket() instead of send(). jpayne@69: // jpayne@69: // If the request is an invalid WebSocket request (e.g., it has an Upgrade: websocket header, jpayne@69: // but other WebSocket-related headers are invalid), `acceptWebSocket()` will throw an jpayne@69: // exception, and the HttpServer will return a 400 Bad Request response and close the jpayne@69: // connection. In this circumstance, the HttpServer will ignore any exceptions which propagate jpayne@69: // from the `HttpService::request()` promise. `HttpServerErrorHandler::handleApplicationError()` jpayne@69: // will not be invoked, and the HttpServer's listen task will be fulfilled normally. jpayne@69: // jpayne@69: // `acceptWebSocket()` may only be called a single time. Calling it a second time will cause an jpayne@69: // exception to be thrown. jpayne@69: jpayne@69: kj::Promise sendError(uint statusCode, kj::StringPtr statusText, jpayne@69: const HttpHeaders& headers); jpayne@69: kj::Promise sendError(uint statusCode, kj::StringPtr statusText, jpayne@69: const HttpHeaderTable& headerTable); jpayne@69: // Convenience wrapper around send() which sends a basic error. A generic error page specifying jpayne@69: // the error code is sent as the body. jpayne@69: // jpayne@69: // You must provide headers or a header table because downstream service wrappers may be jpayne@69: // expecting response headers built with a particular table so that they can insert additional jpayne@69: // headers. jpayne@69: }; jpayne@69: jpayne@69: virtual kj::Promise request( jpayne@69: HttpMethod method, kj::StringPtr url, const HttpHeaders& headers, jpayne@69: kj::AsyncInputStream& requestBody, Response& response) = 0; jpayne@69: // Perform an HTTP request. jpayne@69: // jpayne@69: // `url` may be a full URL (with protocol and host) or it may be only the path part of the URL, jpayne@69: // depending on whether the service is a proxy service or a host service. jpayne@69: // jpayne@69: // `url` and `headers` are invalidated on the first read from `requestBody` or when the returned jpayne@69: // promise resolves, whichever comes first. jpayne@69: // jpayne@69: // Request processing can be canceled by dropping the returned promise. HttpServer may do so if jpayne@69: // the client disconnects prematurely. jpayne@69: // jpayne@69: // The implementation of `request()` should usually not try to use `response` in any way in jpayne@69: // exception-handling code, because it is often not possible to tell whether `Response::send()` or jpayne@69: // `Response::acceptWebSocket()` has already been called. Instead, to generate error HTTP jpayne@69: // responses for the client, implement an HttpServerErrorHandler and pass it to the HttpServer via jpayne@69: // HttpServerSettings. If the `HttpService::request()` promise rejects and no response has yet jpayne@69: // been sent, `HttpServerErrorHandler::handleApplicationError()` will be passed a non-null jpayne@69: // `Maybe` parameter. jpayne@69: jpayne@69: class ConnectResponse { jpayne@69: public: jpayne@69: virtual void accept( jpayne@69: uint statusCode, jpayne@69: kj::StringPtr statusText, jpayne@69: const HttpHeaders& headers) = 0; jpayne@69: // Signals acceptance of the CONNECT tunnel. jpayne@69: jpayne@69: virtual kj::Own reject( jpayne@69: uint statusCode, jpayne@69: kj::StringPtr statusText, jpayne@69: const HttpHeaders& headers, jpayne@69: kj::Maybe expectedBodySize = nullptr) = 0; jpayne@69: // Signals rejection of the CONNECT tunnel. jpayne@69: }; jpayne@69: jpayne@69: virtual kj::Promise connect(kj::StringPtr host, jpayne@69: const HttpHeaders& headers, jpayne@69: kj::AsyncIoStream& connection, jpayne@69: ConnectResponse& response, jpayne@69: HttpConnectSettings settings); jpayne@69: // Handles CONNECT requests. jpayne@69: // jpayne@69: // The `host` must include host and port. jpayne@69: // jpayne@69: // `host` and `headers` are invalidated when accept or reject is called on the ConnectResponse jpayne@69: // or when the returned promise resolves, whichever comes first. jpayne@69: // jpayne@69: // The connection is provided to support pipelining. Writes to the connection will be blocked jpayne@69: // until one of either accept() or reject() is called on tunnel. Reads from the connection are jpayne@69: // permitted at any time. jpayne@69: // jpayne@69: // Request processing can be canceled by dropping the returned promise. HttpServer may do so if jpayne@69: // the client disconnects prematurely. jpayne@69: }; jpayne@69: jpayne@69: class HttpClientErrorHandler { jpayne@69: public: jpayne@69: virtual HttpClient::Response handleProtocolError(HttpHeaders::ProtocolError protocolError); jpayne@69: // Override this function to customize error handling when the client receives an HTTP message jpayne@69: // that fails to parse. The default implementations throws an exception. jpayne@69: // jpayne@69: // There are two main use cases for overriding this: jpayne@69: // 1. `protocolError` contains the actual header content that failed to parse, giving you the jpayne@69: // opportunity to log it for debugging purposes. The default implementation throws away this jpayne@69: // content. jpayne@69: // 2. You could potentially convert protocol errors into HTTP error codes, e.g. 502 Bad Gateway. jpayne@69: // jpayne@69: // Note that `protocolError` may contain pointers into buffers that are no longer valid once jpayne@69: // this method returns; you will have to make copies if you want to keep them. jpayne@69: jpayne@69: virtual HttpClient::WebSocketResponse handleWebSocketProtocolError( jpayne@69: HttpHeaders::ProtocolError protocolError); jpayne@69: // Like handleProtocolError() but for WebSocket requests. The default implementation calls jpayne@69: // handleProtocolError() and converts the Response to WebSocketResponse. There is probably very jpayne@69: // little reason to override this. jpayne@69: }; jpayne@69: jpayne@69: struct HttpClientSettings { jpayne@69: kj::Duration idleTimeout = 5 * kj::SECONDS; jpayne@69: // For clients which automatically create new connections, any connection idle for at least this jpayne@69: // long will be closed. Set this to 0 to prevent connection reuse entirely. jpayne@69: jpayne@69: kj::Maybe entropySource = nullptr; jpayne@69: // Must be provided in order to use `openWebSocket`. If you don't need WebSockets, this can be jpayne@69: // omitted. The WebSocket protocol uses random values to avoid triggering flaws (including jpayne@69: // security flaws) in certain HTTP proxy software. Specifically, entropy is used to generate the jpayne@69: // `Sec-WebSocket-Key` header and to generate frame masks. If you know that there are no broken jpayne@69: // or vulnerable proxies between you and the server, you can provide a dummy entropy source that jpayne@69: // doesn't generate real entropy (e.g. returning the same value every time). Otherwise, you must jpayne@69: // provide a cryptographically-random entropy source. jpayne@69: jpayne@69: kj::Maybe errorHandler = nullptr; jpayne@69: // Customize how protocol errors are handled by the HttpClient. If null, HttpClientErrorHandler's jpayne@69: // default implementation will be used. jpayne@69: jpayne@69: enum WebSocketCompressionMode { jpayne@69: NO_COMPRESSION, jpayne@69: MANUAL_COMPRESSION, // Lets the application decide the compression configuration (if any). jpayne@69: AUTOMATIC_COMPRESSION, // Automatically includes the compression header in the WebSocket request. jpayne@69: }; jpayne@69: WebSocketCompressionMode webSocketCompressionMode = NO_COMPRESSION; jpayne@69: jpayne@69: kj::Maybe tlsContext; jpayne@69: // A reference to a TLS context that will be used when tlsStarter is invoked. jpayne@69: }; jpayne@69: jpayne@69: class WebSocketErrorHandler { jpayne@69: public: jpayne@69: virtual kj::Exception handleWebSocketProtocolError(WebSocket::ProtocolError protocolError); jpayne@69: // Handles low-level protocol errors in received WebSocket data. jpayne@69: // jpayne@69: // This is called when the WebSocket peer sends us bad data *after* a successful WebSocket jpayne@69: // upgrade, e.g. a continuation frame without a preceding start frame, a frame with an unknown jpayne@69: // opcode, or similar. jpayne@69: // jpayne@69: // You would override this method in order to customize the exception. You cannot prevent the jpayne@69: // exception from being thrown. jpayne@69: }; jpayne@69: jpayne@69: kj::Own newHttpClient(kj::Timer& timer, const HttpHeaderTable& responseHeaderTable, jpayne@69: kj::Network& network, kj::Maybe tlsNetwork, jpayne@69: HttpClientSettings settings = HttpClientSettings()); jpayne@69: // Creates a proxy HttpClient that connects to hosts over the given network. The URL must always jpayne@69: // be an absolute URL; the host is parsed from the URL. This implementation will automatically jpayne@69: // add an appropriate Host header (and convert the URL to just a path) once it has connected. jpayne@69: // jpayne@69: // Note that if you wish to route traffic through an HTTP proxy server rather than connect to jpayne@69: // remote hosts directly, you should use the form of newHttpClient() that takes a NetworkAddress, jpayne@69: // and supply the proxy's address. jpayne@69: // jpayne@69: // `responseHeaderTable` is used when parsing HTTP responses. Requests can use any header table. jpayne@69: // jpayne@69: // `tlsNetwork` is required to support HTTPS destination URLs. If null, only HTTP URLs can be jpayne@69: // fetched. jpayne@69: jpayne@69: kj::Own newHttpClient(kj::Timer& timer, const HttpHeaderTable& responseHeaderTable, jpayne@69: kj::NetworkAddress& addr, jpayne@69: HttpClientSettings settings = HttpClientSettings()); jpayne@69: // Creates an HttpClient that always connects to the given address no matter what URL is requested. jpayne@69: // The client will open and close connections as needed. It will attempt to reuse connections for jpayne@69: // multiple requests but will not send a new request before the previous response on the same jpayne@69: // connection has completed, as doing so can result in head-of-line blocking issues. The client may jpayne@69: // be used as a proxy client or a host client depending on whether the peer is operating as jpayne@69: // a proxy. (Hint: This is the best kind of client to use when routing traffic through an HTTP jpayne@69: // proxy. `addr` should be the address of the proxy, and the proxy itself will resolve remote hosts jpayne@69: // based on the URLs passed to it.) jpayne@69: // jpayne@69: // `responseHeaderTable` is used when parsing HTTP responses. Requests can use any header table. jpayne@69: jpayne@69: kj::Own newHttpClient(const HttpHeaderTable& responseHeaderTable, jpayne@69: kj::AsyncIoStream& stream, jpayne@69: HttpClientSettings settings = HttpClientSettings()); jpayne@69: // Creates an HttpClient that speaks over the given pre-established connection. The client may jpayne@69: // be used as a proxy client or a host client depending on whether the peer is operating as jpayne@69: // a proxy. jpayne@69: // jpayne@69: // Note that since this client has only one stream to work with, it will try to pipeline all jpayne@69: // requests on this stream. If one request or response has an I/O failure, all subsequent requests jpayne@69: // fail as well. If the destination server chooses to close the connection after a response, jpayne@69: // subsequent requests will fail. If a response takes a long time, it blocks subsequent responses. jpayne@69: // If a WebSocket is opened successfully, all subsequent requests fail. jpayne@69: jpayne@69: kj::Own newConcurrencyLimitingHttpClient( jpayne@69: HttpClient& inner, uint maxConcurrentRequests, jpayne@69: kj::Function countChangedCallback); jpayne@69: // Creates an HttpClient that is limited to a maximum number of concurrent requests. Additional jpayne@69: // requests are queued, to be opened only after an open request completes. `countChangedCallback` jpayne@69: // is called when a new connection is opened or enqueued and when an open connection is closed, jpayne@69: // passing the number of open and pending connections. jpayne@69: jpayne@69: kj::Own newHttpClient(HttpService& service); jpayne@69: kj::Own newHttpService(HttpClient& client); jpayne@69: // Adapts an HttpClient to an HttpService and vice versa. jpayne@69: jpayne@69: kj::Own newHttpInputStream( jpayne@69: kj::AsyncInputStream& input, const HttpHeaderTable& headerTable); jpayne@69: // Create an HttpInputStream on top of the given stream. Normally applications would not call this jpayne@69: // directly, but it can be useful for implementing protocols that aren't quite HTTP but use similar jpayne@69: // message delimiting. jpayne@69: // jpayne@69: // The HttpInputStream implementation does read-ahead buffering on `input`. Therefore, when the jpayne@69: // HttpInputStream is destroyed, some data read from `input` may be lost, so it's not possible to jpayne@69: // continue reading from `input` in a reliable way. jpayne@69: jpayne@69: kj::Own newWebSocket(kj::Own stream, jpayne@69: kj::Maybe maskEntropySource, jpayne@69: kj::Maybe compressionConfig = nullptr, jpayne@69: kj::Maybe errorHandler = nullptr); jpayne@69: // Create a new WebSocket on top of the given stream. It is assumed that the HTTP -> WebSocket jpayne@69: // upgrade handshake has already occurred (or is not needed), and messages can immediately be jpayne@69: // sent and received on the stream. Normally applications would not call this directly. jpayne@69: // jpayne@69: // `maskEntropySource` is used to generate cryptographically-random frame masks. If null, outgoing jpayne@69: // frames will not be masked. Servers are required NOT to mask their outgoing frames, but clients jpayne@69: // ARE required to do so. So, on the client side, you MUST specify an entropy source. The mask jpayne@69: // must be crytographically random if the data being sent on the WebSocket may be malicious. The jpayne@69: // purpose of the mask is to prevent badly-written HTTP proxies from interpreting "things that look jpayne@69: // like HTTP requests" in a message as being actual HTTP requests, which could result in cache jpayne@69: // poisoning. See RFC6455 section 10.3. jpayne@69: // jpayne@69: // `compressionConfig` is an optional argument that allows us to specify how the WebSocket should jpayne@69: // compress and decompress messages. The configuration is determined by the jpayne@69: // `Sec-WebSocket-Extensions` header during WebSocket negotiation. jpayne@69: // jpayne@69: // `errorHandler` is an optional argument that lets callers throw custom exceptions for WebSocket jpayne@69: // protocol errors. jpayne@69: jpayne@69: struct WebSocketPipe { jpayne@69: kj::Own ends[2]; jpayne@69: }; jpayne@69: jpayne@69: WebSocketPipe newWebSocketPipe(); jpayne@69: // Create a WebSocket pipe. Messages written to one end of the pipe will be readable from the other jpayne@69: // end. No buffering occurs -- a message send does not complete until a corresponding receive jpayne@69: // accepts the message. jpayne@69: jpayne@69: class HttpServerErrorHandler; jpayne@69: class HttpServerCallbacks; jpayne@69: jpayne@69: struct HttpServerSettings { jpayne@69: kj::Duration headerTimeout = 15 * kj::SECONDS; jpayne@69: // After initial connection open, or after receiving the first byte of a pipelined request, jpayne@69: // the client must send the complete request within this time. jpayne@69: jpayne@69: kj::Duration pipelineTimeout = 5 * kj::SECONDS; jpayne@69: // After one request/response completes, we'll wait up to this long for a pipelined request to jpayne@69: // arrive. jpayne@69: jpayne@69: kj::Duration canceledUploadGracePeriod = 1 * kj::SECONDS; jpayne@69: size_t canceledUploadGraceBytes = 65536; jpayne@69: // If the HttpService sends a response and returns without having read the entire request body, jpayne@69: // then we have to decide whether to close the connection or wait for the client to finish the jpayne@69: // request so that it can pipeline the next one. We'll give them a grace period defined by the jpayne@69: // above two values -- if they hit either one, we'll close the socket, but if the request jpayne@69: // completes, we'll let the connection stay open to handle more requests. jpayne@69: jpayne@69: kj::Maybe errorHandler = nullptr; jpayne@69: // Customize how client protocol errors and service application exceptions are handled by the jpayne@69: // HttpServer. If null, HttpServerErrorHandler's default implementation will be used. jpayne@69: jpayne@69: kj::Maybe callbacks = nullptr; jpayne@69: // Additional optional callbacks used to control some server behavior. jpayne@69: jpayne@69: kj::Maybe webSocketErrorHandler = nullptr; jpayne@69: // Customize exceptions thrown on WebSocket protocol errors. jpayne@69: jpayne@69: enum WebSocketCompressionMode { jpayne@69: NO_COMPRESSION, jpayne@69: MANUAL_COMPRESSION, // Gives the application more control when considering whether to compress. jpayne@69: AUTOMATIC_COMPRESSION, // Will perform compression parameter negotiation if client requests it. jpayne@69: }; jpayne@69: WebSocketCompressionMode webSocketCompressionMode = NO_COMPRESSION; jpayne@69: }; jpayne@69: jpayne@69: class HttpServerErrorHandler { jpayne@69: public: jpayne@69: virtual kj::Promise handleClientProtocolError( jpayne@69: HttpHeaders::ProtocolError protocolError, kj::HttpService::Response& response); jpayne@69: virtual kj::Promise handleApplicationError( jpayne@69: kj::Exception exception, kj::Maybe response); jpayne@69: virtual kj::Promise handleNoResponse(kj::HttpService::Response& response); jpayne@69: // Override these functions to customize error handling during the request/response cycle. jpayne@69: // jpayne@69: // Client protocol errors arise when the server receives an HTTP message that fails to parse. As jpayne@69: // such, HttpService::request() will not have been called yet, and the handler is always jpayne@69: // guaranteed an opportunity to send a response. The default implementation of jpayne@69: // handleClientProtocolError() replies with a 400 Bad Request response. jpayne@69: // jpayne@69: // Application errors arise when HttpService::request() throws an exception. The default jpayne@69: // implementation of handleApplicationError() maps the following exception types to HTTP statuses, jpayne@69: // and generates bodies from the stringified exceptions: jpayne@69: // jpayne@69: // - OVERLOADED: 503 Service Unavailable jpayne@69: // - UNIMPLEMENTED: 501 Not Implemented jpayne@69: // - DISCONNECTED: (no response) jpayne@69: // - FAILED: 500 Internal Server Error jpayne@69: // jpayne@69: // No-response errors occur when HttpService::request() allows its promise to settle before jpayne@69: // sending a response. The default implementation of handleNoResponse() replies with a 500 jpayne@69: // Internal Server Error response. jpayne@69: // jpayne@69: // Unlike `HttpService::request()`, when calling `response.send()` in the context of one of these jpayne@69: // functions, a "Connection: close" header will be added, and the connection will be closed. jpayne@69: // jpayne@69: // Also unlike `HttpService::request()`, it is okay to return kj::READY_NOW without calling jpayne@69: // `response.send()`. In this case, no response will be sent, and the connection will be closed. jpayne@69: jpayne@69: virtual void handleListenLoopException(kj::Exception&& exception); jpayne@69: // Override this function to customize error handling for individual connections in the jpayne@69: // `listenHttp()` overload which accepts a ConnectionReceiver reference. jpayne@69: // jpayne@69: // The default handler uses KJ_LOG() to log the exception as an error. jpayne@69: }; jpayne@69: jpayne@69: class HttpServerCallbacks { jpayne@69: public: jpayne@69: virtual bool shouldClose() { return false; } jpayne@69: // Whenever the HttpServer begins response headers, it will check `shouldClose()` to decide jpayne@69: // whether to send a `Connection: close` header and close the connection. jpayne@69: // jpayne@69: // This can be useful e.g. if the server has too many connections open and wants to shed some jpayne@69: // of them. Note that to implement graceful shutdown of a server, you should use jpayne@69: // `HttpServer::drain()` instead. jpayne@69: }; jpayne@69: jpayne@69: class HttpServer final: private kj::TaskSet::ErrorHandler { jpayne@69: // Class which listens for requests on ports or connections and sends them to an HttpService. jpayne@69: jpayne@69: public: jpayne@69: typedef HttpServerSettings Settings; jpayne@69: typedef kj::Function(kj::AsyncIoStream&)> HttpServiceFactory; jpayne@69: class SuspendableRequest; jpayne@69: typedef kj::Function>(SuspendableRequest&)> jpayne@69: SuspendableHttpServiceFactory; jpayne@69: jpayne@69: HttpServer(kj::Timer& timer, const HttpHeaderTable& requestHeaderTable, HttpService& service, jpayne@69: Settings settings = Settings()); jpayne@69: // Set up an HttpServer that directs incoming connections to the given service. The service jpayne@69: // may be a host service or a proxy service depending on whether you are intending to implement jpayne@69: // an HTTP server or an HTTP proxy. jpayne@69: jpayne@69: HttpServer(kj::Timer& timer, const HttpHeaderTable& requestHeaderTable, jpayne@69: HttpServiceFactory serviceFactory, Settings settings = Settings()); jpayne@69: // Like the other constructor, but allows a new HttpService object to be used for each jpayne@69: // connection, based on the connection object. This is particularly useful for capturing the jpayne@69: // client's IP address and injecting it as a header. jpayne@69: jpayne@69: kj::Promise drain(); jpayne@69: // Stop accepting new connections or new requests on existing connections. Finish any requests jpayne@69: // that are already executing, then close the connections. Returns once no more requests are jpayne@69: // in-flight. jpayne@69: jpayne@69: kj::Promise listenHttp(kj::ConnectionReceiver& port); jpayne@69: // Accepts HTTP connections on the given port and directs them to the handler. jpayne@69: // jpayne@69: // The returned promise never completes normally. It may throw if port.accept() throws. Dropping jpayne@69: // the returned promise will cause the server to stop listening on the port, but already-open jpayne@69: // connections will continue to be served. Destroy the whole HttpServer to cancel all I/O. jpayne@69: jpayne@69: kj::Promise listenHttp(kj::Own connection); jpayne@69: // Reads HTTP requests from the given connection and directs them to the handler. A successful jpayne@69: // completion of the promise indicates that all requests received on the connection resulted in jpayne@69: // a complete response, and the client closed the connection gracefully or drain() was called. jpayne@69: // The promise throws if an unparsable request is received or if some I/O error occurs. Dropping jpayne@69: // the returned promise will cancel all I/O on the connection and cancel any in-flight requests. jpayne@69: jpayne@69: kj::Promise listenHttpCleanDrain(kj::AsyncIoStream& connection); jpayne@69: // Like listenHttp(), but allows you to potentially drain the server without closing connections. jpayne@69: // The returned promise resolves to `true` if the connection has been left in a state where a jpayne@69: // new HttpServer could potentially accept further requests from it. If `false`, then the jpayne@69: // connection is either in an inconsistent state or already completed a closing handshake; the jpayne@69: // caller should close it without any further reads/writes. Note this only ever returns `true` jpayne@69: // if you called `drain()` -- otherwise this server would keep handling the connection. jpayne@69: jpayne@69: class SuspendedRequest { jpayne@69: // SuspendedRequest is a representation of a request immediately after parsing the method line and jpayne@69: // headers. You can obtain one of these by suspending a request by calling jpayne@69: // SuspendableRequest::suspend(), then later resume the request with another call to jpayne@69: // listenHttpCleanDrain(). jpayne@69: jpayne@69: public: jpayne@69: // Nothing, this is an opaque type. jpayne@69: jpayne@69: private: jpayne@69: SuspendedRequest(kj::Array, kj::ArrayPtr, kj::OneOf, kj::StringPtr, HttpHeaders); jpayne@69: jpayne@69: kj::Array buffer; jpayne@69: // A buffer containing at least the request's method, URL, and headers, and possibly content jpayne@69: // thereafter. jpayne@69: jpayne@69: kj::ArrayPtr leftover; jpayne@69: // Pointer to the end of the request headers. If this has a non-zero length, then our buffer jpayne@69: // contains additional content, presumably the head of the request body. jpayne@69: jpayne@69: kj::OneOf method; jpayne@69: kj::StringPtr url; jpayne@69: HttpHeaders headers; jpayne@69: // Parsed request front matter. `url` and `headers` both store pointers into `buffer`. jpayne@69: jpayne@69: friend class HttpServer; jpayne@69: }; jpayne@69: jpayne@69: kj::Promise listenHttpCleanDrain(kj::AsyncIoStream& connection, jpayne@69: SuspendableHttpServiceFactory factory, jpayne@69: kj::Maybe suspendedRequest = nullptr); jpayne@69: // Like listenHttpCleanDrain(), but allows you to suspend requests. jpayne@69: // jpayne@69: // When this overload is in use, the HttpServer's default HttpService or HttpServiceFactory is not jpayne@69: // used. Instead, the HttpServer reads the request method line and headers, then calls `factory` jpayne@69: // with a SuspendableRequest representing the request parsed so far. The factory may then return jpayne@69: // a kj::Own for that specific request, or it may call SuspendableRequest::suspend() jpayne@69: // and return nullptr. (It is an error for the factory to return nullptr without also calling jpayne@69: // suspend(); this will result in a rejected listenHttpCleanDrain() promise.) jpayne@69: // jpayne@69: // If the factory chooses to suspend, the listenHttpCleanDrain() promise is resolved with false jpayne@69: // at the earliest opportunity. jpayne@69: // jpayne@69: // SuspendableRequest::suspend() returns a SuspendedRequest. You can resume this request later by jpayne@69: // calling this same listenHttpCleanDrain() overload with the original connection stream, and the jpayne@69: // SuspendedRequest in question. jpayne@69: // jpayne@69: // This overload of listenHttpCleanDrain() implements draining, as documented above. Note that the jpayne@69: // returned promise will resolve to false (not clean) if a request is suspended. jpayne@69: jpayne@69: private: jpayne@69: class Connection; jpayne@69: jpayne@69: kj::Timer& timer; jpayne@69: const HttpHeaderTable& requestHeaderTable; jpayne@69: kj::OneOf service; jpayne@69: Settings settings; jpayne@69: jpayne@69: bool draining = false; jpayne@69: kj::ForkedPromise onDrain; jpayne@69: kj::Own> drainFulfiller; jpayne@69: jpayne@69: uint connectionCount = 0; jpayne@69: kj::Maybe>> zeroConnectionsFulfiller; jpayne@69: jpayne@69: kj::TaskSet tasks; jpayne@69: jpayne@69: HttpServer(kj::Timer& timer, const HttpHeaderTable& requestHeaderTable, jpayne@69: kj::OneOf service, jpayne@69: Settings settings, kj::PromiseFulfillerPair paf); jpayne@69: jpayne@69: kj::Promise listenLoop(kj::ConnectionReceiver& port); jpayne@69: jpayne@69: void taskFailed(kj::Exception&& exception) override; jpayne@69: jpayne@69: kj::Promise listenHttpImpl(kj::AsyncIoStream& connection, bool wantCleanDrain); jpayne@69: kj::Promise listenHttpImpl(kj::AsyncIoStream& connection, jpayne@69: SuspendableHttpServiceFactory factory, jpayne@69: kj::Maybe suspendedRequest, jpayne@69: bool wantCleanDrain); jpayne@69: }; jpayne@69: jpayne@69: class HttpServer::SuspendableRequest { jpayne@69: // Interface passed to the SuspendableHttpServiceFactory parameter of listenHttpCleanDrain(). jpayne@69: jpayne@69: public: jpayne@69: kj::OneOf method; jpayne@69: kj::StringPtr url; jpayne@69: const HttpHeaders& headers; jpayne@69: // Parsed request front matter, so the implementer can decide whether to suspend the request. jpayne@69: jpayne@69: SuspendedRequest suspend(); jpayne@69: // Signal to the HttpServer that the current request loop should be exited. Return a jpayne@69: // SuspendedRequest, containing HTTP method, URL, and headers access, along with the actual header jpayne@69: // buffer. The request can be later resumed with a call to listenHttpCleanDrain() using the same jpayne@69: // connection. jpayne@69: jpayne@69: private: jpayne@69: explicit SuspendableRequest( jpayne@69: Connection& connection, kj::OneOf method, kj::StringPtr url, const HttpHeaders& headers) jpayne@69: : method(method), url(url), headers(headers), connection(connection) {} jpayne@69: KJ_DISALLOW_COPY_AND_MOVE(SuspendableRequest); jpayne@69: jpayne@69: Connection& connection; jpayne@69: jpayne@69: friend class Connection; jpayne@69: }; jpayne@69: jpayne@69: // ======================================================================================= jpayne@69: // inline implementation jpayne@69: jpayne@69: inline void HttpHeaderId::requireFrom(const HttpHeaderTable& table) const { jpayne@69: KJ_IREQUIRE(this->table == nullptr || this->table == &table, jpayne@69: "the provided HttpHeaderId is from the wrong HttpHeaderTable"); jpayne@69: } jpayne@69: jpayne@69: inline kj::Own HttpHeaderTable::Builder::build() { jpayne@69: table->buildStatus = BuildStatus::FINISHED; jpayne@69: return kj::mv(table); jpayne@69: } jpayne@69: inline HttpHeaderTable& HttpHeaderTable::Builder::getFutureTable() { return *table; } jpayne@69: jpayne@69: inline uint HttpHeaderTable::idCount() const { return namesById.size(); } jpayne@69: inline bool HttpHeaderTable::isReady() const { jpayne@69: switch (buildStatus) { jpayne@69: case BuildStatus::UNSTARTED: return true; jpayne@69: case BuildStatus::BUILDING: return false; jpayne@69: case BuildStatus::FINISHED: return true; jpayne@69: } jpayne@69: jpayne@69: KJ_UNREACHABLE; jpayne@69: } jpayne@69: jpayne@69: inline kj::StringPtr HttpHeaderTable::idToString(HttpHeaderId id) const { jpayne@69: id.requireFrom(*this); jpayne@69: return namesById[id.id]; jpayne@69: } jpayne@69: jpayne@69: inline kj::Maybe HttpHeaders::get(HttpHeaderId id) const { jpayne@69: id.requireFrom(*table); jpayne@69: auto result = indexedHeaders[id.id]; jpayne@69: return result == nullptr ? kj::Maybe(nullptr) : result; jpayne@69: } jpayne@69: jpayne@69: inline void HttpHeaders::unset(HttpHeaderId id) { jpayne@69: id.requireFrom(*table); jpayne@69: indexedHeaders[id.id] = nullptr; jpayne@69: } jpayne@69: jpayne@69: template jpayne@69: inline void HttpHeaders::forEach(Func&& func) const { jpayne@69: for (auto i: kj::indices(indexedHeaders)) { jpayne@69: if (indexedHeaders[i] != nullptr) { jpayne@69: func(table->idToString(HttpHeaderId(table, i)), indexedHeaders[i]); jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: for (auto& header: unindexedHeaders) { jpayne@69: func(header.name, header.value); jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: template jpayne@69: inline void HttpHeaders::forEach(Func1&& func1, Func2&& func2) const { jpayne@69: for (auto i: kj::indices(indexedHeaders)) { jpayne@69: if (indexedHeaders[i] != nullptr) { jpayne@69: func1(HttpHeaderId(table, i), indexedHeaders[i]); jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: for (auto& header: unindexedHeaders) { jpayne@69: func2(header.name, header.value); jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: // ======================================================================================= jpayne@69: namespace _ { // private implementation details for WebSocket compression jpayne@69: jpayne@69: kj::ArrayPtr splitNext(kj::ArrayPtr& cursor, char delimiter); jpayne@69: jpayne@69: void stripLeadingAndTrailingSpace(ArrayPtr& str); jpayne@69: jpayne@69: kj::Vector> splitParts(kj::ArrayPtr input, char delim); jpayne@69: jpayne@69: struct KeyMaybeVal { jpayne@69: ArrayPtr key; jpayne@69: kj::Maybe> val; jpayne@69: }; jpayne@69: jpayne@69: kj::Array toKeysAndVals(const kj::ArrayPtr>& params); jpayne@69: jpayne@69: struct UnverifiedConfig { jpayne@69: // An intermediate representation of the final `CompressionParameters` struct; used during parsing. jpayne@69: // We use it to ensure the structure of an offer is generally correct, see jpayne@69: // `populateUnverifiedConfig()` for details. jpayne@69: bool clientNoContextTakeover = false; jpayne@69: bool serverNoContextTakeover = false; jpayne@69: kj::Maybe> clientMaxWindowBits = nullptr; jpayne@69: kj::Maybe> serverMaxWindowBits = nullptr; jpayne@69: }; jpayne@69: jpayne@69: kj::Maybe populateUnverifiedConfig(kj::Array& params); jpayne@69: jpayne@69: kj::Maybe validateCompressionConfig(UnverifiedConfig&& config, jpayne@69: bool isAgreement); jpayne@69: jpayne@69: kj::Vector findValidExtensionOffers(StringPtr offers); jpayne@69: jpayne@69: kj::String generateExtensionRequest(const ArrayPtr& extensions); jpayne@69: jpayne@69: kj::Maybe tryParseExtensionOffers(StringPtr offers); jpayne@69: jpayne@69: kj::Maybe tryParseAllExtensionOffers(StringPtr offers, jpayne@69: CompressionParameters manualConfig); jpayne@69: jpayne@69: kj::Maybe compareClientAndServerConfigs(CompressionParameters requestConfig, jpayne@69: CompressionParameters manualConfig); jpayne@69: jpayne@69: kj::String generateExtensionResponse(const CompressionParameters& parameters); jpayne@69: jpayne@69: kj::OneOf tryParseExtensionAgreement( jpayne@69: const Maybe& clientOffer, jpayne@69: StringPtr agreedParameters); jpayne@69: jpayne@69: }; // namespace _ (private) jpayne@69: jpayne@69: } // namespace kj jpayne@69: jpayne@69: KJ_END_HEADER