-
-
Notifications
You must be signed in to change notification settings - Fork 584
Added whatwg-node-server adapter for grafserv #2288
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: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,154 @@ | ||||||||
import { createServerAdapter } from '@whatwg-node/server' | ||||||||
|
||||||||
import { GrafservBase } from "../../core/base.js"; | ||||||||
import type { | ||||||||
GrafservBody, | ||||||||
GrafservConfig, | ||||||||
RequestDigest, | ||||||||
Result, | ||||||||
} from "../../interfaces.js"; | ||||||||
|
||||||||
import { OptionsFromConfig } from '../../options.js'; | ||||||||
import { httpError } from '../../utils.js'; | ||||||||
|
||||||||
export function getBodyFromRequest( | ||||||||
req: Request /* IncomingMessage */, | ||||||||
maxLength: number, | ||||||||
): Promise<GrafservBody> { | ||||||||
return new Promise(async (resolve, reject) => { | ||||||||
const chunks: Buffer[] = []; | ||||||||
let len = 0; | ||||||||
const handleDataCb = (chunk: Uint8Array<ArrayBufferLike>) => { | ||||||||
chunks.push(Buffer.from(chunk)); | ||||||||
len += chunk.length; | ||||||||
if (len > maxLength) { | ||||||||
reject(httpError(413, "Too much data")); | ||||||||
} | ||||||||
}; | ||||||||
const doneCb = () => { | ||||||||
resolve({ type: "buffer", buffer: Buffer.concat(chunks) }); | ||||||||
}; | ||||||||
const reader = req.body?.getReader() | ||||||||
if (!reader) { | ||||||||
return doneCb() | ||||||||
} | ||||||||
while (true) { | ||||||||
const {done, value} = await reader?.read() | ||||||||
if (value) { | ||||||||
handleDataCb(value) | ||||||||
} | ||||||||
if (done) { | ||||||||
return doneCb() | ||||||||
} | ||||||||
} | ||||||||
}); | ||||||||
} | ||||||||
|
||||||||
declare global { | ||||||||
// eslint-disable-next-line @typescript-eslint/no-namespace | ||||||||
namespace Grafast { | ||||||||
interface RequestContext { | ||||||||
whatwg: { | ||||||||
version:string | ||||||||
Comment on lines
+51
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry; I meant that the version should be part of the scope key like with the other servers; then when a new version is supported we can add support for that without breaking existing users. See the Koa, Express, Fastify, Lambda and H3 adaptors for example.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should also be factored into the file name; e.g. |
||||||||
request: Request | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
/** @experimental */ | ||||||||
export class WhatwgGrafserv extends GrafservBase { | ||||||||
protected whatwgRequestToGrafserv( | ||||||||
dynamicOptions: OptionsFromConfig, | ||||||||
request: Request | ||||||||
): RequestDigest { | ||||||||
const url = new URL(request.url); | ||||||||
return { | ||||||||
httpVersionMajor: 1, | ||||||||
httpVersionMinor: 1, | ||||||||
isSecure: url.protocol === 'https:', | ||||||||
method: request.method, | ||||||||
path: url.pathname, | ||||||||
headers: this.processHeaders(request.headers), | ||||||||
getQueryParams() { | ||||||||
return Object.fromEntries(url.searchParams.entries()) as Record<string, string>; | ||||||||
}, | ||||||||
async getBody() { | ||||||||
return getBodyFromRequest(request, dynamicOptions.maxRequestLength) | ||||||||
}, | ||||||||
requestContext: { | ||||||||
whatwg: { | ||||||||
version:'whatwgv1', | ||||||||
Comment on lines
+80
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
request | ||||||||
} | ||||||||
}, | ||||||||
preferJSON: true, | ||||||||
}; | ||||||||
} | ||||||||
|
||||||||
protected processHeaders(headers: Headers): Record<string, string> { | ||||||||
const headerDigest: Record<string, string> = Object.create(null); | ||||||||
headers.forEach((v,k)=> { | ||||||||
headerDigest[k]= v | ||||||||
}) | ||||||||
return headerDigest | ||||||||
} | ||||||||
|
||||||||
protected grafservResponseToWhatwg(response: Result | null): Response { | ||||||||
if (response === null) { | ||||||||
return new Response("¯\\_(ツ)_/¯", {status: 404, headers: new Headers({"Content-Type": "text/plain"})}) | ||||||||
} | ||||||||
|
||||||||
switch (response.type) { | ||||||||
case "error": { | ||||||||
const { statusCode, headers, error } = response; | ||||||||
const respHeaders = new Headers(headers) | ||||||||
respHeaders.append("Content-Type", "text/plain") | ||||||||
return new Response(error.message, {status: statusCode, headers:respHeaders}) | ||||||||
} | ||||||||
|
||||||||
case "buffer": { | ||||||||
const { statusCode, headers, buffer } = response; | ||||||||
const respHeaders = new Headers(headers) | ||||||||
return new Response(buffer, {status: statusCode, headers:respHeaders}) | ||||||||
} | ||||||||
|
||||||||
case "json": { | ||||||||
const { statusCode, headers, json } = response; | ||||||||
const respHeaders = new Headers(headers) | ||||||||
return new Response(JSON.stringify(json), {status: statusCode, headers:respHeaders}) | ||||||||
} | ||||||||
|
||||||||
default: { | ||||||||
console.log("Unhandled:"); | ||||||||
console.dir(response); | ||||||||
return new Response("Server hasn't implemented this yet", {status: 501, headers: new Headers({"Content-Type": "text/plain"})}) | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
createHandler() { | ||||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias | ||||||||
return createServerAdapter(async (request: Request): Promise<Response> => { | ||||||||
const dynamicOptions = this.dynamicOptions; | ||||||||
return this.grafservResponseToWhatwg( | ||||||||
await this.processWhatwgRequest( | ||||||||
request, | ||||||||
this.whatwgRequestToGrafserv(dynamicOptions, request), | ||||||||
), | ||||||||
); | ||||||||
}) | ||||||||
} | ||||||||
|
||||||||
protected processWhatwgRequest( | ||||||||
_request: Request, | ||||||||
request: RequestDigest, | ||||||||
) { | ||||||||
return this.processRequest(request); | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
/** @experimental */ | ||||||||
export function grafserv(config: GrafservConfig) { | ||||||||
return new WhatwgGrafserv(config); | ||||||||
} |
Uh oh!
There was an error while loading. Please reload this page.