From 2c1716d3b68726b00de8786ace119fda3d3e7cba Mon Sep 17 00:00:00 2001 From: Shigma Date: Sat, 17 Feb 2024 00:12:01 +0800 Subject: [PATCH] fix: fix request encoding --- packages/core/package.json | 2 +- packages/core/src/index.ts | 64 ++++++++++++++++++++++--------- packages/file/package.json | 4 +- packages/file/src/index.ts | 6 +-- packages/proxy-agent/package.json | 4 +- 5 files changed, 54 insertions(+), 26 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 4b165cf..1a1245e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "undios", "description": "Fetch-based axios-style HTTP client", - "version": "0.1.5", + "version": "0.1.9", "type": "module", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b6f9d35..08447a9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -21,7 +21,7 @@ declare module 'cordis' { } } -const kHTTPError = Symbol.for('cordis.http.error') +const kHTTPError = Symbol.for('undios.error') class HTTPError extends Error { [kHTTPError] = true @@ -32,6 +32,18 @@ class HTTPError extends Error { } } +/** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch + */ +function encodeRequest(data: any): [string | null, any] { + if (data instanceof URLSearchParams) return [null, data] + if (data instanceof ArrayBuffer) return [null, data] + if (ArrayBuffer.isView(data)) return [null, data] + if (data instanceof Blob) return [null, data] + if (data instanceof FormData) return [null, data] + return ['application/json', JSON.stringify(data)] +} + export namespace HTTP { export type Method = | 'get' | 'GET' @@ -45,23 +57,21 @@ export namespace HTTP { | 'link' | 'LINK' | 'unlink' | 'UNLINK' - export type ResponseType = - | 'arraybuffer' - | 'json' - | 'text' - | 'stream' + export interface ResponseTypes { + text: string + stream: ReadableStream + blob: Blob + formdata: FormData + arraybuffer: ArrayBuffer + } export interface Request1 { - (url: string, config?: HTTP.RequestConfig & { responseType: 'arraybuffer' }): Promise - (url: string, config?: HTTP.RequestConfig & { responseType: 'stream' }): Promise> - (url: string, config?: HTTP.RequestConfig & { responseType: 'text' }): Promise + (url: string, config: HTTP.RequestConfig & { responseType: K }): Promise (url: string, config?: HTTP.RequestConfig): Promise } export interface Request2 { - (url: string, data?: any, config?: HTTP.RequestConfig & { responseType: 'arraybuffer' }): Promise - (url: string, data?: any, config?: HTTP.RequestConfig & { responseType: 'stream' }): Promise> - (url: string, data?: any, config?: HTTP.RequestConfig & { responseType: 'text' }): Promise + (url: string, data: any, config: HTTP.RequestConfig & { responseType: K }): Promise (url: string, data?: any, config?: HTTP.RequestConfig): Promise } @@ -78,7 +88,8 @@ export namespace HTTP { params?: Dict data?: any keepAlive?: boolean - responseType?: ResponseType + redirect?: RequestRedirect + responseType?: keyof ResponseTypes } export interface Response { @@ -93,6 +104,7 @@ export namespace HTTP { } export interface HTTP { + (url: string, config: HTTP.RequestConfig & { responseType: K }): Promise> (url: string | URL, config?: HTTP.RequestConfig): Promise> (method: HTTP.Method, url: string | URL, config?: HTTP.RequestConfig): Promise> config: HTTP.Config @@ -111,14 +123,14 @@ export class HTTP extends Service { static { for (const method of ['get', 'delete'] as const) { defineProperty(HTTP.prototype, method, async function (this: HTTP, url: string, config?: HTTP.Config) { - const response = await this(method, url, config) + const response = await this(url, { method, ...config }) return response.data }) } for (const method of ['patch', 'post', 'put'] as const) { defineProperty(HTTP.prototype, method, async function (this: HTTP, url: string, data?: any, config?: HTTP.Config) { - const response = await this(method, url, { data, ...config }) + const response = await this(url, { method, data, ...config }) return response.data }) } @@ -176,7 +188,7 @@ export class HTTP extends Service { } decodeResponse(response: Response) { - const type = response.headers.get('content-type') + const type = response.headers.get('Content-Type') if (type?.startsWith('application/json')) { return response.json() } else if (type?.startsWith('text/')) { @@ -194,6 +206,7 @@ export class HTTP extends Service { } const config = this.resolveConfig(args[1]) const url = this.resolveURL(args[0], config) + method ??= config.method ?? 'GET' const controller = new AbortController() let timer: NodeJS.Timeout | number | undefined @@ -208,13 +221,22 @@ export class HTTP extends Service { } try { + const headers = new Headers(config.headers) const init: RequestInit = { method, + headers, body: config.data, keepalive: config.keepAlive, - headers: config.headers, + redirect: config.redirect, signal: controller.signal, } + if (config.data && typeof config.data === 'object') { + const [type, body] = encodeRequest(config.data) + init.body = body + if (type && !headers.has('Content-Type')) { + headers.append('Content-Type', type) + } + } caller.emit('http/fetch-init', init, config) const raw = await fetch(url, init).catch((cause) => { const error = new HTTP.Error(`fetch ${url} failed`) @@ -241,6 +263,12 @@ export class HTTP extends Service { if (config.responseType === 'arraybuffer') { response.data = await raw.arrayBuffer() + } else if (config.responseType === 'text') { + response.data = await raw.text() + } else if (config.responseType === 'blob') { + response.data = await raw.blob() + } else if (config.responseType === 'formdata') { + response.data = await raw.formData() } else if (config.responseType === 'stream') { response.data = raw.body } else { @@ -253,7 +281,7 @@ export class HTTP extends Service { } async head(url: string, config?: HTTP.Config) { - const response = await this('HEAD', url, config) + const response = await this(url, { method: 'HEAD', ...config }) return response.headers } diff --git a/packages/file/package.json b/packages/file/package.json index d6b96a9..1773d63 100644 --- a/packages/file/package.json +++ b/packages/file/package.json @@ -1,7 +1,7 @@ { "name": "undios-file", "description": "File support for undios", - "version": "0.1.1", + "version": "0.1.2", "type": "module", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -56,7 +56,7 @@ }, "peerDependencies": { "cordis": "^3.10.4", - "undios": "^0.1.5" + "undios": "^0.1.9" }, "dependencies": { "cosmokit": "^1.5.2", diff --git a/packages/file/src/index.ts b/packages/file/src/index.ts index b07d1ed..9ec6a49 100644 --- a/packages/file/src/index.ts +++ b/packages/file/src/index.ts @@ -30,7 +30,7 @@ export const Config: z = z.object({}) export function apply(ctx: Context, config: Config) { ctx.provide('http.file') - ctx.provide('http.local') + ctx.provide('http.isLocal') function createName(mime: string | undefined) { let name = 'file' @@ -57,7 +57,7 @@ export function apply(ctx: Context, config: Config) { return { mime, filename: name, data } } - ctx['http.local'] = async function isLocal(url: string) { + ctx['http.isLocal'] = async function isLocal(url: string) { let { hostname, protocol } = new URL(url) if (protocol !== 'http:' && protocol !== 'https:') return true if (/^\[.+\]$/.test(hostname)) { @@ -73,6 +73,6 @@ export function apply(ctx: Context, config: Config) { ctx.on('dispose', () => { ctx['http.file'] = undefined - ctx['http.local'] = undefined + ctx['http.isLocal'] = undefined }) } diff --git a/packages/proxy-agent/package.json b/packages/proxy-agent/package.json index c584798..4cb76a7 100644 --- a/packages/proxy-agent/package.json +++ b/packages/proxy-agent/package.json @@ -1,7 +1,7 @@ { "name": "undios-proxy-agent", "description": "Proxy agent support for undios", - "version": "0.1.3", + "version": "0.1.4", "type": "module", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -48,7 +48,7 @@ }, "peerDependencies": { "cordis": "^3.10.4", - "undios": "^0.1.5" + "undios": "^0.1.9" }, "dependencies": { "http-proxy-agent": "^7.0.0",