UNPKG

@connectrpc/connect-node

Version:

Connect is a family of libraries for building and consuming APIs on different languages and platforms, and [@connectrpc/connect](https://www.npmjs.com/package/@connectrpc/connect) brings type-safe APIs with Protobuf to TypeScript.

226 lines (225 loc) 11.7 kB
"use strict"; // Copyright 2021-2025 The Connect Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. var __asyncValues = (this && this.__asyncValues) || function (o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } }; var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i; function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; } function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.universalRequestFromNodeRequest = universalRequestFromNodeRequest; exports.universalResponseToNodeResponse = universalResponseToNodeResponse; const connect_1 = require("@connectrpc/connect"); const node_universal_header_js_1 = require("./node-universal-header.js"); const node_error_js_1 = require("./node-error.js"); function universalRequestFromNodeRequest(nodeRequest, ...rest) { var _a, _b; const nodeResponse = rest.length === 3 ? rest[0] : undefined; const parsedJsonBody = rest.length === 3 ? rest[1] : rest[0]; const contextValues = rest.length === 3 ? rest[2] : rest[1]; const encrypted = "encrypted" in nodeRequest.socket && nodeRequest.socket.encrypted; const protocol = encrypted ? "https" : "http"; const authority = "authority" in nodeRequest ? nodeRequest.authority : nodeRequest.headers.host; const pathname = (_a = nodeRequest.url) !== null && _a !== void 0 ? _a : ""; if (authority === undefined) { throw new connect_1.ConnectError("unable to determine request authority from Node.js server request", connect_1.Code.Internal); } const body = parsedJsonBody !== undefined ? parsedJsonBody : asyncIterableFromNodeServerRequest(nodeRequest); const abortController = new AbortController(); if ("stream" in nodeRequest) { // HTTP/2 has error codes we want to honor nodeRequest.once("close", () => { const err = (0, node_error_js_1.connectErrorFromH2ResetCode)(nodeRequest.stream.rstCode); if (err !== undefined) { abortController.abort(err); } else { abortController.abort(); } }); } else { // HTTP/1.1 does not have error codes, but Node.js has ECONNRESET const nodeResponsOrRequest = nodeResponse !== null && nodeResponse !== void 0 ? nodeResponse : nodeRequest; const onH1Error = (e) => { nodeRequest.off("error", onH1Error); nodeResponsOrRequest.off("close", onH1Close); abortController.abort((0, node_error_js_1.connectErrorFromNodeReason)(e)); }; const onH1Close = () => { nodeRequest.off("error", onH1Error); nodeResponsOrRequest.off("close", onH1Close); // When subscribed to the response, this can get called before "error" abortController.abort(nodeRequest.errored ? (0, node_error_js_1.connectErrorFromNodeReason)(nodeRequest.errored) : undefined); }; nodeRequest.once("error", onH1Error); // Node emits close on the request as soon as all data is read. // We instead subscribe to the response (if available) // // Ref: https://github.com/nodejs/node/issues/40775 nodeResponsOrRequest.once("close", onH1Close); } return { httpVersion: nodeRequest.httpVersion, method: (_b = nodeRequest.method) !== null && _b !== void 0 ? _b : "", url: new URL(pathname, `${protocol}://${authority}`).toString(), header: (0, node_universal_header_js_1.nodeHeaderToWebHeader)(nodeRequest.headers), body, signal: abortController.signal, contextValues: contextValues, }; } /** * Writes a UniversalServerResponse to a Node.js server response. * This function helps to implement adapters to server frameworks running * on Node.js. Please be careful using this function in your own code, as we * may have to make changes to it in the future. */ async function universalResponseToNodeResponse(universalResponse, nodeResponse) { var _a, _b, _c; const it = (_a = universalResponse.body) === null || _a === void 0 ? void 0 : _a[Symbol.asyncIterator](); let isWriteError = false; try { if (it !== undefined) { let chunk = await it.next(); isWriteError = true; // we deliberately send headers after first read, not before, // because we have to give the implementation a chance to // set response headers nodeResponse.writeHead(universalResponse.status, (0, node_universal_header_js_1.webHeaderToNodeHeaders)(universalResponse.header)); isWriteError = false; for (; chunk.done !== true; chunk = await it.next()) { isWriteError = true; await write(nodeResponse, chunk.value); if ("flush" in nodeResponse && typeof nodeResponse.flush == "function") { // The npm package "compression" is an express middleware that is widely used, // for example in next.js. It uses the npm package "compressible" to determine // whether to apply compression to a response. Unfortunately, "compressible" // matches every mime type that ends with "+json", causing our server-streaming // RPCs to be buffered. // The package modifies the response object, and adds a flush() method, which // flushes the underlying gzip or deflate stream from the Node.js zlib module. // The method is added here: // https://github.com/expressjs/compression/blob/ad5113b98cafe1382a0ece30bb4673707ac59ce7/index.js#L70 nodeResponse.flush(); } isWriteError = false; } } if (!nodeResponse.headersSent) { nodeResponse.writeHead(universalResponse.status, (0, node_universal_header_js_1.webHeaderToNodeHeaders)(universalResponse.header)); } if (universalResponse.trailer) { nodeResponse.addTrailers((0, node_universal_header_js_1.webHeaderToNodeHeaders)(universalResponse.trailer)); } await new Promise((resolve) => { // The npm package "compression" crashes when a callback is passed to end() // https://github.com/expressjs/compression/blob/ad5113b98cafe1382a0ece30bb4673707ac59ce7/index.js#L115 nodeResponse.once("end", resolve); nodeResponse.end(); }); } catch (e) { // Report write errors to the handler. if (isWriteError) { (_b = it === null || it === void 0 ? void 0 : it.throw) === null || _b === void 0 ? void 0 : _b.call(it, e).catch(() => { }); } throw (0, node_error_js_1.connectErrorFromNodeReason)(e); } finally { (_c = it === null || it === void 0 ? void 0 : it.return) === null || _c === void 0 ? void 0 : _c.call(it).catch(() => { }); } } function asyncIterableFromNodeServerRequest(request) { return __asyncGenerator(this, arguments, function* asyncIterableFromNodeServerRequest_1() { var _a, e_1, _b, _c; try { for (var _d = true, request_1 = __asyncValues(request), request_1_1; request_1_1 = yield __await(request_1.next()), _a = request_1_1.done, !_a; _d = true) { _c = request_1_1.value; _d = false; const chunk = _c; yield yield __await(chunk); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_d && !_a && (_b = request_1.return)) yield __await(_b.call(request_1)); } finally { if (e_1) throw e_1.error; } } }); } function write(stream, data) { return new Promise((resolve, reject) => { if (stream.errored) { return error(stream.errored); } stream.once("error", error); stream.once("drain", drain); // flushed == false: the stream wishes for the calling code to wait for // the 'drain' event to be emitted before continuing to write additional // data. const flushed = stream.write(data, "binary", function (err) { if (err && !flushed) { // We are never getting a "drain" nor an "error" event if the stream // has already ended (ERR_STREAM_WRITE_AFTER_END), so we have to // resolve our promise in this callback. error(err); // However, once we do that (and remove our event listeners), we _do_ // get an "error" event, which ends up as an uncaught exception. // We silence this error specifically with the following listener. // All of this seems very fragile. stream.once("error", () => { // }); } }); if (flushed) { drain(); } function error(err) { stream.off("error", error); stream.off("drain", drain); reject(err); } function drain() { stream.off("error", error); stream.off("drain", drain); resolve(); } }); }