@czf0613/http_client 0.0.2 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,9 +68,9 @@ Makes an HTTP request with default configuration using the Fetch API.
68
68
  - String or number: sent as `text/plain`
69
69
  - Object: sent as `application/json`
70
70
  - FormData: sent as `multipart/form-data`
71
- - `timeoutMs` (number): Timeout in milliseconds. Default: 5000
71
+ - `timeoutMs` (number): Timeout in milliseconds. Default: 5000. **Note: this value is the *connect timeout*, not the total request timeout. You need to handle the *read timeout* in your application logic.**
72
72
 
73
- **Returns:** Promise<Response> - Fetch API Response object
73
+ **Returns:** Promise<ExtendedResponse> - Extended Response object with additional helper methods. It's the same as the standard Response object, but with additional methods for convenience.
74
74
 
75
75
  **Note:** This function does not handle exceptions by default. You should wrap it in a try-catch block.
76
76
 
@@ -128,9 +128,13 @@ Makes an SSE (Server-Sent Events) request and returns an async generator that yi
128
128
  - `queryParams` (Record<string, string | number> | null): Query parameters to be appended to the URL. Default: null
129
129
  - `customHeaders` (Record<string, string> | null): Custom headers. **Important: Do NOT include Content-Type in customHeaders.** Default: null
130
130
  - `body` (any | null): Request body for POST/PUT requests. Default: null
131
+ - `connectTimeoutMs` (number): Connection timeout in milliseconds. Default: 30000
132
+ - `messageTimeoutMs` (number): Timeout for each message read in milliseconds. Default: 30000
131
133
 
