Skip to content

Commit

Permalink
Add a 'absoluteUrl' property to ctx.request
Browse files Browse the repository at this point in the history
This keeps on coming back, and maybe it is better to have a standard
convention for this after all.

This also adds a ctx.request.publicBaseUrl and
ctx.response.publicBaseUrl, but perhaps these should actually be called
ctx.request.origin and ctx.response.origin
  • Loading branch information
evert committed Apr 25, 2022
1 parent 3c64813 commit efa7cbf
Show file tree
Hide file tree
Showing 17 changed files with 165 additions and 74 deletions.
48 changes: 41 additions & 7 deletions src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ export default class Application extends EventEmitter {
});
wss.on('connection', async(ws, req) => {

const request = new NodeRequest(req);
const response = new MemoryResponse();
const request = new NodeRequest(req, this.publicBaseUrl);
const response = new MemoryResponse(this.publicBaseUrl);
const context = new Context(request, response);

context.webSocket = ws;
Expand Down Expand Up @@ -180,8 +180,8 @@ export default class Application extends EventEmitter {
// We don't have an existing Websocket server. Lets make one.
this.wss = new WebSocket.Server({ noServer: true });
this.wss.on('connection', async(ws, req) => {
const request = new NodeRequest(req);
const response = new MemoryResponse();
const request = new NodeRequest(req, this.publicBaseUrl);
const response = new MemoryResponse(this.publicBaseUrl);
const context = new Context(request, response);

context.webSocket = ws;
Expand Down Expand Up @@ -213,12 +213,12 @@ export default class Application extends EventEmitter {
let request: Request;

if (typeof arg1 === 'string') {
request = new MemoryRequest(arg1, path!, headers, body);
request = new MemoryRequest(arg1, path!, this.publicBaseUrl, headers, body);
} else {
request = arg1;
}

const context = new Context(request, new MemoryResponse());
const context = new Context(request, new MemoryResponse(this.publicBaseUrl));

try {
await this.handle(context);
Expand Down Expand Up @@ -247,9 +247,43 @@ export default class Application extends EventEmitter {
req: NodeHttpRequest,
res: NodeHttpResponse
): Context {
const context = new Context(new NodeRequest(req), new NodeResponse(res));
const context = new Context(
new NodeRequest(req, this.publicBaseUrl),
new NodeResponse(res, this.publicBaseUrl)
);

return context;
}

private _publicBaseUrl?: string;

/**
* The public base url of the application.
*
* This can be auto-detected, but will often be wrong when your server is
* running behind a reverse proxy or load balancer.
*
* To provide this, set the process.env.PUBLIC_URI property.
*/
get publicBaseUrl(): string {

if (this._publicBaseUrl) {
return this._publicBaseUrl;
}

if (process.env.PUBLIC_URI) {
return process.env.PUBLIC_URI;
}

const port = process.env.PORT ? +process.env.PORT : 80;
return 'http://localhost' + (port?':' + port : '');

}

set publicBaseUrl(baseUrl: string) {

this._publicBaseUrl = new URL(baseUrl).origin;

}

}
7 changes: 7 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ export class Context<ReqT = any, ResT = any> {

}

get absoluteUrl(): string {

return this.request.absoluteUrl;

}


/**
* HTTP method
*
Expand Down
4 changes: 2 additions & 2 deletions src/memory-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export class MemoryRequest<T> extends Request<T> {
*/
body: T;

constructor(method: string, requestTarget: string, headers?: HeadersInterface | HeadersObject, body: any = null) {
constructor(method: string, requestTarget: string, publicBaseUrl: string, headers?: HeadersInterface | HeadersObject, body: any = null, absoluteUrl?: string) {

super(method, requestTarget);
super(method, requestTarget, publicBaseUrl);
if (headers?.get !== undefined) {
this.headers = headers as HeadersInterface;
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/memory-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { Response, Body } from './response';

export class MemoryResponse<T = Body> extends Response<T> {

constructor() {
constructor(publicBaseUrl: string) {

super();
super(publicBaseUrl);
this.headers = new Headers();
this.status = 200;
(this.body as any) = null;
Expand Down
4 changes: 2 additions & 2 deletions src/node/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ export class NodeRequest<T> extends Request<T> {
*/
private inner: NodeHttpRequest;

constructor(inner: NodeHttpRequest) {
constructor(inner: NodeHttpRequest, publicBaseUrl: string) {

super(inner.method!, inner.url!);
super(inner.method!, inner.url!, publicBaseUrl);
this.inner = inner;
// @ts-expect-error ignoring that headers might be undefined
this.headers = new Headers(this.inner.headers);
Expand Down
14 changes: 11 additions & 3 deletions src/node/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class NodeResponse<T> implements Response<T> {
private bodyValue!: T;
private explicitStatus: boolean;

constructor(inner: NodeHttpResponse) {
constructor(inner: NodeHttpResponse, publicBaseUrl: string) {

// The default response status is 404.
this.inner = inner;
Expand All @@ -27,6 +27,7 @@ export class NodeResponse<T> implements Response<T> {
this.body = null;
this.status = 404;
this.explicitStatus = false;
this.publicBaseUrl = publicBaseUrl;

}

Expand Down Expand Up @@ -149,8 +150,8 @@ export class NodeResponse<T> implements Response<T> {
}

const pushCtx = new Context(
new MemoryRequest('GET', '|||DELIBERATELY_INVALID|||'),
new MemoryResponse()
new MemoryRequest('GET', '|||DELIBERATELY_INVALID|||', this.publicBaseUrl),
new MemoryResponse(this.publicBaseUrl)
);

await invokeMiddlewares(pushCtx, [callback]);
Expand Down Expand Up @@ -232,6 +233,13 @@ export class NodeResponse<T> implements Response<T> {
this.headers.set('Location', addr);
}

/**
* Public base URL
*
* This will be used to determine the absoluteUrl
*/
readonly publicBaseUrl: string;

}

export default NodeResponse;
22 changes: 21 additions & 1 deletion src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ export type Encoding = 'utf-8' | 'ascii' | 'hex';
*/
export abstract class Request<T = unknown> {

constructor(method: string, requestTarget: string) {
constructor(method: string, requestTarget: string, publicBaseUrl: string) {
this.method = method;
this.requestTarget = requestTarget;
this.headers = new Headers();
this.publicBaseUrl = publicBaseUrl;
}

/**
Expand Down Expand Up @@ -210,6 +211,25 @@ export abstract class Request<T = unknown> {

}

/**
* Returns the absolute url for this request.
*
* This may not always be correct, because it's based on a best guess.
* If you have a reverse proxy in front of your curveball server, you may
* need to provide a 'publicBaseUrl' argument when constructing the server.
*/
get absoluteUrl(): string {

return this.publicBaseUrl + this.requestTarget;

}

/**
* Public base URL
*
* This will be used to determine the absoluteUrl
*/
readonly publicBaseUrl: string;
}

export default Request;
9 changes: 8 additions & 1 deletion src/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ export type Body =
*/
export abstract class Response<T = Body> {

constructor() {
constructor(publicBaseUrl: string) {

this.headers = new Headers();
this.status = 200;
this.publicBaseUrl = publicBaseUrl;

}

Expand Down Expand Up @@ -134,6 +135,12 @@ export abstract class Response<T = Body> {
this.headers.set('Location', addr);
}

/**
* Public base URL
*
* This will be used to determine the absoluteUrl
*/
readonly publicBaseUrl: string;
}

export default Response;
1 change: 1 addition & 0 deletions test/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ describe('Application', () => {
const request = new MemoryRequest(
'POST',
'/',
application.publicBaseUrl,
{ foo: 'bar' },
'request-body'
);
Expand Down
16 changes: 8 additions & 8 deletions test/conditional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('conditionals', () => {
for (const [status, method, header] of tests) {
it(`should return ${status} when doing ${method} with If-Match: ${header}`, () => {

const request = new MemoryRequest(method, '/foo', { 'If-Match': header });
const request = new MemoryRequest(method, '/foo', 'http://localhost', { 'If-Match': header });
expect(conditionalCheck(request, null, '"a"')).to.eql(status);

});
Expand All @@ -38,7 +38,7 @@ describe('conditionals', () => {
for (const [status, method, header] of tests) {
it(`should return ${status} when doing ${method} with If-Match: ${header}`, () => {

const request = new MemoryRequest(method, '/foo', { 'If-Match': header });
const request = new MemoryRequest(method, '/foo', 'http://localhost', { 'If-Match': header });
expect(conditionalCheck(request, null, null)).to.eql(status);

});
Expand All @@ -63,7 +63,7 @@ describe('conditionals', () => {
for (const [status, method, header] of tests) {
it(`should return ${status} when doing ${method} with If-None-Match: ${header}`, () => {

const request = new MemoryRequest(method, '/foo', { 'If-None-Match': header });
const request = new MemoryRequest(method, '/foo', 'http://localhost', { 'If-None-Match': header });
expect(conditionalCheck(request, null, '"a"')).to.eql(status);

});
Expand All @@ -83,7 +83,7 @@ describe('conditionals', () => {
for (const [status, method, header] of tests) {
it(`should return ${status} when doing ${method} with If-None-Match: ${header}`, () => {

const request = new MemoryRequest(method, '/foo', { 'If-None-Match': header });
const request = new MemoryRequest(method, '/foo', 'http://localhost', { 'If-None-Match': header });
expect(conditionalCheck(request, null, null)).to.eql(status);

});
Expand All @@ -103,7 +103,7 @@ describe('conditionals', () => {
for (const [status, method, headerDate] of tests) {
it(`should return ${status} when doing ${method} with If-Modified-Since: ${headerDate}`, () => {

const request = new MemoryRequest(method, '/foo', { 'If-Modified-Since': headerDate });
const request = new MemoryRequest(method, '/foo', 'http://localhost', { 'If-Modified-Since': headerDate });
expect(conditionalCheck(request, new Date('2020-03-06 00:00:00'), null)).to.eql(status);

});
Expand All @@ -115,7 +115,7 @@ describe('conditionals', () => {

it('should return 200', () => {

const request = new MemoryRequest('GET', '/foo', { 'If-Modified-Since': 'Thu, 7 Mar 2019 14:49:00 GMT' });
const request = new MemoryRequest('GET', '/foo', 'http://localhost', { 'If-Modified-Since': 'Thu, 7 Mar 2019 14:49:00 GMT' });
expect(conditionalCheck(request, null, null)).to.eql(200);

});
Expand All @@ -134,7 +134,7 @@ describe('conditionals', () => {
for (const [status, method, headerDate] of tests) {
it(`should return ${status} when doing ${method} with If-Unmodified-Since: ${headerDate}`, () => {

const request = new MemoryRequest(method, '/foo', { 'If-Unmodified-Since': headerDate });
const request = new MemoryRequest(method, '/foo', 'http://localhost', { 'If-Unmodified-Since': headerDate });
expect(conditionalCheck(request, new Date('2020-03-06 00:00:00'), null)).to.eql(status);

});
Expand All @@ -146,7 +146,7 @@ describe('conditionals', () => {

it('should return 412', () => {

const request = new MemoryRequest('GET', '/foo', { 'If-Unmodified-Since': 'Thu, 7 Mar 2019 14:49:00 GMT' });
const request = new MemoryRequest('GET', '/foo', 'http://localhost', { 'If-Unmodified-Since': 'Thu, 7 Mar 2019 14:49:00 GMT' });
expect(conditionalCheck(request, null, null)).to.eql(412);

});
Expand Down
Loading

0 comments on commit efa7cbf

Please sign in to comment.