diff --git a/packages/network/src/services/http/implementations/fetch.ts b/packages/network/src/services/http/implementations/fetch.ts index 0ad5b88c09..1d09e808da 100644 --- a/packages/network/src/services/http/implementations/fetch.ts +++ b/packages/network/src/services/http/implementations/fetch.ts @@ -21,10 +21,12 @@ import type { Subscriber } from 'rxjs'; import type { HTTPRequest } from '../request'; import type { HTTPEvent, HTTPResponseBody } from '../response'; import type { IHTTPImplementation } from './implementation'; +import { ILogService } from '@univerjs/core'; import { Observable } from 'rxjs'; import { HTTPHeaders } from '../headers'; import { HTTPStatusCode } from '../http'; import { HTTPProgress, HTTPResponse, HTTPResponseError } from '../response'; +import { parseFetchParamsFromRequest } from './util'; /** * An HTTP implementation using Fetch API. This implementation can both run in browser and Node.js. @@ -32,10 +34,14 @@ import { HTTPProgress, HTTPResponse, HTTPResponseError } from '../response'; * It does not support streaming response yet (May 12, 2024). */ export class FetchHTTPImplementation implements IHTTPImplementation { + constructor( + @ILogService private readonly _logService: ILogService + ) { } + send(request: HTTPRequest): Observable> { return new Observable((subscriber) => { const abortController = new AbortController(); - this._send(request, subscriber, abortController).then(() => {}, (error) => { + this._send(request, subscriber, abortController).catch((error) => { subscriber.error(new HTTPResponseError({ error, request, @@ -50,21 +56,28 @@ export class FetchHTTPImplementation implements IHTTPImplementation { let response: Response; try { - const fetchParams = this._parseFetchParamsFromRequest(request); - const fetchPromise = fetch(request.getUrlWithParams(), { + const fetchParams = parseFetchParamsFromRequest(request); + const urlWithParams = request.getUrlWithParams(); + const fetchPromise = fetch(urlWithParams, { signal: abortController.signal, ...fetchParams, }); + this._logService.debug(`[FetchHTTPImplementation]: sending request to url ${urlWithParams} with params ${fetchParams}`); + response = await fetchPromise; } catch (error: any) { - subscriber.error(new HTTPResponseError({ + const e = new HTTPResponseError({ request, error, status: error.status ?? 0, statusText: error.statusText ?? 'Unknown Error', headers: error.headers, - })); + }); + + this._logService.error('[FetchHTTPImplementation]: network error', e); + + subscriber.error(e); return; } @@ -87,13 +100,17 @@ export class FetchHTTPImplementation implements IHTTPImplementation { statusText, })); } else { - subscriber.error(new HTTPResponseError({ + const e = new HTTPResponseError({ request, error: body, status, statusText, headers: responseHeaders, - })); + }); + + this._logService.error('[FetchHTTPImplementation]: network error', e); + + subscriber.error(e); } subscriber.complete(); @@ -138,27 +155,21 @@ export class FetchHTTPImplementation implements IHTTPImplementation { const body = deserialize(request, all, contentType); return body; } catch (error) { - subscriber.error(new HTTPResponseError({ + const e = new HTTPResponseError({ request, error, status: response.status, statusText: response.statusText, headers: new HTTPHeaders(response.headers), - })); + }); + + this._logService.error('[FetchHTTPImplementation]: network error', e); + + subscriber.error(e); + return null; } } - - private _parseFetchParamsFromRequest(request: HTTPRequest): RequestInit { - const fetchParams: RequestInit = { - method: request.method, - headers: request.getHeadersInit(), - body: request.getBody(), - credentials: request.withCredentials ? 'include' : undefined, - }; - - return fetchParams; - } } function mergeChunks(chunks: Uint8Array[], totalLength: number): Uint8Array { diff --git a/packages/network/src/services/http/implementations/implementation.ts b/packages/network/src/services/http/implementations/implementation.ts index 3144812e7e..88fe334988 100644 --- a/packages/network/src/services/http/implementations/implementation.ts +++ b/packages/network/src/services/http/implementations/implementation.ts @@ -14,11 +14,10 @@ * limitations under the License. */ -import { createIdentifier } from '@univerjs/core'; import type { Observable } from 'rxjs'; - import type { HTTPRequest } from '../request'; import type { HTTPEvent } from '../response'; +import { createIdentifier } from '@univerjs/core'; /** * HTTP service could be implemented differently on platforms. diff --git a/packages/network/src/services/http/implementations/util.ts b/packages/network/src/services/http/implementations/util.ts new file mode 100644 index 0000000000..d784170a9d --- /dev/null +++ b/packages/network/src/services/http/implementations/util.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * 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. + */ + +import type { HTTPRequest } from '../request'; + +export function parseFetchParamsFromRequest(request: HTTPRequest): RequestInit { + const fetchParams: RequestInit = { + method: request.method, + headers: request.getHeadersInit(), + body: request.getBody(), + credentials: request.withCredentials ? 'include' : undefined, + }; + + return fetchParams; +} + diff --git a/packages/network/src/services/http/implementations/xhr.ts b/packages/network/src/services/http/implementations/xhr.ts index bbd1f3d0f5..7a2c2b402c 100644 --- a/packages/network/src/services/http/implementations/xhr.ts +++ b/packages/network/src/services/http/implementations/xhr.ts @@ -17,38 +17,38 @@ /* eslint-disable max-lines-per-function */ /* eslint-disable ts/no-explicit-any */ -import type { Nullable } from '@univerjs/core'; import type { Observer } from 'rxjs'; import type { HTTPRequest } from '../request'; - import type { HTTPEvent } from '../response'; import type { IHTTPImplementation } from './implementation'; +import { ILogService, type Nullable } from '@univerjs/core'; import { Observable } from 'rxjs'; import { HTTPHeaders } from '../headers'; import { ErrorStatusCodeLowerBound, HTTPStatusCode, SuccessStatusCodeLowerBound } from '../http'; import { HTTPResponse, HTTPResponseError, ResponseHeader } from '../response'; +import { parseFetchParamsFromRequest } from './util'; /** * An HTTP implementation using XHR. HTTP service provided by this service could only be async (we do not support sync XHR now). */ export class XHRHTTPImplementation implements IHTTPImplementation { + constructor( + @ILogService private readonly _logService: ILogService + ) { } + send(request: HTTPRequest): Observable> { return new Observable((observer: Observer>) => { const xhr = new XMLHttpRequest(); - xhr.open(request.method, request.getUrlWithParams()); + const urlWithParams = request.getUrlWithParams(); + const fetchParams = parseFetchParamsFromRequest(request); + + xhr.open(request.method, urlWithParams); if (request.withCredentials) { xhr.withCredentials = true; } - // set default HTTP headers - - request.headers.forEach((key, value) => xhr.setRequestHeader(key, value.join(','))); - if (!request.headers.has('Accept')) { - xhr.setRequestHeader('Accept', 'application/json, text/plain, */*'); - } - - if (!request.headers.has('Content-Type')) { - xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); + if (fetchParams.headers) { + Object.entries(fetchParams.headers).forEach(([key, value]) => xhr.setRequestHeader(key, value)); } let responseHeader: Nullable; @@ -103,21 +103,22 @@ export class XHRHTTPImplementation implements IHTTPImplementation { }) ); } else { - observer.error( - new HTTPResponseError({ - request, - error, - headers, - status, - statusText, - }) - ); - // Handler server logic error here + const e = new HTTPResponseError({ + request, + error, + headers, + status, + statusText, + }); + + this._logService.error('[XHRHTTPImplementation]: network error', e); + + observer.error(e); } }; const onErrorHandler = (error: ProgressEvent) => { - const res = new HTTPResponseError({ + const e = new HTTPResponseError({ request, error, status: xhr.status || 0, @@ -125,7 +126,9 @@ export class XHRHTTPImplementation implements IHTTPImplementation { headers: buildResponseHeader().headers, }); - observer.error(res); + this._logService.error('[XHRHTTPImplementation]: network error', e); + + observer.error(e); }; xhr.addEventListener('load', onLoadHandler); @@ -136,6 +139,8 @@ export class XHRHTTPImplementation implements IHTTPImplementation { const body = request.getBody(); xhr.send(body); + this._logService.debug(`[XHRHTTPImplementation]: sending request to url ${urlWithParams} with params ${fetchParams}`); + // Abort the request if the subscription is disposed before the request completes. return () => { if (xhr.readyState !== xhr.DONE) { diff --git a/packages/network/src/services/http/request.ts b/packages/network/src/services/http/request.ts index afa7f2bc67..cdac91c744 100644 --- a/packages/network/src/services/http/request.ts +++ b/packages/network/src/services/http/request.ts @@ -15,9 +15,9 @@ */ import type { HTTPHeaders } from './headers'; -import { ApplicationJSONType } from './headers'; import type { HTTPResponseType } from './http'; import type { HTTPParams } from './params'; +import { ApplicationJSONType } from './headers'; export type HTTPRequestMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; @@ -51,7 +51,7 @@ export class HTTPRequest { readonly method: HTTPRequestMethod, readonly url: string, readonly requestParams?: IHTTPRequestParams - ) {} + ) { } getUrlWithParams(): string { const params = this.requestParams?.params?.toString();