UNPKG

@itwin/core-backend

Version:
139 lines • 7.21 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module RpcInterface */ Object.defineProperty(exports, "__esModule", { value: true }); exports.sendResponse = sendResponse; const core_bentley_1 = require("@itwin/core-bentley"); const core_common_1 = require("@itwin/core-common"); const node_stream_1 = require("node:stream"); const node_util_1 = require("node:util"); const node_zlib_1 = require("node:zlib"); /* eslint-disable @typescript-eslint/no-deprecated */ function configureResponse(protocol, request, fulfillment, res) { const success = protocol.getStatus(fulfillment.status) === core_common_1.RpcRequestStatus.Resolved; // TODO: Use stale-while-revalidate in cache headers. This needs to be tested, and does not currently have support in the router/caching-service. // This will allow browsers to use stale cached responses while also revalidating with the router, allowing us to start up a backend if necessary. // RPC Caching Service uses the s-maxage header to determine the TTL for the redis cache. const oneHourInSeconds = 3600; if (success && request.caching === core_common_1.RpcResponseCacheControl.Immutable) { // If response size is > 50 MB, do not cache it. if (fulfillment.result.objects.length > (50 * 10 ** 7)) { res.set("Cache-Control", "no-store"); } else if (request.operation.operationName === "generateTileContent") { res.set("Cache-Control", "no-store"); } else if (request.operation.operationName === "getConnectionProps") { // GetConnectionprops can't be cached on the browser longer than the lifespan of the backend. The lifespan of backend may shrink too. Keep it at 1 second to be safe. res.set("Cache-Control", `s-maxage=${oneHourInSeconds * 24}, max-age=1, immutable`); } else if (request.operation.operationName === "getTileCacheContainerUrl") { // getTileCacheContainerUrl returns a SAS with an expiry of 23:59:59. We can't exceed that time when setting the max-age. res.set("Cache-Control", `s-maxage=${oneHourInSeconds * 23}, max-age=${oneHourInSeconds * 23}, immutable`); } else { res.set("Cache-Control", `s-maxage=${oneHourInSeconds * 24}, max-age=${oneHourInSeconds * 48}, immutable`); } } if (fulfillment.retry) { res.set("Retry-After", fulfillment.retry); } } function configureText(fulfillment, res) { res.set(core_common_1.WEB_RPC_CONSTANTS.CONTENT, core_common_1.WEB_RPC_CONSTANTS.TEXT); return (fulfillment.status === 204) ? "" : fulfillment.result.objects; } function configureBinary(fulfillment, res) { res.set(core_common_1.WEB_RPC_CONSTANTS.CONTENT, core_common_1.WEB_RPC_CONSTANTS.BINARY); const data = fulfillment.result.data[0]; return Buffer.isBuffer(data) ? data : Buffer.from(data); } function configureMultipart(fulfillment, res) { const response = core_common_1.RpcMultipart.createStream(fulfillment.result); const headers = response.getHeaders(); for (const header in headers) { if (headers.hasOwnProperty(header)) { res.set(header, headers[header]); } } return response; } function configureStream(fulfillment) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return fulfillment.result.stream; } async function configureEncoding(req, res, responseBody) { const acceptedEncodings = req.header("Accept-Encoding")?.split(",").map((value) => value.trim()); if (!acceptedEncodings) return responseBody; const encoding = acceptedEncodings.includes("br") ? "br" : acceptedEncodings.includes("gzip") ? "gzip" : undefined; if (!encoding) return responseBody; res.set("Content-Encoding", encoding); const brotliOptions = { params: { // Experimentation revealed that the default compression quality significantly increases the compression time for larger texts. // Reducing the quality improves speed substantially without a significant loss in the compression ratio. [node_zlib_1.constants.BROTLI_PARAM_QUALITY]: 3, }, }; if (responseBody instanceof node_stream_1.Stream) { const compressStream = encoding === "br" ? (0, node_zlib_1.createBrotliCompress)(brotliOptions) : (0, node_zlib_1.createGzip)(); return responseBody.pipe(compressStream); } return encoding === "br" ? (0, node_util_1.promisify)(node_zlib_1.brotliCompress)(responseBody, brotliOptions) : (0, node_util_1.promisify)(node_zlib_1.gzip)(responseBody); } /** @internal */ async function sendResponse(protocol, request, fulfillment, req, res) { logResponse(request, fulfillment.status, fulfillment.rawResult); const versionHeader = protocol.protocolVersionHeaderName; if (versionHeader && core_common_1.RpcProtocol.protocolVersion) { res.set(versionHeader, core_common_1.RpcProtocol.protocolVersion.toString()); } const transportType = core_common_1.WebAppRpcRequest.computeTransportType(fulfillment.result, fulfillment.rawResult); let responseBody; if (transportType === core_common_1.RpcContentType.Binary) { responseBody = configureBinary(fulfillment, res); } else if (transportType === core_common_1.RpcContentType.Multipart) { responseBody = configureMultipart(fulfillment, res); } else if (transportType === core_common_1.RpcContentType.Stream) { responseBody = configureStream(fulfillment); } else { responseBody = configureText(fulfillment, res); } configureResponse(protocol, request, fulfillment, res); res.status(fulfillment.status); if (fulfillment.allowCompression) responseBody = await configureEncoding(req, res, responseBody); // This check should in theory look for instances of Readable, but that would break backend implementation at // core/backend/src/RpcBackend.ts if (responseBody instanceof node_stream_1.Stream) { responseBody.pipe(res); } else { res.send(responseBody); } } function logResponse(request, statusCode, resultObj) { const metadata = { ActivityId: request.id, // eslint-disable-line @typescript-eslint/naming-convention method: request.method, path: request.path, operation: request.operation, statusCode, errorObj: resultObj instanceof Error ? core_bentley_1.BentleyError.getErrorProps(resultObj) : undefined, }; if (statusCode < 400) core_bentley_1.Logger.logInfo(core_common_1.CommonLoggerCategory.RpcInterfaceBackend, "RPC over HTTP success response", metadata); else core_bentley_1.Logger.logError(core_common_1.CommonLoggerCategory.RpcInterfaceBackend, "RPC over HTTP failure response", metadata); } //# sourceMappingURL=response.js.map