// Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 #pragma once #include "basics.h" #include "blob.h" #include "cf-property.h" #include "form-data.h" #include "headers.h" #include "queue.h" #include "web-socket.h" #include "worker-rpc.h" #include #include #include #include #include #include #include namespace workerd::api { // Base class for Request and Response. In JavaScript, this class is a mixin, meaning no one will // be instantiating objects of this type -- it exists solely to house body-related functionality // common to both Requests and Responses. class Body: public jsg::Object { public: // The types of objects from which a Body can be created. // // If the object is a ReadableStream, Body will adopt it directly; otherwise the object is some // sort of buffer-like source. In this case, Body will store its own ReadableStream that wraps the // source, and it keeps a reference to the source object around. This allows Requests and // Responses created from Strings, ArrayBuffers, FormDatas, Blobs, or URLSearchParams to be // retransmitted. // // For an example of where this is important, consider a POST Request in redirect-follow mode and // containing a body: if passed to a fetch() call that results in a 307 or 308 response, fetch() // will re-POST to the new URL. If the body was constructed from a ReadableStream, this re-POST // will fail, because there is no body source left. On the other hand, if the body was constructed // from any of the other source types, Body can create a new ReadableStream from the source, and // the POST will successfully retransmit. using Initializer = kj::OneOf, kj::String, jsg::JsRef, jsg::Ref, jsg::Ref, jsg::Ref, jsg::Ref, jsg::AsyncGeneratorIgnoringStrings>; struct RefcountedBytes final: public kj::Refcounted { kj::Array bytes; RefcountedBytes(kj::Array&& bytes): bytes(kj::mv(bytes)) {} JSG_MEMORY_INFO(RefcountedBytes) { tracker.trackFieldWithSize("bytes", bytes.size()); } }; // The Fetch spec calls this type the body's "source", even though it really is a buffer. I end // talking about things like "a buffer-backed body", whereas in standardese I should say // "a body with a non-null source". // // I find that confusing, so let's just call it what it is: a Body::Buffer. struct Buffer { // In order to reconstruct buffer-backed ReadableStreams without gratuitous array copying, we // need to be able to tie the lifetime of the source buffer to the lifetime of the // ReadableStream's native stream, AND the lifetime of the Body itself. Thus we need // refcounting. // // NOTE: ownBytes may contain a v8::Global reference, hence instances of `Buffer` must exist // only within the V8 heap space. kj::OneOf, jsg::Ref, jsg::JsRef> ownBytes; // TODO(cleanup): When we integrate with V8's garbage collection APIs, we need to account for // that here. // Bodies constructed from buffers rather than ReadableStreams can be retransmitted if necessary // (e.g. for redirects, authentication). In these cases, we need to keep an ArrayPtr view onto // the Array source itself, because the source may be a string, and thus have a trailing nul // byte. kj::ArrayPtr view; Buffer() = default; Buffer(jsg::Lock& js, jsg::JsBufferSource& source) : ownBytes(source.addRef(js)), view(source.asArrayPtr()) { // If the BufferSource is resizable, then it should have been copied into // a kj::Array and passed in to that constructor. KJ_ASSERT(!source.isResizable()); } Buffer(kj::Array array) : ownBytes(kj::refcounted(kj::mv(array))), view(ownBytes.get>()->bytes) {} Buffer(kj::String string) : ownBytes(kj::refcounted(string.releaseArray().releaseAsBytes())), view([this] { auto bytesIncludingNull = ownBytes.get>()->bytes.asPtr(); return bytesIncludingNull.first(bytesIncludingNull.size() - 1); }()) {} Buffer(jsg::Ref blob) : ownBytes(kj::mv(blob)), view(ownBytes.get>()->getData()) {} Buffer clone(jsg::Lock& js); JSG_MEMORY_INFO(Buffer) { KJ_SWITCH_ONEOF(ownBytes) { KJ_CASE_ONEOF(ref, jsg::JsRef) { tracker.trackField("ref", ref); } KJ_CASE_ONEOF(bytes, kj::Own) { tracker.trackField("bytes", bytes); } KJ_CASE_ONEOF(blob, jsg::Ref) { tracker.trackField("blob", blob); } } } }; struct Impl { jsg::Ref stream; kj::Maybe buffer; JSG_MEMORY_INFO(Impl) { tracker.trackField("stream", stream); tracker.trackField("buffer", buffer); } }; struct ExtractedBody { ExtractedBody(jsg::Ref stream, kj::Maybe source = kj::none, kj::Maybe contentType = kj::none); Impl impl; kj::Maybe contentType; }; // Implements the "extract a body" algorithm from the Fetch spec. // https://fetch.spec.whatwg.org/#concept-bodyinit-extract static ExtractedBody extractBody(jsg::Lock& js, Initializer init); explicit Body(jsg::Lock& js, kj::Maybe init, Headers& headers); kj::Maybe getBodyBuffer(jsg::Lock& js); // The following body rewind/nullification functions are helpers for implementing fetch() redirect // handling. // True if this body is null or buffer-backed, false if this body is a ReadableStream. bool canRewindBody(); // Reconstruct this body from its backing buffer. Precondition: `canRewindBody() == true`. void rewindBody(jsg::Lock& js); // Convert this body into a null body. void nullifyBody(); // --------------------------------------------------------------------------- // JS API kj::Maybe> getBody(); bool getBodyUsed(); jsg::Promise arrayBuffer(jsg::Lock& js); jsg::Promise bytes(jsg::Lock& js); jsg::Promise text(jsg::Lock& js); jsg::Promise> formData(jsg::Lock& js); jsg::Promise json(jsg::Lock& js); jsg::Promise> blob(jsg::Lock& js); JSG_RESOURCE_TYPE(Body, CompatibilityFlags::Reader flags) { if (flags.getJsgPropertyOnPrototypeTemplate()) { JSG_READONLY_PROTOTYPE_PROPERTY(body, getBody); JSG_READONLY_PROTOTYPE_PROPERTY(bodyUsed, getBodyUsed); } else { JSG_READONLY_INSTANCE_PROPERTY(body, getBody); JSG_READONLY_INSTANCE_PROPERTY(bodyUsed, getBodyUsed); } JSG_METHOD(arrayBuffer); JSG_METHOD(bytes); JSG_METHOD(text); JSG_METHOD(json); JSG_METHOD(formData); JSG_METHOD(blob); if (flags.getFetchIterableTypeSupport()) { JSG_TS_DEFINE(type BodyInit = ReadableStream | string | ArrayBuffer | ArrayBufferView | Blob | URLSearchParams | FormData | Iterable | AsyncIterable); } else { JSG_TS_DEFINE(type BodyInit = ReadableStream | string | ArrayBuffer | ArrayBufferView | Blob | URLSearchParams | FormData); } // All type aliases get inlined when exporting RTTI, but this type alias is included by // the official TypeScript types, so users might be depending on it. JSG_TS_OVERRIDE({ json(): Promise; bytes(): Promise; arrayBuffer(): Promise; }); // Allow JSON body type to be specified } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("impl", impl); } protected: // Helper to implement Request/Response::clone(). kj::Maybe clone(jsg::Lock& js); private: kj::Maybe impl; // HACK: This `headersRef` variable refers to a Headers object in the Request/Response subclass. // As such, it will briefly dangle during object destruction. While unlikely to be an issue, // it's worth being aware of. Headers& headersRef; void visitForGc(jsg::GcVisitor& visitor) { KJ_IF_SOME(i, impl) { visitor.visit(i.stream); } } }; // Controls how response bodies are encoded/decoded according to Content-Encoding headers enum class Response_BodyEncoding { AUTO, // Automatically encode/decode based on Content-Encoding headers MANUAL // Treat Content-Encoding headers as opaque (no automatic encoding/decoding) }; class Request; class Response; struct RequestInitializerDict; class Socket; struct SocketOptions; struct SocketAddress; using AnySocketAddress = kj::OneOf; // Represents a client to a remote "web service". // // Originally, this meant an HTTP service, and `Fetcher` had just one method, `fetch()`, hence the // name. However, `Fetcher` is really the JavaScript type for a `WorkerInterface`, and is used in // particular to represent service bindings as well as Durable Object stubs. As such, as Workers // have grown new ways to talk to other Workers, `Fetcher` has added methods other than `fetch()`. // // TODO(cleanup): This probably doesn't belong in `http.h` anymore. And perhaps it should be // renamed, though I haven't heard any great suggestions for what the name should be. class Fetcher: public JsRpcClientProvider { public: // Should we use a fake https base url if we lack a scheme+authority? enum class RequiresHostAndProtocol { YES, NO }; // `channel` is what to pass to IoContext::getSubrequestChannel() to get a WorkerInterface // representing this Fetcher. Note that different requests potentially have different client // objects because a WorkerInterface is a KJ I/O object and therefore tied to a thread. // Abstractly, within a worker instance, the same channel always refers to the same Fetcher, even // though the WorkerInterface object changes from request to request. // // If `requiresHost` is false, then requests using this Fetcher are allowed to specify a // URL that has no protocol or host. // // See pipeline.capnp or request-context.h for an explanation of `isInHouse`. explicit Fetcher(uint channel, RequiresHostAndProtocol requiresHost, bool isInHouse = false) : channelOrClientFactory(channel), requiresHost(requiresHost), isInHouse(isInHouse) {} // Create a Fetcher bound to an IoChannelFactory::SubrequestChannel object rather than a numeric // channel. This Fetcher will inherently be bound to the current I/O context. explicit Fetcher(IoOwn subrequestChannel, RequiresHostAndProtocol requiresHost = RequiresHostAndProtocol::YES, bool isInHouse = false) : channelOrClientFactory(kj::mv(subrequestChannel)), requiresHost(requiresHost), isInHouse(isInHouse) {} // Used by Fetchers that use ad-hoc, single-use WorkerInterface instances, such as ones // created for Actors. // // Implementations that create HTTP connections should route through // IoContext::getSubrequestNoChecks() (or getSubrequest()) internally, to ensure external memory // adjustment and other subrequest accounting are applied. // // TODO(cleanup): Consider removing this in favor of `IoChannelFactory::SubrequestChannel`, which // is almost the same thing. class OutgoingFactory { public: virtual kj::Own newSingleUseClient(kj::Maybe cfStr) = 0; // Get a `SubrequestChannel` representing this Fetcher. This is used especially when the // Fetcher is being passed to another isolate. virtual kj::Own getSubrequestChannel() { // TODO(soon): Update all implementations and remove this default implementation. KJ_UNIMPLEMENTED("this Fetcher doesn't yet implement getSubrequestChannel()"); } }; // Used by Fetchers that obtain their HttpClient in a custom way, but which aren't tied // to a specific I/O context. The factory object moves with the isolate across threads and // contexts, and must work from any context. // // Same as OutgoingFactory: implementations that create HTTP connections should route through // IoContext::getSubrequestNoChecks() internally. class CrossContextOutgoingFactory { public: virtual kj::Own newSingleUseClient( IoContext& context, kj::Maybe cfStr) = 0; virtual kj::Own getSubrequestChannel(IoContext& context) { // TODO(soon): Update all implementations and remove this default implementation. KJ_UNIMPLEMENTED("this Fetcher doesn't yet implement getSubrequestChannel()"); } }; // `outgoingFactory` is used for Fetchers that use ad-hoc WorkerInterface instances, such as ones // created for Actors. Fetcher(IoOwn outgoingFactory, RequiresHostAndProtocol requiresHost, bool isInHouse = false) : channelOrClientFactory(kj::mv(outgoingFactory)), requiresHost(requiresHost), isInHouse(isInHouse) {} // `outgoingFactory` is used for Fetchers that use ad-hoc WorkerInterface instances, but doesn't // require an IoContext Fetcher(kj::Own outgoingFactory, RequiresHostAndProtocol requiresHost, bool isInHouse = false) : channelOrClientFactory(kj::mv(outgoingFactory)), requiresHost(requiresHost), isInHouse(isInHouse) {} // Returns an `WorkerInterface` that is only valid for the lifetime of the current // `IoContext`. kj::Own getClient( IoContext& ioContext, kj::Maybe cfStr, kj::ConstString operationName); // Result of getClient call that includes optional trace context struct ClientWithTracing { kj::Own client; kj::Maybe traceContext; }; // Get client and optionally create trace context, all in one call ClientWithTracing getClientWithTracing( IoContext& ioContext, kj::Maybe cfStr, kj::ConstString operationName); // Get a SubrequestChannel representing this Fetcher. kj::Own getSubrequestChannel(IoContext& ioContext); // Wraps kj::Url::parse to take into account whether the Fetcher requires a host to be // specified on URLs, Fetcher-specific URL decoding options, and error handling. kj::Url parseUrl(jsg::Lock& js, kj::StringPtr url); jsg::Ref connect( jsg::Lock& js, AnySocketAddress address, jsg::Optional options); jsg::Promise> fetch(jsg::Lock& js, kj::OneOf, kj::String> requestOrUrl, jsg::Optional>> requestInit); using GetResult = kj::OneOf, jsg::BufferSource, kj::String, jsg::Value>; jsg::Promise get(jsg::Lock& js, kj::String url, jsg::Optional type); // Optional parameter for passing options into a Fetcher::put. Initially // intended for supporting expiration times in KV bindings. struct PutOptions { jsg::Optional expiration; jsg::Optional expirationTtl; JSG_STRUCT(expiration, expirationTtl); }; jsg::Promise put( jsg::Lock& js, kj::String url, Body::Initializer body, jsg::Optional options); jsg::Promise delete_(jsg::Lock& js, kj::String url); // Representation of a queue message for use when invoking the queue() event handler on another // worker via a service binding. struct ServiceBindingQueueMessage { kj::String id; kj::Date timestamp; jsg::Optional body; jsg::Optional> serializedBody; uint16_t attempts; JSG_STRUCT(id, timestamp, body, serializedBody, attempts); JSG_STRUCT_TS_OVERRIDE(type ServiceBindingQueueMessage = { id: string; timestamp: Date; attempts: number; } & ( | { body: Body } | { serializedBody: ArrayBuffer | ArrayBufferView } )); }; struct QueueResult { kj::String outcome; bool ackAll; QueueRetryBatch retryBatch; kj::Array explicitAcks; kj::Array retryMessages; JSG_STRUCT(outcome, ackAll, retryBatch, explicitAcks, retryMessages); }; jsg::Promise queue(jsg::Lock& js, kj::String queueName, kj::Array messages, jsg::Optional metadata); struct ScheduledOptions { jsg::Optional scheduledTime; jsg::Optional cron; JSG_STRUCT(scheduledTime, cron); }; struct ScheduledResult { kj::String outcome; bool noRetry; JSG_STRUCT(outcome, noRetry); }; jsg::Promise scheduled(jsg::Lock& js, jsg::Optional options); kj::Maybe> getRpcMethod(jsg::Lock& js, kj::String name); // Internal method for use from bindings code. It skips compatibility flags checks. kj::Maybe> getRpcMethodInternal(jsg::Lock& js, kj::String name); kj::Maybe> getRpcMethodForTestOnly(jsg::Lock& js, kj::String name) { return getRpcMethod(js, kj::mv(name)); } rpc::JsRpcTarget::Client getClientForOneCall( jsg::Lock& js, kj::Vector& path) override; JSG_RESOURCE_TYPE(Fetcher, CompatibilityFlags::Reader flags) { // WARNING: New JSG_METHODs on Fetcher must be gated via compatibility flag to prevent // conflicts with JS RPC methods (implemented via the wildcard property). Ideally, we do not // add any new methods here, and instead rely on RPC for all future needs. // // Similarly, subclasses of `Fetcher` (notably, `DurableObject`) must follow the same rule, // as any methods added to them will shadow RPC methods of the same name. JSG_METHOD(fetch); JSG_METHOD(connect); if (flags.getServiceBindingExtraHandlers()) { JSG_METHOD(queue); JSG_METHOD(scheduled); JSG_TS_OVERRIDE(type Fetcher< T extends Rpc.EntrypointBranded | undefined = undefined, Reserved extends string = never > = ( T extends Rpc.EntrypointBranded ? Rpc.Provider : unknown ) & { fetch(input: RequestInfo | URL, init?: RequestInit): Promise; connect(address: SocketAddress | string, options?: SocketOptions): Socket; queue(queueName: string, messages: ServiceBindingQueueMessage[], metadata?: MessageBatchMetadata): Promise; scheduled(options?: FetcherScheduledOptions): Promise; }); } else { JSG_TS_OVERRIDE(type Fetcher< T extends Rpc.EntrypointBranded | undefined = undefined, Reserved extends string = never > = ( T extends Rpc.EntrypointBranded ? Rpc.Provider : unknown ) & { fetch(input: RequestInfo | URL, init?: RequestInit): Promise; connect(address: SocketAddress | string, options?: SocketOptions): Socket; }); } JSG_TS_DEFINE( type Service< T extends | (new (...args: any[]) => Rpc.WorkerEntrypointBranded) | Rpc.WorkerEntrypointBranded | ExportedHandler | undefined = undefined, > = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? Fetcher> : T extends Rpc.WorkerEntrypointBranded ? Fetcher : T extends Exclude ? never : Fetcher ); if (!flags.getFetcherNoGetPutDelete()) { // These helpers just map to `fetch()` with the corresponding HTTP method. They were never // documented and probably never should have been defined. We are removing them to make room // for RPC. JSG_METHOD(get); JSG_METHOD(put); JSG_METHOD_NAMED(delete, delete_); } JSG_WILDCARD_PROPERTY(getRpcMethod); if (flags.getWorkerdExperimental()) { // We export a copy of getRpcMethod for use in tests only which allows the caller to provide // an arbitrary string as the method name. This allows invoking methods that would normally // be shadowed by non-wildcard methods. JSG_METHOD(getRpcMethodForTestOnly); } } void serialize(jsg::Lock& js, jsg::Serializer& serializer); static jsg::Ref deserialize( jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer); JSG_SERIALIZABLE(rpc::SerializationTag::SERVICE_STUB); private: kj::OneOf, kj::Own, IoOwn> channelOrClientFactory; RequiresHostAndProtocol requiresHost; bool isInHouse; }; // Type of the second parameter to Request's constructor. Also the type of the second parameter // to fetch(). // // When adding new properties to this struct, don't forget to update Request::serialize(). struct RequestInitializerDict { jsg::Optional method; jsg::Optional headers; // The script author may specify an empty body either implicitly, by allowing this property to // be undefined, or explicitly, by setting this property to null. To support both cases, this // body initializer must be Optional>. jsg::Optional> body; // follow, error, manual (default follow) jsg::Optional redirect; jsg::Optional>> fetcher; // Cloudflare-specific feature flags. jsg::Optional> cf; // TODO(someday): We should generalize this concept to sending control information to // downstream workers in the pipeline. That is, when multiple workers apply to the same // request (with the first worker's subrequests being passed to the next worker), then // first worker should be able to set flags on the request that the second worker can see. // Perhaps we should say that any field you set on a Request object will be JSON-serialized // and passed on to the next worker? Then `cf` is just one such field: it's not special, // it's only named `cf` because the consumer is Cloudflare code. // The fetch standard defines additional properties that are really only relevant in browser // implementations that implement CORS. The WinterTC has determined that for non-browser // environments, these should be silently ignoredif the runtime has no use for them. // * mode // * credentials // * referrer // * referrerPolicy // * keepalive // * window // In browsers this controls the local browser cache. For Cloudflare Workers it could control the // Cloudflare edge cache. While the standard defines a number of values for this property, our // implementation supports only three: undefined (identifying the default caching behavior that // has been implemented by the runtime), "no-store", and "no-cache". jsg::Optional cache; // Subresource integrity (check response against a given hash). // We do not implement integrity checking, however, we will accept either an undefined // or empty string value for the property. If any other value is given we will throw. jsg::Optional integrity; // The spec declares this optional, but is unclear on whether it is nullable. The spec is also // unclear on whether the `Request.signal` property is nullable. If `Request.signal` is nullable, // then we definitely have to accept `null` as an input here, otherwise // `new Request(url, {...request})` will fail when `request.signal` is null. However, it's also // possible that neither property should be nullable. Indeed, it appears that Chrome always // constructs a dummy signal even if none was provided, and uses that. But Chrome is also happy // to accept `null` as an input, so if we're doing what Chrome does, then we should accept // `null`. jsg::Optional>> signal; // Controls whether the response body is automatically decoded according to Content-Encoding // headers. Default behavior is "automatic" which means bodies are decoded. Setting this to // "manual" means the raw compressed bytes are returned. jsg::Optional encodeResponseBody; // The duplex option controls whether or not a fetch is expected to send the entire request // before processing the response. The default value ("half"), which is currently the only // option supported by the standard, dictates that the request is fully sent before handling // the response. There are currently a proposal to add a "full" option which is the model // we support. Once "full" is added, we need to update this to accept either undefined or // "full", and possibly decide if we want to support the "half" option. For now we'll just // ignore this option. Enabling this option later might require a compatibility flag. // jsg::Optional duplex; // TODO(conform): Might support later? // Specifies the relative priority of the request. We currently do not make use of this // information. Per the spec, the only values acceptable for the priority option are // "high", "low", and "auto", with "auto" being considered the default. For now we'll just // ignore this option. Enabling this option later might require a compatibility flag. // jsg::Optional priority; // TODO(conform): Might support later? JSG_STRUCT( method, headers, body, redirect, fetcher, cf, cache, integrity, signal, encodeResponseBody); JSG_STRUCT_TS_OVERRIDE_DYNAMIC(CompatibilityFlags::Reader flags) { if (flags.getCacheOptionEnabled()) { if (flags.getCacheReload()) { JSG_TS_OVERRIDE(RequestInit { headers?: HeadersInit; body?: BodyInit | null; cache?: 'no-store' | 'no-cache' | 'reload'; cf?: Cf; encodeResponseBody?: "automatic" | "manual"; }); } else if (flags.getCacheNoCache()) { JSG_TS_OVERRIDE(RequestInit { headers?: HeadersInit; body?: BodyInit | null; cache?: 'no-store' | 'no-cache'; cf?: Cf; encodeResponseBody?: "automatic" | "manual"; }); } else { JSG_TS_OVERRIDE(RequestInit { headers?: HeadersInit; body?: BodyInit | null; cache?: 'no-store'; cf?: Cf; encodeResponseBody?: "automatic" | "manual"; }); } } else { JSG_TS_OVERRIDE(RequestInit { headers?: HeadersInit; body?: BodyInit | null; cache?: never; cf?: Cf; encodeResponseBody?: "automatic" | "manual"; }); } } // This method is called within tryUnwrap() when the type is unpacked from v8. // See jsg Readme for more details. void validate(jsg::Lock&); }; class Request final: public Body { public: enum class Redirect { FOLLOW, MANUAL // Note: error mode doesn't make sense for us. }; static kj::Maybe tryParseRedirect(kj::StringPtr redirect); enum class CacheMode { // CacheMode::NONE is set when cache is undefined. It represents the default cache // mode that workers has supported. NONE, NOSTORE, NOCACHE, RELOAD, }; Request(jsg::Lock& js, kj::HttpMethod method, kj::StringPtr url, Redirect redirect, jsg::Ref headers, kj::Maybe> fetcher, kj::Maybe> signal, CfProperty&& cf, kj::Maybe body, kj::Maybe> thisSignal, CacheMode cacheMode = CacheMode::NONE, Response_BodyEncoding responseBodyEncoding = Response_BodyEncoding::AUTO) : Body(js, kj::mv(body), *headers), method(method), url(kj::str(url)), redirect(redirect), headers(kj::mv(headers)), fetcher(kj::mv(fetcher)), cacheMode(cacheMode), cf(kj::mv(cf)), responseBodyEncoding(responseBodyEncoding) { KJ_IF_SOME(s, signal) { // If the AbortSignal will never abort, assigning it to thisSignal instead ensures // that the cancel machinery is not used but the request.signal accessor will still // do the right thing. if (s->getNeverAborts()) { this->thisSignal = s.addRef(); } else { this->signal = s.addRef(); } } } // TODO(conform): Technically, the request's URL should be parsed immediately upon Request // construction, and any errors encountered should be thrown. Instead, we defer parsing until // fetch()-time. This sidesteps an awkward issue: The request URL should be parsed relative to // the service worker script's URL (e.g. https://capnproto.org/sw.js), but edge worker scripts // don't have a script URL, so we have no choice but to parse it as an absolute URL. This means // constructs like `new Request("")` should actually throw TypeError, but constructing Requests // with empty URLs is useful in testing. kj::HttpMethod getMethodEnum() { return method; } void setMethodEnum(kj::HttpMethod newMethod) { method = newMethod; } Redirect getRedirectEnum() { return redirect; } void shallowCopyHeadersTo(kj::HttpHeaders& out); kj::Maybe serializeCfBlobJson(jsg::Lock& js); // --------------------------------------------------------------------------- // JS API using InitializerDict = RequestInitializerDict; using Info = kj::OneOf, kj::String>; using Initializer = kj::OneOf>; // Wrapper around Request::constructor that calls it only if necessary, and returns a // jsg::Ref. // // C++ API, but declared down here because we need the InitializerDict type. static jsg::Ref coerce( jsg::Lock& js, Request::Info input, jsg::Optional init); static jsg::Ref constructor( jsg::Lock& js, Request::Info input, jsg::Optional init); jsg::Ref clone(jsg::Lock& js); kj::StringPtr getMethod(); kj::StringPtr getUrl(); jsg::Ref getHeaders(jsg::Lock& js); kj::StringPtr getRedirect(); kj::Maybe> getFetcher(); // getSignal() is the one that we used internally to determine if there's actually // an AbortSignal that can be triggered to cancel things. The getThisSignal() is // used only on the JavaScript side to conform to the spec, which requires // request.signal to always return an AbortSignal even if one is not actively // used on this request. kj::Maybe> getSignal(); jsg::Ref getThisSignal(jsg::Lock& js); // Clear the request's signal if the 'ignoreForSubrequests' flag is set. This happens when // a request from an incoming fetch is passed-through to another fetch. We want to avoid // aborting the subrequest in that case. void clearSignalIfIgnoredForSubrequest(jsg::Lock& js); // Returns the `cf` field containing Cloudflare feature flags. jsg::Optional getCf(jsg::Lock& js); // The duplex option controls whether or not a fetch is expected to send the entire request // before processing the response. The default value ("half"), which is currently the only // option supported by the standard, dictates that the request is fully sent before handling // the response. There are currently a proposal to add a "full" option which is the model // we support. Once "full" is added, we need to update this to accept either undefined or // "full", and possibly decide if we want to support the "half" option. // jsg::JsValue getDuplex(jsg::Lock& js) { return js.undefined(); } // TODO(conform): Might implement? // These relate to CORS support, which we do not implement. WinterTC has determined that // non-browser implementations that do not implement CORS support should ignore these // entirely as if they were not defined. // * destination // * mode // * credentials // * referrer // * referrerPolicy // * isReloadNavigation // * isHistoryNavigation // * keepalive (see below) // We do not implement support for the keepalive option but we do want to at least provide // the standard property, hard-coded to always be false. WinterTC actually recommends that // this one just be left undefined but we already had this returning false always and it // would require a compat flag to remove. Just keep it as it's harmless. bool getKeepalive() { return false; } // The cache mode determines how HTTP cache is used with the request. jsg::Optional getCache(jsg::Lock& js); CacheMode getCacheMode(); // We do not implement integrity checking at all. However, the spec says that // the default value should be an empty string. When the Request object is // created we verify that the given value is undefined or empty. kj::String getIntegrity() { return kj::String(); } // Get the response body encoding setting for this request Response_BodyEncoding getResponseBodyEncoding() { return responseBodyEncoding; } JSG_RESOURCE_TYPE(Request, CompatibilityFlags::Reader flags) { JSG_INHERIT(Body); JSG_METHOD(clone); JSG_TS_DEFINE(type RequestInfo> = Request | string); // All type aliases get inlined when exporting RTTI, but this type alias is included by // the official TypeScript types, so users might be depending on it. if (flags.getJsgPropertyOnPrototypeTemplate()) { JSG_READONLY_PROTOTYPE_PROPERTY(method, getMethod); JSG_READONLY_PROTOTYPE_PROPERTY(url, getUrl); JSG_READONLY_PROTOTYPE_PROPERTY(headers, getHeaders); JSG_READONLY_PROTOTYPE_PROPERTY(redirect, getRedirect); JSG_READONLY_PROTOTYPE_PROPERTY(fetcher, getFetcher); JSG_READONLY_PROTOTYPE_PROPERTY(signal, getThisSignal); JSG_READONLY_PROTOTYPE_PROPERTY(cf, getCf); // TODO(conform): These are standard properties that we do not implement (see descriptions // above). // JSG_READONLY_PROTOTYPE_PROPERTY(duplex, getDuplex); JSG_READONLY_PROTOTYPE_PROPERTY(integrity, getIntegrity); JSG_READONLY_PROTOTYPE_PROPERTY(keepalive, getKeepalive); if (flags.getCacheOptionEnabled()) { JSG_READONLY_PROTOTYPE_PROPERTY(cache, getCache); if (flags.getCacheReload()) { JSG_TS_OVERRIDE(> { constructor(input: RequestInfo | URL, init?: RequestInit); clone(): Request; cache?: "no-store" | "no-cache" | "reload"; cf?: Cf; }); } else if (flags.getCacheNoCache()) { JSG_TS_OVERRIDE(> { constructor(input: RequestInfo | URL, init?: RequestInit); clone(): Request; cache?: "no-store" | "no-cache"; cf?: Cf; }); } else { JSG_TS_OVERRIDE(> { constructor(input: RequestInfo | URL, init?: RequestInit); clone(): Request; cache?: "no-store"; cf?: Cf; }); } } else { JSG_TS_OVERRIDE(> { constructor(input: RequestInfo | URL, init?: RequestInit); clone(): Request; cf?: Cf; }); } // Use `RequestInfo` and `RequestInit` type aliases in constructor instead of inlining. // `CfProperties` is defined in `/types/defines/cf.d.ts`. We only really need a single `Cf` // type parameter here, but it would be a breaking type change to remove `CfHostMetadata`. } else { JSG_READONLY_INSTANCE_PROPERTY(method, getMethod); JSG_READONLY_INSTANCE_PROPERTY(url, getUrl); JSG_READONLY_INSTANCE_PROPERTY(headers, getHeaders); JSG_READONLY_INSTANCE_PROPERTY(redirect, getRedirect); JSG_READONLY_INSTANCE_PROPERTY(fetcher, getFetcher); JSG_READONLY_INSTANCE_PROPERTY(signal, getThisSignal); JSG_READONLY_INSTANCE_PROPERTY(cf, getCf); // TODO(conform): These are standard properties that we do not implement (see descriptions // above). // JSG_READONLY_INSTANCE_PROPERTY(duplex, getDuplex); JSG_READONLY_INSTANCE_PROPERTY(integrity, getIntegrity); JSG_READONLY_INSTANCE_PROPERTY(keepalive, getKeepalive); JSG_TS_OVERRIDE(> { constructor(input: RequestInfo | URL, init?: RequestInit); clone(): Request; readonly cf?: Cf; }); } } void serialize(jsg::Lock& js, jsg::Serializer& serializer, const jsg::TypeHandler& initDictHandler); static jsg::Ref deserialize(jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer, const jsg::TypeHandler& initDictHandler); JSG_SERIALIZABLE(rpc::SerializationTag::REQUEST); void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("url", url); tracker.trackField("headers", headers); tracker.trackField("fetcher", fetcher); tracker.trackField("signal", signal); tracker.trackField("thisSignal", thisSignal); tracker.trackField("cf", cf); } private: kj::HttpMethod method; kj::String url; Redirect redirect; jsg::Ref headers; kj::Maybe> fetcher; kj::Maybe> signal; CacheMode cacheMode = CacheMode::NONE; // The fetch spec definition of Request has a distinction between the "signal" (which is // an optional AbortSignal passed in with the options), and "this' signal", which is an // AbortSignal that is always available via the request.signal accessor. When signal is // used explicitly, thisSignal will not be. kj::Maybe> thisSignal; CfProperty cf; // Controls how to handle Content-Encoding headers in the response Response_BodyEncoding responseBodyEncoding = Response_BodyEncoding::AUTO; void visitForGc(jsg::GcVisitor& visitor) { visitor.visit(headers, fetcher, signal, thisSignal, cf); } }; class Response final: public Body { public: // Alias to the global Response_BodyEncoding enum for backward compatibility using BodyEncoding = Response_BodyEncoding; Response(jsg::Lock& js, int statusCode, kj::Maybe statusText, jsg::Ref headers, CfProperty&& cf, kj::Maybe body, kj::Array urlList = {}, kj::Maybe> webSocket = kj::none, BodyEncoding bodyEncoding = BodyEncoding::AUTO); // --------------------------------------------------------------------------- // JS API struct InitializerDict { jsg::Optional status; jsg::Optional statusText; jsg::Optional headers; // Cloudflare-specific feature flags. jsg::Optional> cf; jsg::Optional>> webSocket; jsg::Optional encodeBody; JSG_STRUCT(status, statusText, headers, cf, webSocket, encodeBody); JSG_STRUCT_TS_OVERRIDE(ResponseInit { headers?: HeadersInit; encodeBody?: "automatic" | "manual"; }); }; using Initializer = kj::OneOf>; // Response's constructor has two arguments: an optional, nullable body that defaults to null, and // an optional initializer property bag. Tragically, the only way to express the "optional, // nullable body that defaults to null" is with an Optional>. The reason // for this is because: // // - We need to be able to call `new Response()`, meaning the body initializer MUST be Optional. // - We need to be able to call `new Response(null)`, but `null` cannot implicitly convert to // an Optional, so we need an inner Maybe to inhibit string coercion to Body::Initializer. static jsg::Ref constructor(jsg::Lock& js, jsg::Optional> bodyInit, jsg::Optional maybeInit); // Constructs a redirection response. `status` must be a redirect status if given, otherwise it // defaults to 302 (technically a non-conformity, but both Chrome and Firefox use this default). // // It's worth noting a couple property quirks of Responses constructed using this method: // 1. `url` will be the empty string, because the response didn't actually come from any // particular URL. // 2. `redirected` will equal false, for the same reason as (1). // 3. `body` will be empty -- we don't even provide a default courtesy body. If you need one, // you'll need to use the regular constructor, which is more flexible. // // These behaviors surprised me, but they match both the spec and Chrome/Firefox behavior. static jsg::Ref redirect(jsg::Lock& js, kj::String url, jsg::Optional status); // Constructs a `network error` response. // // A network error is a response whose status is always 0, status message is always the empty // byte sequence, header list is always empty, body is always null, and trailer is always empty. static jsg::Ref error(jsg::Lock& js); jsg::Ref clone(jsg::Lock& js); static jsg::Ref json_( jsg::Lock& js, jsg::JsValue any, jsg::Optional maybeInit); struct SendOptions { bool allowWebSocket = false; }; // Helper not exposed to JavaScript. kj::Promise> send(jsg::Lock& js, kj::HttpService::Response& outer, SendOptions options, kj::Maybe maybeReqHeaders); int getStatus(); kj::StringPtr getStatusText(); jsg::Ref getHeaders(jsg::Lock& js); bool getOk(); bool getRedirected(); kj::StringPtr getUrl(); kj::Maybe> getWebSocket(jsg::Lock& js); // Returns the `cf` field containing Cloudflare feature flags. jsg::Optional getCf(jsg::Lock& js); // This relates to CORS, which doesn't apply on the edge -- see Request::Initializer::mode. // In discussing with other runtime implementations that do not implement CORS, it was // determined that only the `'default'` and `'error'` properties should be implemented. kj::StringPtr getType() { if (statusCode == 0) return "error"_kj; return "default"_kj; } JSG_RESOURCE_TYPE(Response, CompatibilityFlags::Reader flags) { JSG_INHERIT(Body); JSG_STATIC_METHOD(error); JSG_STATIC_METHOD(redirect); JSG_STATIC_METHOD_NAMED(json, json_); JSG_METHOD(clone); if (flags.getJsgPropertyOnPrototypeTemplate()) { JSG_READONLY_PROTOTYPE_PROPERTY(status, getStatus); JSG_READONLY_PROTOTYPE_PROPERTY(statusText, getStatusText); JSG_READONLY_PROTOTYPE_PROPERTY(headers, getHeaders); JSG_READONLY_PROTOTYPE_PROPERTY(ok, getOk); JSG_READONLY_PROTOTYPE_PROPERTY(redirected, getRedirected); JSG_READONLY_PROTOTYPE_PROPERTY(url, getUrl); JSG_READONLY_PROTOTYPE_PROPERTY(webSocket, getWebSocket); JSG_READONLY_PROTOTYPE_PROPERTY(cf, getCf); JSG_READONLY_PROTOTYPE_PROPERTY(type, getType); } else { JSG_READONLY_INSTANCE_PROPERTY(status, getStatus); JSG_READONLY_INSTANCE_PROPERTY(statusText, getStatusText); JSG_READONLY_INSTANCE_PROPERTY(headers, getHeaders); JSG_READONLY_INSTANCE_PROPERTY(ok, getOk); JSG_READONLY_INSTANCE_PROPERTY(redirected, getRedirected); JSG_READONLY_INSTANCE_PROPERTY(url, getUrl); JSG_READONLY_INSTANCE_PROPERTY(webSocket, getWebSocket); JSG_READONLY_INSTANCE_PROPERTY(cf, getCf); JSG_READONLY_INSTANCE_PROPERTY(type, getType); } JSG_TS_OVERRIDE({ constructor(body?: BodyInit | null, init?: ResponseInit); type: 'default' | 'error'; }); // Use `BodyInit` and `ResponseInit` type aliases in constructor instead of inlining } void serialize(jsg::Lock& js, jsg::Serializer& serializer, const jsg::TypeHandler& initDictHandler, const jsg::TypeHandler>>& streamHandler); static jsg::Ref deserialize(jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer, const jsg::TypeHandler& initDictHandler, const jsg::TypeHandler>>& streamHandler); JSG_SERIALIZABLE(rpc::SerializationTag::RESPONSE); void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("statusText", statusText); tracker.trackField("headers", headers); tracker.trackField("webSocket", webSocket); tracker.trackField("cf", cf); for (const auto& url: urlList) { tracker.trackField("urlList", url); } tracker.trackField("asyncContext", asyncContext); } private: int statusCode; // If the statusText is empty, we will derive it from the statusCode. If there's no // match, it will be empty. kj::Maybe statusText; jsg::Ref headers; CfProperty cf; // The URL list, per the Fetch spec. Only Responses actually created by fetch() have a non-empty // URL list; for responses created from JavaScript this is empty. The list is filled in with the // sequence of URLs that fetch() requested. In redirect manual mode, this will be one element, // and just be a copy of the corresponding request's URL; in redirect follow mode the length of // the list will be one plus the number of redirects followed. // // The last URL is typically the only one that the user will care about, and is the one exposed // by getUrl(). kj::Array urlList; // If this response represents a successful WebSocket handshake, this is the socket, and the body // is empty. kj::Maybe> webSocket; // If this response is already encoded and the user don't want to encode the // body twice, they can specify encodeBody: "manual". Response::BodyEncoding bodyEncoding; // Capturing the AsyncContextFrame when the Response is created is necessary because there's // a natural separation that occurs between the moment the Response is created and when we // actually start consuming it. If a JS-backed ReadableStream is used, we end up losing the // appropriate async context in the promise read loop since that is kicked off later. kj::Maybe> asyncContext; void visitForGc(jsg::GcVisitor& visitor) { visitor.visit(headers, webSocket, cf, asyncContext); } }; class FetchEvent final: public ExtendableEvent { public: FetchEvent(jsg::Ref request) : ExtendableEvent("fetch"), request(kj::mv(request)), state(AwaitingRespondWith()) {} kj::Maybe>> getResponsePromise(jsg::Lock& js); // TODO(soon): constructor static jsg::Ref constructor(kj::String type) = delete; jsg::Ref getRequest(); void respondWith(jsg::Lock& js, jsg::Promise> promise); void passThroughOnException(); // TODO(someday): Do any other FetchEvent members make sense on the edge? JSG_RESOURCE_TYPE(FetchEvent) { JSG_INHERIT(ExtendableEvent); JSG_READONLY_INSTANCE_PROPERTY(request, getRequest); JSG_METHOD(respondWith); JSG_METHOD(passThroughOnException); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("request", request); KJ_IF_SOME(respondWithCalled, state.tryGet()) { tracker.trackField("promise", respondWithCalled.promise); } } private: jsg::Ref request; struct AwaitingRespondWith {}; struct RespondWithCalled { jsg::Promise> promise; }; struct ResponseSent {}; kj::OneOf state; void visitForGc(jsg::GcVisitor& visitor) { visitor.visit(request); KJ_IF_SOME(respondWithCalled, state.tryGet()) { visitor.visit(respondWithCalled.promise); } } }; jsg::Promise> fetchImpl(jsg::Lock& js, kj::Maybe> fetcher, // if null, use fetcher from request object Request::Info requestOrUrl, jsg::Optional requestInit); jsg::Ref makeHttpResponse(jsg::Lock& js, kj::HttpMethod method, kj::Vector urlList, uint statusCode, kj::StringPtr statusText, const kj::HttpHeaders& headers, kj::Own body, kj::Maybe> webSocket, Response::BodyEncoding bodyEncoding = Response::BodyEncoding::AUTO, kj::Maybe> signal = kj::none); #define EW_HTTP_ISOLATE_TYPES \ api::FetchEvent, api::Headers, api::Headers::EntryIterator, api::Headers::EntryIterator::Next, \ api::Headers::KeyIterator, api::Headers::KeyIterator::Next, api::Headers::ValueIterator, \ api::Headers::ValueIterator::Next, api::Body, api::Response, api::Response::InitializerDict, \ api::Request, api::Request::InitializerDict, api::Fetcher, api::Fetcher::PutOptions, \ api::Fetcher::ScheduledOptions, api::Fetcher::ScheduledResult, api::Fetcher::QueueResult, \ api::Fetcher::ServiceBindingQueueMessage // The list of http.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE } // namespace workerd::api