-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: Improve Compatibility with standard Response object
- Loading branch information
Showing
3 changed files
with
94 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,58 +1,62 @@ | ||
// Define lightweight pseudo Response object and replace global.Response with it. | ||
// Define lightweight pseudo Response class and replace global.Response with it. | ||
|
||
import type { OutgoingHttpHeaders } from 'node:http' | ||
import { buildOutgoingHttpHeaders } from './utils' | ||
|
||
const globalResponse = global.Response | ||
const responsePrototype: Record<string, any> = { | ||
export const globalResponse = global.Response | ||
class Response { | ||
getResponseCache() { | ||
delete this.__cache | ||
return (this.responseCache ||= new globalResponse(this.__body, this.__init)) | ||
}, | ||
delete (this as any).__cache | ||
return ((this as any).responseCache ||= new globalResponse( | ||
(this as any).__body, | ||
(this as any).__init | ||
)) | ||
} | ||
|
||
constructor(body: BodyInit | null, init?: ResponseInit) { | ||
;(this as any).__body = body | ||
;(this as any).__init = init | ||
|
||
if (typeof body === 'string' || body instanceof ReadableStream) { | ||
let headers = (init?.headers || { 'content-type': 'text/plain;charset=UTF-8' }) as | ||
| Record<string, string> | ||
| Headers | ||
| OutgoingHttpHeaders | ||
if (headers instanceof Headers) { | ||
headers = buildOutgoingHttpHeaders(headers) | ||
} | ||
|
||
;(this as any).__cache = [init?.status || 200, body, headers] | ||
} | ||
} | ||
} | ||
;[ | ||
'body', | ||
'bodyUsed', | ||
'headers', | ||
'ok', | ||
'redirected', | ||
'status', | ||
'statusText', | ||
'trailers', | ||
'type', | ||
'url', | ||
].forEach((k) => { | ||
Object.defineProperty(responsePrototype, k, { | ||
Object.defineProperty(Response.prototype, k, { | ||
get() { | ||
return this.getResponseCache()[k] | ||
}, | ||
}) | ||
}) | ||
;['arrayBuffer', 'blob', 'clone', 'error', 'formData', 'json', 'redirect', 'text'].forEach((k) => { | ||
Object.defineProperty(responsePrototype, k, { | ||
;['arrayBuffer', 'blob', 'clone', 'formData', 'json', 'text'].forEach((k) => { | ||
Object.defineProperty(Response.prototype, k, { | ||
value: function () { | ||
return this.getResponseCache()[k]() | ||
}, | ||
}) | ||
}) | ||
|
||
function newResponse(this: Response, body: BodyInit | null, init?: ResponseInit) { | ||
;(this as any).status = init?.status || 200 | ||
;(this as any).__body = body | ||
;(this as any).__init = init | ||
|
||
if (typeof body === 'string' || body instanceof ReadableStream) { | ||
let headers = (init?.headers || { 'content-type': 'text/plain;charset=UTF-8' }) as | ||
| Record<string, string> | ||
| Headers | ||
| OutgoingHttpHeaders | ||
if (headers instanceof Headers) { | ||
headers = buildOutgoingHttpHeaders(headers) | ||
} | ||
|
||
;(this as any).__cache = [body, headers] | ||
} | ||
} | ||
newResponse.prototype = responsePrototype | ||
Object.setPrototypeOf(Response, globalResponse) | ||
Object.setPrototypeOf(Response.prototype, globalResponse.prototype) | ||
Object.defineProperty(global, 'Response', { | ||
value: newResponse, | ||
value: Response, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { createServer, type Server } from 'node:http' | ||
import { AddressInfo } from 'node:net' | ||
import { globalResponse } from '../src/response' | ||
|
||
class NextResponse extends Response {} | ||
|
||
describe('Response', () => { | ||
let server: Server | ||
let port: number | ||
beforeAll( | ||
async () => | ||
new Promise<void>((resolve) => { | ||
server = createServer((_, res) => { | ||
res.writeHead(200, { | ||
'Content-Type': 'application/json charset=UTF-8', | ||
}) | ||
res.end(JSON.stringify({ status: 'ok' })) | ||
}) | ||
.listen(0) | ||
.on('listening', () => { | ||
port = (server.address() as AddressInfo).port | ||
resolve() | ||
}) | ||
}) | ||
) | ||
|
||
afterAll(() => { | ||
server.close() | ||
}) | ||
|
||
it('Compatibility with standard Response object', async () => { | ||
// response name not changed | ||
expect(Response.name).toEqual('Response') | ||
|
||
// response prototype chain not changed | ||
expect(new Response() instanceof globalResponse).toBe(true) | ||
|
||
// `fetch()` and `Response` are not changed | ||
const fetchRes = await fetch(`http://localhost:${port}`) | ||
expect(new Response() instanceof fetchRes.constructor).toBe(true) | ||
const resJson = await fetchRes.json() | ||
expect(fetchRes.headers.get('content-type')).toEqual('application/json charset=UTF-8') | ||
expect(resJson).toEqual({ status: 'ok' }) | ||
|
||
// can only use new operator | ||
expect(() => { | ||
;(Response as any)() | ||
}).toThrow() | ||
|
||
// support Response static method | ||
expect(Response.error).toEqual(expect.any(Function)) | ||
expect((Response as any).json).toEqual(expect.any(Function)) | ||
expect(Response.redirect).toEqual(expect.any(Function)) | ||
|
||
// support other class to extends from Response | ||
expect(NextResponse.prototype).toBeInstanceOf(Response) | ||
}) | ||
}) |