npm install headers-polyfill
# or
yarn add headers-polyfill
Last active
August 26, 2022 12:09
-
-
Save ViieeS/139057f70bf98b66295b73bc3b30c391 to your computer and use it in GitHub Desktop.
NJS Web API
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
import convertNjsRequestToCloudflare from './request-adaptor' | |
import RequestPolyfill from './request-polyfill' | |
describe('request-adaptor', () => { | |
it('convertNjsRequestToCloudflare function', () => { | |
const cloudflareRequest: RequestPolyfill = convertNjsRequestToCloudflare({ | |
variables: { scheme: 'https' }, | |
headersIn: { | |
Host: 'example.com', | |
foo: 'bar', | |
}, | |
uri: '/path/name', | |
args: { foo: 'bar' }, | |
} as unknown as NginxHTTPRequest) | |
expect(cloudflareRequest.url).toBe('https://example.com/path/name?foo=bar') | |
expect(cloudflareRequest.headers.get('Host')).toBe('example.com') | |
expect(cloudflareRequest.headers.get('foo')).toBe('bar') | |
}) | |
}) |
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
import { Headers } from 'headers-polyfill' | |
import RequestPolyfill from './request-polyfill' | |
import resolveOrigin from './resolveOrigin' | |
/** | |
* This function converts NJS request to Cloudflare Request. | |
* @param request - NJS request | |
* @returns CloudFlare request | |
*/ | |
export default function convertNjsRequestToCloudflare( | |
request: NginxHTTPRequest | |
): RequestPolyfill { | |
const scheme: string = request.variables.scheme || 'http' | |
const host = resolveOrigin(request.headersIn['Host']) | |
if (!host) { | |
throw new Error('Host header is empty.') | |
} | |
const urlSearchParams = new URLSearchParams(request.args) | |
// 1. Cloudflare API expects this field named as "url". | |
// 2. NJS request.uri never contains scheme & host, so we add both. | |
let urlString = `${scheme}://${host}${request.uri}` | |
if (urlSearchParams.toString()) { | |
urlString = urlString.concat('?', urlSearchParams.toString()) | |
} | |
const url = new URL(urlString) | |
// HTTP headers | |
const headers = new Headers(request.headersIn as Record<string, string>) | |
return new RequestPolyfill(url.href, { headers: Object.fromEntries(headers) }) | |
} |
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
import RequestPolyfill from './request-polyfill' | |
describe('Request', () => { | |
it('constructor overloading - with url (string) as the 1st param', () => { | |
const request = new RequestPolyfill('http://example.com', { | |
headers: { foo: 'bar' }, | |
}) | |
expect(request.url).toBe('http://example.com') | |
expect(request.headers.get('foo')).toBe('bar') | |
}) | |
it('constructor overloading - with Request as the 1st param', () => { | |
const request = new RequestPolyfill( | |
new RequestPolyfill('http://example.com', {}), | |
{ | |
headers: { foo: 'bar' }, | |
} | |
) | |
expect(request.url).toBe('http://example.com') | |
expect(request.headers.get('foo')).toBe('bar') | |
}) | |
}) |
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
import { Headers } from 'headers-polyfill' | |
// Possible constructor overloading: | |
// | |
// 1. new Request(request.url, newRequestInit) | |
// 1. new Request(newUrl, { ...request, headers: newHeaders }) | |
// 1. new Request(newUrl, request) | |
// 2. new Request(request, { headers: newHeaders }) | |
class RequestPolyfill { | |
public readonly url: string | |
public readonly headers: Headers | |
constructor(url: string | RequestPolyfill, options?: RequestInit) { | |
if (url instanceof RequestPolyfill) { | |
this.url = url.url | |
this.headers = new Headers(options?.headers as Record<string, string>) | |
} else { | |
this.url = url | |
this.headers = new Headers(options?.headers as Record<string, string>) | |
} | |
} | |
toString(): string { | |
return 'URL = ' + this.url + '; headers = ' + this.headers | |
} | |
} | |
export default RequestPolyfill |
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
import { | |
convertNjsResponseToCloudflare, | |
buildAndReturnNjsResponse, | |
} from './response-adaptor' | |
import ResponsePolyfill from './response-polyfill' | |
describe('response-adaptor', () => { | |
it('convertNjsResponseToCloudflare function', async () => { | |
const cloudflareResponse: ResponsePolyfill = | |
await convertNjsResponseToCloudflare({ | |
headers: { foo: 'bar' }, | |
status: 200, | |
statusText: 'OK', | |
arrayBuffer: () => Promise.resolve('test body'), | |
} as unknown as NgxResponse) | |
expect(cloudflareResponse.headers.get('foo')).toBe('bar') | |
expect(cloudflareResponse.status).toBe(200) | |
expect(cloudflareResponse.statusText).toBe('OK') | |
expect(cloudflareResponse.body).toBe('test body') | |
}) | |
it('buildAndReturnNjsResponse function', async () => { | |
let mockStatus: undefined | number | |
let mockBody: undefined | NjsStringOrBuffer | |
const mockNgxRequest = { | |
headersOut: {}, | |
return: (status: number, body: NjsStringOrBuffer) => { | |
mockStatus = status | |
mockBody = body | |
}, | |
} as unknown as NginxHTTPRequest | |
buildAndReturnNjsResponse( | |
mockNgxRequest, | |
new ResponsePolyfill('test body', { | |
status: 200, | |
headers: { foo: 'bar' }, | |
}) | |
) | |
expect(mockNgxRequest.headersOut['foo']).toBe('bar') | |
expect(mockStatus).toBe(200) | |
expect(mockBody).toBe('test body') | |
}) | |
}) |
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
import ResponsePolyfill from './response-polyfill' | |
function copyHeadersToNgxRequest( | |
ngxRequest: NginxHTTPRequest, | |
headers: Record<string, string> | |
) { | |
// In NJS, response headers are stored in ngxRequest.headersOut | |
// Copying headers here (ngxRequest.headersOut are immutable): | |
Object.entries(headers).forEach(([key, value]) => { | |
ngxRequest.headersOut[key] = value | |
}) | |
} | |
/** | |
* @param ngxRequest - NJS request | |
* @param response - Cloudflare response | |
* @returns void | |
*/ | |
function buildAndReturnNjsResponse( | |
ngxRequest: NginxHTTPRequest, | |
response: ResponsePolyfill | |
): void { | |
if (!response.status) { | |
throw new Error('Cloudflare response has missing status.') | |
} | |
copyHeadersToNgxRequest(ngxRequest, Object.fromEntries(response.headers)) | |
if ([301, 302, 303, 307, 308].includes(response.status)) { | |
delete ngxRequest.headersOut["Location"] | |
ngxRequest.return(response.status, response.headers.get("Location") as NjsStringOrBuffer) | |
} else { | |
ngxRequest.return(response.status, response.body as NjsStringOrBuffer) | |
} | |
} | |
async function convertNjsResponseToCloudflare( | |
response: NgxResponse | |
): Promise<ResponsePolyfill> { | |
const buffer = await response.arrayBuffer() | |
return new ResponsePolyfill(buffer, { | |
...response, | |
headers: { ...response.headers } as unknown as Record<string, string>, // TODO find a better way | |
}) | |
} | |
export { buildAndReturnNjsResponse, convertNjsResponseToCloudflare } |
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
import ResponsePolyfill from './response-polyfill' | |
describe('Response', () => { | |
it('constructor should fill public fields', () => { | |
const response = new ResponsePolyfill('test body', { | |
status: 200, | |
statusText: 'OK', | |
headers: { foo: 'bar' }, | |
}) | |
expect(response.body).toBe('test body') | |
expect(response.status).toBe(200) | |
expect(response.statusText).toBe('OK') | |
expect(response.headers.get('foo')).toBe('bar') | |
}) | |
}) |
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
import { Headers } from 'headers-polyfill' | |
class ResponsePolyfill implements Response { | |
public readonly headers: Headers | |
public readonly status: number | |
public readonly statusText: string | |
public readonly body: any | |
readonly ok: boolean | |
readonly trailer!: Promise<Headers> | |
readonly type!: ResponseType | |
readonly url!: string | |
readonly bodyUsed!: boolean | |
readonly redirected!: boolean | |
constructor(body?: BodyInit, init?: ResponseInit) { | |
this.headers = init?.headers ? new Headers(init.headers) : new Headers() | |
this.status = init?.status ?? 200 | |
this.ok = this.status >= 200 && this.status <= 299 | |
this.statusText = init?.statusText ?? '' | |
this.body = body | |
} | |
toString(): string { | |
return JSON.stringify(this) | |
} | |
arrayBuffer(): Promise<ArrayBuffer> { | |
throw new Error('Method not implemented.') | |
} | |
blob(): Promise<Blob> { | |
throw new Error('Method not implemented.') | |
} | |
clone(): Response { | |
throw new Error('Method not implemented.') | |
} | |
formData(): Promise<FormData> { | |
throw new Error('Method not implemented.') | |
} | |
json(): Promise<any> { | |
throw new Error('Method not implemented.') | |
} | |
text(): Promise<string> { | |
throw new Error('Method not implemented.') | |
} | |
} | |
export default ResponsePolyfill |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment