diff options
Diffstat (limited to 'extensions/http/01_http.js')
-rw-r--r-- | extensions/http/01_http.js | 383 |
1 files changed, 0 insertions, 383 deletions
diff --git a/extensions/http/01_http.js b/extensions/http/01_http.js deleted file mode 100644 index 3f8bcb3a8..000000000 --- a/extensions/http/01_http.js +++ /dev/null @@ -1,383 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const webidl = window.__bootstrap.webidl; - const { InnerBody } = window.__bootstrap.fetchBody; - const { setEventTargetData } = window.__bootstrap.eventTarget; - const { - Response, - fromInnerRequest, - toInnerResponse, - newInnerRequest, - newInnerResponse, - fromInnerResponse, - } = window.__bootstrap.fetch; - const core = window.Deno.core; - const { BadResource, Interrupted } = core; - const { ReadableStream } = window.__bootstrap.streams; - const abortSignal = window.__bootstrap.abortSignal; - const { WebSocket, _rid, _readyState, _eventLoop, _protocol, _server } = - window.__bootstrap.webSocket; - const { - ArrayPrototypeIncludes, - ArrayPrototypePush, - ArrayPrototypeSome, - Promise, - StringPrototypeIncludes, - StringPrototypeToLowerCase, - StringPrototypeSplit, - Symbol, - SymbolAsyncIterator, - TypedArrayPrototypeSubarray, - TypeError, - Uint8Array, - } = window.__bootstrap.primordials; - - const connErrorSymbol = Symbol("connError"); - - class HttpConn { - #rid = 0; - - constructor(rid) { - this.#rid = rid; - } - - /** @returns {number} */ - get rid() { - return this.#rid; - } - - /** @returns {Promise<ResponseEvent | null>} */ - async nextRequest() { - let nextRequest; - try { - nextRequest = await core.opAsync( - "op_http_request_next", - this.#rid, - ); - } catch (error) { - // A connection error seen here would cause disrupted responses to throw - // a generic `BadResource` error. Instead store this error and replace - // those with it. - this[connErrorSymbol] = error; - if (error instanceof BadResource) { - return null; - } else if (error instanceof Interrupted) { - return null; - } else if ( - StringPrototypeIncludes(error.message, "connection closed") - ) { - return null; - } - throw error; - } - if (nextRequest === null) return null; - - const [ - requestRid, - responseSenderRid, - method, - headersList, - url, - ] = nextRequest; - - /** @type {ReadableStream<Uint8Array> | undefined} */ - let body = null; - if (typeof requestRid === "number") { - body = createRequestBodyStream(requestRid); - } - - const innerRequest = newInnerRequest( - method, - url, - headersList, - body !== null ? new InnerBody(body) : null, - ); - const signal = abortSignal.newSignal(); - const request = fromInnerRequest(innerRequest, signal, "immutable"); - - const respondWith = createRespondWith( - this, - responseSenderRid, - requestRid, - ); - - return { request, respondWith }; - } - - /** @returns {void} */ - close() { - core.close(this.#rid); - } - - [SymbolAsyncIterator]() { - // deno-lint-ignore no-this-alias - const httpConn = this; - return { - async next() { - const reqEvt = await httpConn.nextRequest(); - // Change with caution, current form avoids a v8 deopt - return { value: reqEvt, done: reqEvt === null }; - }, - }; - } - } - - function readRequest(requestRid, zeroCopyBuf) { - return core.opAsync( - "op_http_request_read", - requestRid, - zeroCopyBuf, - ); - } - - function createRespondWith(httpConn, responseSenderRid, requestRid) { - return async function respondWith(resp) { - if (resp instanceof Promise) { - resp = await resp; - } - - if (!(resp instanceof Response)) { - throw new TypeError( - "First argument to respondWith must be a Response or a promise resolving to a Response.", - ); - } - - const innerResp = toInnerResponse(resp); - - // If response body length is known, it will be sent synchronously in a - // single op, in other case a "response body" resource will be created and - // we'll be streaming it. - /** @type {ReadableStream<Uint8Array> | Uint8Array | null} */ - let respBody = null; - if (innerResp.body !== null) { - if (innerResp.body.unusable()) throw new TypeError("Body is unusable."); - if (innerResp.body.streamOrStatic instanceof ReadableStream) { - if ( - innerResp.body.length === null || - innerResp.body.source instanceof Blob - ) { - respBody = innerResp.body.stream; - } else { - const reader = innerResp.body.stream.getReader(); - const r1 = await reader.read(); - if (r1.done) { - respBody = new Uint8Array(0); - } else { - respBody = r1.value; - const r2 = await reader.read(); - if (!r2.done) throw new TypeError("Unreachable"); - } - } - } else { - innerResp.body.streamOrStatic.consumed = true; - respBody = innerResp.body.streamOrStatic.body; - } - } else { - respBody = new Uint8Array(0); - } - - let responseBodyRid; - try { - responseBodyRid = await core.opAsync("op_http_response", [ - responseSenderRid, - innerResp.status ?? 200, - innerResp.headerList, - ], respBody instanceof Uint8Array ? respBody : null); - } catch (error) { - const connError = httpConn[connErrorSymbol]; - if (error instanceof BadResource && connError != null) { - // deno-lint-ignore no-ex-assign - error = new connError.constructor(connError.message); - } - if (respBody !== null && respBody instanceof ReadableStream) { - await respBody.cancel(error); - } - throw error; - } - - // If `respond` returns a responseBodyRid, we should stream the body - // to that resource. - if (responseBodyRid !== null) { - try { - if (respBody === null || !(respBody instanceof ReadableStream)) { - throw new TypeError("Unreachable"); - } - const reader = respBody.getReader(); - while (true) { - const { value, done } = await reader.read(); - if (done) break; - if (!(value instanceof Uint8Array)) { - await reader.cancel(new TypeError("Value not a Uint8Array")); - break; - } - try { - await core.opAsync( - "op_http_response_write", - responseBodyRid, - value, - ); - } catch (error) { - const connError = httpConn[connErrorSymbol]; - if (error instanceof BadResource && connError != null) { - // deno-lint-ignore no-ex-assign - error = new connError.constructor(connError.message); - } - await reader.cancel(error); - throw error; - } - } - } finally { - // Once all chunks are sent, and the request body is closed, we can - // close the response body. - try { - await core.opAsync("op_http_response_close", responseBodyRid); - } catch { /* pass */ } - } - } - - const ws = resp[_ws]; - if (ws) { - if (typeof requestRid !== "number") { - throw new TypeError( - "This request can not be upgraded to a websocket connection.", - ); - } - - const wsRid = await core.opAsync( - "op_http_upgrade_websocket", - requestRid, - ); - ws[_rid] = wsRid; - ws[_protocol] = resp.headers.get("sec-websocket-protocol"); - - if (ws[_readyState] === WebSocket.CLOSING) { - await core.opAsync("op_ws_close", { rid: wsRid }); - - ws[_readyState] = WebSocket.CLOSED; - - const errEvent = new ErrorEvent("error"); - ws.dispatchEvent(errEvent); - - const event = new CloseEvent("close"); - ws.dispatchEvent(event); - - try { - core.close(wsRid); - } catch (err) { - // Ignore error if the socket has already been closed. - if (!(err instanceof Deno.errors.BadResource)) throw err; - } - } else { - ws[_readyState] = WebSocket.OPEN; - const event = new Event("open"); - ws.dispatchEvent(event); - - ws[_eventLoop](); - } - } - }; - } - - function createRequestBodyStream(requestRid) { - return new ReadableStream({ - type: "bytes", - async pull(controller) { - try { - // This is the largest possible size for a single packet on a TLS - // stream. - const chunk = new Uint8Array(16 * 1024 + 256); - const read = await readRequest( - requestRid, - chunk, - ); - if (read > 0) { - // We read some data. Enqueue it onto the stream. - controller.enqueue(TypedArrayPrototypeSubarray(chunk, 0, read)); - } else { - // We have reached the end of the body, so we close the stream. - controller.close(); - core.close(requestRid); - } - } catch (err) { - // There was an error while reading a chunk of the body, so we - // error. - controller.error(err); - controller.close(); - core.close(requestRid); - } - }, - cancel() { - core.close(requestRid); - }, - }); - } - - const _ws = Symbol("[[associated_ws]]"); - - function upgradeWebSocket(request, options = {}) { - if (request.headers.get("upgrade") !== "websocket") { - throw new TypeError( - "Invalid Header: 'upgrade' header must be 'websocket'", - ); - } - - const connection = request.headers.get("connection"); - const connectionHasUpgradeOption = connection !== null && - ArrayPrototypeSome( - StringPrototypeSplit(connection, /\s*,\s*/), - (option) => StringPrototypeToLowerCase(option) === "upgrade", - ); - if (!connectionHasUpgradeOption) { - throw new TypeError( - "Invalid Header: 'connection' header must be 'Upgrade'", - ); - } - - const websocketKey = request.headers.get("sec-websocket-key"); - if (websocketKey === null) { - throw new TypeError( - "Invalid Header: 'sec-websocket-key' header must be set", - ); - } - - const accept = core.opSync("op_http_websocket_accept_header", websocketKey); - - const r = newInnerResponse(101); - r.headerList = [ - ["upgrade", "websocket"], - ["connection", "Upgrade"], - ["sec-websocket-accept", accept], - ]; - - const protocolsStr = request.headers.get("sec-websocket-protocol") || ""; - const protocols = StringPrototypeSplit(protocolsStr, ", "); - if (protocols && options.protocol) { - if (ArrayPrototypeIncludes(protocols, options.protocol)) { - ArrayPrototypePush(r.headerList, [ - "sec-websocket-protocol", - options.protocol, - ]); - } else { - throw new TypeError( - `Protocol '${options.protocol}' not in the request's protocol list (non negotiable)`, - ); - } - } - - const response = fromInnerResponse(r, "immutable"); - - const socket = webidl.createBranded(WebSocket); - setEventTargetData(socket); - socket[_server] = true; - response[_ws] = socket; - - return { response, socket }; - } - - window.__bootstrap.http = { - HttpConn, - upgradeWebSocket, - }; -})(this); |