UNPKG

@typespec/ts-http-runtime

Version:

Isomorphic client library for making HTTP requests in node.js and browser.

197 lines 7.01 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { isRestError, RestError } from "../restError.js"; import { createHttpHeaders } from "../httpHeaders.js"; import { createPipelineRequest } from "../pipelineRequest.js"; import { getCachedDefaultHttpsClient } from "./clientHelpers.js"; import { isBlob, isReadableStream } from "../util/typeGuards.js"; import { buildMultipartBody } from "./multipart.js"; /** * Helper function to send request used by the client * @param method - method to use to send the request * @param url - url to send the request to * @param pipeline - pipeline with the policies to run when sending the request * @param options - request options * @param customHttpClient - a custom HttpClient to use when making the request * @returns returns and HttpResponse */ export async function sendRequest(method, url, pipeline, options = {}, customHttpClient) { const httpClient = customHttpClient ?? getCachedDefaultHttpsClient(); const request = buildPipelineRequest(method, url, options); try { const response = await pipeline.sendRequest(httpClient, request); const headers = response.headers.toJSON(); const stream = response.readableStreamBody ?? response.browserStreamBody; const parsedBody = options.responseAsStream || stream !== undefined ? undefined : getResponseBody(response); const body = stream ?? parsedBody; if (options?.onResponse) { options.onResponse({ ...response, request, rawHeaders: headers, parsedBody }); } return { request, headers, status: `${response.status}`, body, }; } catch (e) { if (isRestError(e) && e.response && options.onResponse) { const { response } = e; const rawHeaders = response.headers.toJSON(); // UNBRANDED DIFFERENCE: onResponse callback does not have a second __legacyError property options?.onResponse({ ...response, request, rawHeaders }, e); } throw e; } } /** * Function to determine the request content type * @param options - request options InternalRequestParameters * @returns returns the content-type */ function getRequestContentType(options = {}) { if (options.contentType) { return options.contentType; } const headerContentType = options.headers?.["content-type"]; if (typeof headerContentType === "string") { return headerContentType; } return getContentType(options.body); } /** * Function to determine the content-type of a body * this is used if an explicit content-type is not provided * @param body - body in the request * @returns returns the content-type */ function getContentType(body) { if (body === undefined) { return undefined; } if (ArrayBuffer.isView(body)) { return "application/octet-stream"; } if (isBlob(body) && body.type) { return body.type; } if (typeof body === "string") { try { JSON.parse(body); return "application/json"; } catch (error) { // If we fail to parse the body, it is not json return undefined; } } // By default return json return "application/json"; } function buildPipelineRequest(method, url, options = {}) { const requestContentType = getRequestContentType(options); const { body, multipartBody } = getRequestBody(options.body, requestContentType); const headers = createHttpHeaders({ ...(options.headers ? options.headers : {}), accept: options.accept ?? options.headers?.accept ?? "application/json", ...(requestContentType && { "content-type": requestContentType, }), }); const { allowInsecureConnection, abortSignal, onUploadProgress, onDownloadProgress, timeout, responseAsStream, url: _url, method: _method, body: _body, multipartBody: _multiBody, headers: _headers, ...rest } = options; const request = createPipelineRequest({ url, method, body, multipartBody, headers, allowInsecureConnection, abortSignal, onUploadProgress, onDownloadProgress, timeout, enableBrowserStreams: true, streamResponseStatusCodes: responseAsStream ? new Set([Number.POSITIVE_INFINITY]) : undefined, }); Object.assign(request, rest); return request; } /** * Prepares the body before sending the request */ export function getRequestBody(body, contentType = "") { if (body === undefined) { return { body: undefined }; } if (typeof FormData !== "undefined" && body instanceof FormData) { return { body }; } if (isBlob(body)) { return { body }; } if (isReadableStream(body)) { return { body }; } if (typeof body === "function") { return { body: body }; } if (ArrayBuffer.isView(body)) { return { body: body instanceof Uint8Array ? body : JSON.stringify(body), }; } const firstType = contentType.split(";")[0]; switch (firstType) { case "application/json": return { body: JSON.stringify(body) }; case "multipart/form-data": if (Array.isArray(body)) { return { multipartBody: buildMultipartBody(body) }; } return { body: JSON.stringify(body) }; case "text/plain": return { body: String(body) }; default: if (typeof body === "string") { return { body }; } return { body: JSON.stringify(body) }; } } /** * Prepares the response body */ function getResponseBody(response) { // Set the default response type const contentType = response.headers.get("content-type") ?? ""; const firstType = contentType.split(";")[0]; const bodyToParse = response.bodyAsText ?? ""; if (firstType === "text/plain") { return String(bodyToParse); } // Default to "application/json" and fallback to string; try { return bodyToParse ? JSON.parse(bodyToParse) : undefined; } catch (error) { // If we were supposed to get a JSON object and failed to // parse, throw a parse error if (firstType === "application/json") { throw createParseError(response, error); } // We are not sure how to handle the response so we return it as // plain text. return String(bodyToParse); } } function createParseError(response, err) { const msg = `Error "${err}" occurred while parsing the response body - ${response.bodyAsText}.`; const errCode = err.code ?? RestError.PARSE_ERROR; return new RestError(msg, { code: errCode, statusCode: response.status, request: response.request, response: response, }); } //# sourceMappingURL=sendRequest.js.map