New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Base Http for BaseServer #32999
Merged
Merged
Base Http for BaseServer #32999
Changes from 4 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
25712c2
Initial abstract classes
karaggeorge 0be075c
WIP
karaggeorge 1e015b0
Merge branch 'canary' into base-req-res
karaggeorge 74c3a7f
Remove Node http classes from base server
karaggeorge b001269
Fix request meta accessors
karaggeorge 95f7e00
Fix handler parameters
karaggeorge 7fbff58
Remove unused imports
karaggeorge 6525de6
Fix serverless handlers
karaggeorge 80ea9ea
Fix preview data
karaggeorge 8a5ba9d
Fix cookies missing in Node req and remove comment
karaggeorge e9b478a
Merge branch 'canary' into base-http
karaggeorge 4d8fbdb
Merge branch 'canary' into base-http
karaggeorge 01761c7
Merge branch 'canary' into base-http
karaggeorge 5a98664
merge canary and resolve conflicts
shuding 7ee6e3f
small fixes
shuding 7616f52
rename .req and .res for node http interfaces
shuding File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
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,264 @@ | ||
import type { ServerResponse, IncomingMessage, IncomingHttpHeaders } from 'http' | ||
import type { Writable, Readable } from 'stream' | ||
import { PERMANENT_REDIRECT_STATUS } from '../shared/lib/constants' | ||
// import { ParsedNextUrl, parseNextUrl } from '../shared/lib/router/utils/parse-next-url' | ||
import { getCookieParser, NextApiRequestCookies, parseBody } from './api-utils' | ||
import { I18NConfig } from './config-shared' | ||
import { NEXT_REQUEST_META, RequestMeta } from './request-meta' | ||
|
||
export interface BaseNextRequestConfig { | ||
basePath: string | undefined | ||
i18n?: I18NConfig | ||
trailingSlash?: boolean | undefined | ||
} | ||
|
||
export abstract class BaseNextRequest<Body = any> { | ||
protected _cookies: NextApiRequestCookies | undefined | ||
public abstract headers: IncomingHttpHeaders | ||
|
||
constructor(public method: string, public url: string, public body: Body) {} | ||
|
||
abstract parseBody(limit: string | number): Promise<any> | ||
|
||
// Utils implemented using the abstract methods above | ||
|
||
public get cookies() { | ||
if (this._cookies) return this._cookies | ||
|
||
return (this._cookies = getCookieParser(this.headers)()) | ||
} | ||
} | ||
|
||
export class NodeNextRequest extends BaseNextRequest<Readable> { | ||
public headers = this.req.headers | ||
|
||
set [NEXT_REQUEST_META](value: RequestMeta) { | ||
// Mirror meta object to Node request for when `getRequestMeta` gets called on it | ||
// This still happens in render.tsx | ||
this.req[NEXT_REQUEST_META] = value | ||
} | ||
|
||
constructor( | ||
public req: IncomingMessage & { [NEXT_REQUEST_META]?: RequestMeta } | ||
) { | ||
super(req.method!.toUpperCase(), req.url!, req) | ||
} | ||
|
||
async parseBody(limit: string | number): Promise<any> { | ||
return parseBody(this.req, limit) | ||
} | ||
} | ||
|
||
export class WebNextRequest extends BaseNextRequest<ReadableStream | null> { | ||
public request: Request | ||
public headers: IncomingHttpHeaders | ||
|
||
constructor(request: Request) { | ||
const url = new URL(request.url) | ||
|
||
super( | ||
request.method, | ||
url.href.slice(url.origin.length), | ||
request.clone().body | ||
) | ||
this.request = request | ||
|
||
this.headers = {} | ||
for (const [name, value] of request.headers.entries()) { | ||
this.headers[name] = value | ||
} | ||
} | ||
|
||
async parseBody(_limit: string | number): Promise<any> { | ||
// TODO: implement parseBody for web | ||
shuding marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return | ||
} | ||
} | ||
|
||
export abstract class BaseNextResponse<Destination = any> { | ||
abstract statusCode: number | undefined | ||
abstract statusMessage: string | undefined | ||
abstract get sent(): boolean | ||
|
||
constructor(public destination: Destination) {} | ||
|
||
/** | ||
* Sets a value for the header overwriting existing values | ||
*/ | ||
abstract setHeader(name: string, value: string | string[]): this | ||
|
||
/** | ||
* Appends value for the given header name | ||
*/ | ||
abstract appendHeader(name: string, value: string): this | ||
|
||
/** | ||
* Get all vaues for a header as an array or undefined if no value is present | ||
*/ | ||
abstract getHeaderValues(name: string): string[] | undefined | ||
|
||
abstract hasHeader(name: string): boolean | ||
|
||
/** | ||
* Get vaues for a header concatenated using `,` or undefined if no value is present | ||
*/ | ||
abstract getHeader(name: string): string | undefined | ||
|
||
abstract body(value: string): this | ||
|
||
abstract send(): void | ||
|
||
// Utils implemented using the abstract methods above | ||
|
||
redirect(destination: string, statusCode: number) { | ||
this.setHeader('Location', destination) | ||
this.statusCode = statusCode | ||
|
||
if (statusCode === PERMANENT_REDIRECT_STATUS) { | ||
this.setHeader('Refresh', `0;url=${destination}`) | ||
} | ||
return this | ||
} | ||
} | ||
|
||
export class NodeNextResponse extends BaseNextResponse<Writable> { | ||
private textBody: string | undefined = undefined | ||
|
||
constructor(public res: ServerResponse) { | ||
super(res) | ||
} | ||
|
||
get sent() { | ||
return this.res.finished || this.res.headersSent | ||
} | ||
|
||
get statusCode() { | ||
return this.res.statusCode | ||
} | ||
|
||
set statusCode(value: number) { | ||
this.res.statusCode = value | ||
} | ||
|
||
get statusMessage() { | ||
return this.res.statusMessage | ||
} | ||
|
||
set statusMessage(value: string) { | ||
this.res.statusMessage = value | ||
} | ||
|
||
setHeader(name: string, value: string | string[]): this { | ||
this.res.setHeader(name, value) | ||
return this | ||
} | ||
|
||
getHeaderValues(name: string): string[] | undefined { | ||
const values = this.res.getHeader(name) | ||
|
||
if (values === undefined) return undefined | ||
|
||
return (Array.isArray(values) ? values : [values]).map((value) => | ||
value.toString() | ||
) | ||
} | ||
|
||
hasHeader(name: string): boolean { | ||
return this.res.hasHeader(name) | ||
} | ||
|
||
getHeader(name: string): string | undefined { | ||
const values = this.getHeaderValues(name) | ||
return Array.isArray(values) ? values.join(',') : undefined | ||
} | ||
|
||
appendHeader(name: string, value: string): this { | ||
const currentValues = this.getHeaderValues(name) ?? [] | ||
|
||
if (!currentValues.includes(value)) { | ||
this.res.setHeader(name, [...currentValues, value]) | ||
} | ||
|
||
return this | ||
} | ||
|
||
body(value: string) { | ||
this.textBody = value | ||
return this | ||
} | ||
|
||
send() { | ||
this.res.end(this.textBody) | ||
} | ||
} | ||
|
||
export class WebNextResponse extends BaseNextResponse<WritableStream> { | ||
private headers = new Headers() | ||
private textBody: string | undefined = undefined | ||
private _sent = false | ||
|
||
private sendPromise = new Promise<void>((resolve) => { | ||
this.sendResolve = resolve | ||
}) | ||
private sendResolve?: () => void | ||
private response = this.sendPromise.then(() => { | ||
return new Response(this.textBody ?? this.transformStream.readable, { | ||
headers: this.headers, | ||
status: this.statusCode, | ||
statusText: this.statusMessage, | ||
}) | ||
}) | ||
|
||
public statusCode: number | undefined | ||
public statusMessage: string | undefined | ||
|
||
get sent() { | ||
return this._sent | ||
} | ||
|
||
constructor(public transformStream = new TransformStream()) { | ||
super(transformStream.writable) | ||
} | ||
|
||
setHeader(name: string, value: string | string[]): this { | ||
this.headers.delete(name) | ||
for (const val of Array.isArray(value) ? value : [value]) { | ||
this.headers.append(name, val) | ||
} | ||
return this | ||
} | ||
|
||
getHeaderValues(name: string): string[] | undefined { | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Headers/get#example | ||
return this.getHeader(name) | ||
?.split(',') | ||
.map((v) => v.trimStart()) | ||
} | ||
|
||
getHeader(name: string): string | undefined { | ||
return this.headers.get(name) ?? undefined | ||
} | ||
|
||
hasHeader(name: string): boolean { | ||
return this.headers.has(name) | ||
} | ||
|
||
appendHeader(name: string, value: string): this { | ||
this.headers.append(name, value) | ||
return this | ||
} | ||
|
||
body(value: string) { | ||
this.textBody = value | ||
return this | ||
} | ||
|
||
send() { | ||
this.sendResolve?.() | ||
this._sent = true | ||
} | ||
|
||
toResponse() { | ||
return this.response | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Not blocking) maybe we can separate Web and Node request/response in 2 files just like we did for base-server and web-server.