132
134
  **Note:** For detailed parameter descriptions, see [`makeHttpRequest`](#makehttprequest) above. This function only handles responses in the format `data: xxx\n\n`. It does not throw exceptions by default; success/failure is indicated in the generator's return value.
133
135
 
136
+ This method will **not** throw exceptions, you can get the success/failure status from the generator's return value.
137
+
134
138
  **Returns:** AsyncGenerator<string, boolean, undefined> - An async generator that yields each message as a string, and returns a boolean indicating success (true) or failure (false)
135
139
 
136
140
  **Example:**
@@ -144,7 +148,9 @@ async function streamChat() {
144
148
  'POST',
145
149
  null,
146
150
  null,
147
- { message: 'Hello' }
151
+ { message: 'Hello' },
152
+ 30000, // connectTimeoutMs
153
+ 10000 // messageTimeoutMs
148
154
  );
149
155
 
150
156
  // Manually iterate to receive chunks
@@ -166,6 +172,14 @@ async function streamChat() {
166
172
  }
167
173
  ```
168
174
 
175
+ ## Test
176
+
177
+ Run tests using:
178
+
179
+ ```bash
180
+ npm test
181
+ ```
182
+
169
183
  ## License
170
184
 
171
185
  MIT
@@ -1,3 +1,4 @@
1
+ import { ExtendedResponse } from './response_ext';
1
2
  /**
2
3
  * 拼接URL和查询参数,会自动处理转义
3
4
  * @param url 基础URL,不要带任何查询参数
@@ -14,16 +15,19 @@ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD';
14
15
  * @param queryParams 查询参数,会被自动拼接到url中(会自动进行转义)
15
16
  * @param customHeaders 自定义请求头,不需要写content-type这种会被自动处理的头
16
17
  * @param body 请求体,适用于POST/PUT请求,传入字符串、数字会被处理成text/plain,传入对象会被处理成application/json,传入FormData会被处理成multipart/form-data,目前不建议直接发送二进制对象
17
- * @returns 返回Fetch API的Response对象
18
+ * @param timeoutMs 超时时间,单位毫秒,默认5秒。这个超时的时间指的是发出请求,到接收到请求头的时间,不包含读取请求体的时间。如果需要控制读取请求体的超时,请自行用Promise.race实现。
19
+ * @returns 返回Fetch API的Response对象,用法完全一样,但是会被包装成ExtendedResponse,增加了一些方法
18
20
  */
19
- export declare function makeHttpRequest(url: string, method?: HttpMethod, queryParams?: Record<string, string | number> | null, customHeaders?: Record<string, string> | null, body?: any | null, timeoutMs?: number): Promise<Response>;
21
+ export declare function makeHttpRequest(url: string, method?: HttpMethod, queryParams?: Record<string, string | number> | null, customHeaders?: Record<string, string> | null, body?: any | null, timeoutMs?: number): Promise<ExtendedResponse>;
20
22
  /**
21
23
  * 发起一个SSE请求,返回一个异步生成器,每次迭代返回一个字符串(流式响应)
22
24
  * 这个方法是拿来补充浏览器里面的EventSource的,扩展了很多没有的功能,跟原版的SSE协议不完全兼容。
23
25
  * 目前这个接口只支持处理后端不停发送data: xxx\n\n这种格式的响应
24
26
  * 默认不抛出异常,会在生成器的返回值里面指明成功还是失败
25
27
  * @see makeHttpRequest 这里的参数说明
28
+ * @param connectTimeoutMs 连接超时时间,单位毫秒,默认30秒
29
+ * @param messageTimeoutMs 每条消息超时时间,单位毫秒,默认30秒
26
30
  * @returns 返回一个异步生成器,每次迭代返回一个字符串(流式响应)
27
31
  */
28
- export declare function makeSSERequest(url: string, method?: HttpMethod, queryParams?: Record<string, string | number> | null, customHeaders?: Record<string, string> | null, body?: any | null): AsyncGenerator<string, boolean, undefined>;
32
+ export declare function makeSSERequest(url: string, method?: HttpMethod, queryParams?: Record<string, string | number> | null, customHeaders?: Record<string, string> | null, body?: any | null, connectTimeoutMs?: number, messageTimeoutMs?: number): AsyncGenerator<string, boolean, undefined>;
29
33
  export {};
@@ -1,3 +1,5 @@
1
+ import { sleep, TIMEOUT_MARKER, DEFAULT_TIMEOUT } from './timer';
2
+ import { ExtendedResponse } from './response_ext';
1
3
  /**
2
4
  * 拼接URL和查询参数,会自动处理转义
3
5
  * @param url 基础URL,不要带任何查询参数
@@ -19,8 +21,6 @@ export function joinUrlWithParams(url, queryParams) {
19
21
  });
20
22
  return `${url}?${params.toString()}`;
21
23
  }
22
- // 默认超时时间,单位毫秒
23
- const DEFAULT_TIMEOUT = 5000;
24
24
  /**
25
25
  * 发起一个带有默认配置的 HTTP 请求
26
26
  * 默认不处理异常,需要try catch
@@ -29,7 +29,8 @@ const DEFAULT_TIMEOUT = 5000;
29
29
  * @param queryParams 查询参数,会被自动拼接到url中(会自动进行转义)
30
30
  * @param customHeaders 自定义请求头,不需要写content-type这种会被自动处理的头
31
31
  * @param body 请求体,适用于POST/PUT请求,传入字符串、数字会被处理成text/plain,传入对象会被处理成application/json,传入FormData会被处理成multipart/form-data,目前不建议直接发送二进制对象
32
- * @returns 返回Fetch API的Response对象
32
+ * @param timeoutMs 超时时间,单位毫秒,默认5秒。这个超时的时间指的是发出请求,到接收到请求头的时间,不包含读取请求体的时间。如果需要控制读取请求体的超时,请自行用Promise.race实现。
33
+ * @returns 返回Fetch API的Response对象,用法完全一样,但是会被包装成ExtendedResponse,增加了一些方法
33
34
  */
34
35
  export async function makeHttpRequest(url, method = 'GET', queryParams = null, customHeaders = null, body = null, timeoutMs = DEFAULT_TIMEOUT) {
35
36
  // 处理查询参数
@@ -44,7 +45,9 @@ export async function makeHttpRequest(url, method = 'GET', queryParams = null, c
44
45
  // 无body时,不设置Content-Type
45
46
  }
46
47
  else if (body instanceof FormData) {
47
- // 使用FormData时,浏览器会自动设置Content-Type和boundary,不要多手
48
+ // 使用FormData时,浏览器会自动设置Content-Type和boundary,不要多手,有我也得给你删了
49
+ delete customHeaders['Content-Type'];
50
+ delete customHeaders['content-type'];
48
51
  }
49
52
  else if (typeof body === 'string') {
50
53
  customHeaders['Content-Type'] = 'text/plain';
@@ -67,7 +70,7 @@ export async function makeHttpRequest(url, method = 'GET', queryParams = null, c
67
70
  signal: controller.signal,
68
71
  });
69
72
  clearTimeout(timeoutId);
70
- return resp;
73
+ return ExtendedResponse.create(resp);
71
74
  }
72
75
  /**
73
76
  * 发起一个SSE请求,返回一个异步生成器,每次迭代返回一个字符串(流式响应)
@@ -75,22 +78,34 @@ export async function makeHttpRequest(url, method = 'GET', queryParams = null, c
75
78
  * 目前这个接口只支持处理后端不停发送data: xxx\n\n这种格式的响应
76
79
  * 默认不抛出异常,会在生成器的返回值里面指明成功还是失败
77
80
  * @see makeHttpRequest 这里的参数说明
81
+ * @param connectTimeoutMs 连接超时时间,单位毫秒,默认30秒
82
+ * @param messageTimeoutMs 每条消息超时时间,单位毫秒,默认30秒
78
83
  * @returns 返回一个异步生成器,每次迭代返回一个字符串(流式响应)
79
84
  */
80
- export async function* makeSSERequest(url, method = 'GET', queryParams = null, customHeaders = null, body = null) {
85
+ export async function* makeSSERequest(url, method = 'GET', queryParams = null, customHeaders = null, body = null, connectTimeoutMs = 30000, messageTimeoutMs = 30000) {
81
86
  var _a;
82
- const resp = await makeHttpRequest(url, method, queryParams, customHeaders, body, 30000);
83
- if (!resp.ok) {
84
- return false;
85
- }
86
- const reader = (_a = resp.body) === null || _a === void 0 ? void 0 : _a.getReader();
87
- if (reader == null) {
88
- return false;
89
- }
90
- let buffer = new Uint8Array(0);
87
+ // 最后需要释放它的锁,所以需要写出来
88
+ let reader = void 0;
91
89
  try {
90
+ const resp = await makeHttpRequest(url, method, queryParams, customHeaders, body, connectTimeoutMs);
91
+ if (!resp.ok) {
92
+ return false;
93
+ }
94
+ reader = (_a = resp.body) === null || _a === void 0 ? void 0 : _a.getReader();
95
+ if (reader == null) {
96
+ return false;
97
+ }
98
+ let buffer = new Uint8Array(0);
92
99
  while (true) {
93
- const { done, value } = await reader.read();
100
+ const raceResult = await Promise.race([
101
+ sleep(messageTimeoutMs),
102
+ reader.read(),
103
+ ]);
104
+ // 如果timeout先完成,说明超时了
105
+ if (raceResult === TIMEOUT_MARKER) {
106
+ throw new Error('SSE message timeout');
107
+ }
108
+ const { done, value } = raceResult;
94
109
  if (done || !value) {
95
110
  break;
96
111
  }
@@ -143,7 +158,9 @@ export async function* makeSSERequest(url, method = 'GET', queryParams = null, c
143
158
  return false;
144
159
  }
145
160
  finally {
146
- reader.releaseLock();
161
+ if (reader != null) {
162
+ reader.releaseLock();
163
+ }
147
164
  }
148
165
  return true;
149
166
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 扩展的Response类,使用Proxy自动代理所有Response属性和方法
3
+ * 用户可以像普通的Response对象一样使用,也可以调用额外的方法
4
+ */
5
+ export declare class ExtendedResponse implements Response {
6
+ private _response;
7
+ private constructor();
8
+ get [Symbol.toStringTag](): string;
9
+ static create(response: Response): ExtendedResponse;
10
+ clone(): ExtendedResponse;
11
+ readonly body: ReadableStream<Uint8Array<ArrayBuffer>> | null;
12
+ readonly bodyUsed: boolean;
13
+ readonly headers: Headers;
14
+ readonly ok: boolean;
15
+ readonly redirected: boolean;
16
+ readonly status: number;
17
+ readonly statusText: string;
18
+ readonly type: ResponseType;
19
+ readonly url: string;
20
+ arrayBuffer(): Promise<ArrayBuffer>;
21
+ blob(): Promise<Blob>;
22
+ bytes(): Promise<Uint8Array<ArrayBuffer>>;
23
+ formData(): Promise<FormData>;
24
+ json(): Promise<any>;
25
+ text(): Promise<string>;
26
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 扩展的Response类,使用Proxy自动代理所有Response属性和方法
3
+ * 用户可以像普通的Response对象一样使用,也可以调用额外的方法
4
+ */
5
+ export class ExtendedResponse {
6
+ // 私有构造函数,通过静态方法创建实例
7
+ constructor(response) {
8
+ this._response = response;
9
+ }
10
+ // Symbol.toStringTag 用于正确的类型识别
11
+ get [Symbol.toStringTag]() {
12
+ return 'ExtendedResponse';
13
+ }
14
+ // 静态工厂方法,创建代理后的ExtendedResponse实例
15
+ static create(response) {
16
+ const instance = new ExtendedResponse(response);
17
+ // 使用Proxy自动代理所有属性访问
18
+ return new Proxy(instance, {
19
+ get(target, prop) {
20
+ // 如果是ExtendedResponse自定义的属性或方法,优先从target获取
21
+ switch (prop) {
22
+ case '_response':
23
+ case 'create':
24
+ case 'clone':
25
+ return target[prop];
26
+ default:
27
+ // 否则从原始Response获取
28
+ const value = target._response[prop];
29
+ // 如果是函数,需要绑定this
30
+ return typeof value === 'function' ? value.bind(target._response) : value;
31
+ }
32
+ }
33
+ });
34
+ }
35
+ // clone方法要特殊处理
36
+ clone() {
37
+ return ExtendedResponse.create(this._response.clone());
38
+ }
39
+ // Response接口的方法(实际通过Proxy代理到原始Response,这里的方法都也是骗编译器的)
40
+ arrayBuffer() {
41
+ throw new Error('Method not implemented.');
42
+ }
43
+ blob() {
44
+ throw new Error('Method not implemented.');
45
+ }
46
+ bytes() {
47
+ throw new Error('Method not implemented.');
48
+ }
49
+ formData() {
50
+ throw new Error('Method not implemented.');
51
+ }
52
+ json() {
53
+ throw new Error('Method not implemented.');
54
+ }
55
+ text() {
56
+ throw new Error('Method not implemented.');
57
+ }
58
+ }
@@ -0,0 +1,3 @@
1
+ export declare const DEFAULT_TIMEOUT = 5000;
2
+ export declare const TIMEOUT_MARKER: unique symbol;
3
+ export declare function sleep(ms: number): Promise<typeof TIMEOUT_MARKER>;
package/dist/timer.js ADDED
@@ -0,0 +1,6 @@
1
+ // 默认超时时间,单位毫秒
2
+ export const DEFAULT_TIMEOUT = 5000;
3
+ export const TIMEOUT_MARKER = Symbol('timeout');
4
+ export function sleep(ms) {
5
+ return new Promise(resolve => setTimeout(() => resolve(TIMEOUT_MARKER), ms));
6
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@czf0613/http_client",
3
3
  "private": false,
4
- "version": "0.0.2",
4
+ "version": "0.1.1",
5
5
  "description": "Simple http client with fetch, supporting modified SSE protocol",
6
6
  "keywords": [
7
7
  "http client",
@@ -20,8 +20,8 @@
20
20
  },
21
21
  "scripts": {
22
22
  "build": "tsc",
23
- "prepack": "npm run build",
24
- "test": "echo \"Error: no test specified\" && exit 1"
23
+ "pretest": "npm run build",
24
+ "test": "npx tsx tests/test_bing.ts"
25
25
  },
26
26
  "repository": {
27
27
  "type": "git",
@@ -32,6 +32,7 @@
32
32
  "license": "MIT",
33
33
  "homepage": "https://github.com/czf0613/http_client_ts#readme",
34
34
  "devDependencies": {
35
+ "@types/node": "^25.3.3",
35
36
  "typescript": "^5.9.3"
36
37
  }
37
38
  }