Skip to content

Commit

Permalink
Fix urlFor to work with the mount plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Aug 13, 2022
1 parent f3574a2 commit bc4e696
Show file tree
Hide file tree
Showing 10 changed files with 37 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

* Added support for embedding mojo.js applications with `mountPlugin`.
* Added supprt for rewriting `ctx.req.path`.
* Added supprt relative paths with `ctx.req.basePath` to `ctx.urlFor`.
* Added `app:warmup` application hook.

### Bug Fixes
Expand Down
2 changes: 2 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,8 @@ class Context extends EventEmitter {
}

_urlForPath(path: string, isWebSocket: boolean, options: URLOptions): string {
path = this.req.basePath + path;

let query = '';
if (options.query !== undefined && Object.keys(options.query).length > 0) {
query = '?' + new Params(options.query).toString();
Expand Down
7 changes: 4 additions & 3 deletions src/plugins/mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ export default function mountPlugin(app: MojoApp, options: MountOptions): MojoRo
.any(`${path}/*mountPath`, async ctx => {
const {req, res, stash} = ctx;

// The embedded application does not use the path prefix
const originalBasePath = req.basePath;
const originalPath = req.path;
req.path = '/' + stash.mountPath;
const path = (req.path = '/' + stash.mountPath);
req.basePath = originalPath.substring(0, originalPath.length - path.length);

const mountContext = mountApp.newContext(req, res);
Object.assign(mountContext.stash, stash);
Expand All @@ -37,7 +38,7 @@ export default function mountPlugin(app: MojoApp, options: MountOptions): MojoRo
ctx.on('connection', ws => mountContext.handleUpgrade(ws));
}
} finally {
// Application specific information needs to be reset
req.basePath = originalBasePath;
req.path = originalPath;
res.bindContext(ctx);
}
Expand Down
1 change: 0 additions & 1 deletion src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ export class Router extends Route {
if (method === 'HEAD') method = 'GET';

const path = req.path;
if (path === null) return null;
const isWebSocket = ctx.isWebSocket;
ctx.log.trace(`${realMethod} "${path}"`);

Expand Down
8 changes: 6 additions & 2 deletions src/server/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ let requestId = 0;
* Server request class.
*/
export class ServerRequest extends Body {
/**
* Request base path.
*/
basePath = '';
/**
* Check if underlying socket was encrypted with TLS.
*/
Expand All @@ -28,7 +32,7 @@ export class ServerRequest extends Body {
/**
* Request path.
*/
path: string | null;
path = '/';
/**
* Peer address.
*/
Expand Down Expand Up @@ -59,7 +63,7 @@ export class ServerRequest extends Body {
const url = (this.url = options.url ?? null);

const pathMatch = (url ?? '').match(URL_RE);
this.path = pathMatch === null ? null : decodeURIComponentSafe(pathMatch[5]);
this.path = pathMatch === null ? '' : decodeURIComponentSafe(pathMatch[5]) ?? '';

this.isWebSocket = options.isWebSocket;
this.isSecure = options.isSecure;
Expand Down
2 changes: 1 addition & 1 deletion src/static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class Static {
async dispatch(ctx: MojoContext): Promise<boolean> {
const req = ctx.req;
const unsafePath = req.path;
if (unsafePath === null || !unsafePath.startsWith(this.prefix)) return false;
if (unsafePath.startsWith(this.prefix) === false) return false;

const method = req.method;
if (method !== 'GET' && method !== 'HEAD') return false;
Expand Down
3 changes: 3 additions & 0 deletions test/full-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ t.test('Full app', async t => {
(await ua.putOk('/bar/world')).statusIs(404);
(await ua.getOk('/foo/baz')).statusIs(200).bodyIs('Multiple levels');
(await ua.postOk('/foo/baz')).statusIs(404);

(await ua.getOk('/url?target=/foo')).statusIs(200).bodyIs('/foo');
(await ua.getOk('/url?target=websocket_echo')).statusIs(200).bodyLike(/ws:.+\d+\/echo.json/);
});

await t.test('View', async () => {
Expand Down
11 changes: 11 additions & 0 deletions test/mount-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ t.test('Mount app', async t => {
(await ua.getOk('/mount/full/FOO')).statusIs(200).bodyIs('Action works!');
(await ua.getOk('/mount/full/foo/baz')).statusIs(200).bodyIs('Multiple levels');
(await ua.getOk('mount/full/variants?device=tablet')).statusIs(200).bodyIs('Variant: Tablet!\n\n');

(await ua.getOk('mount/full/static/test.txt'))
.statusIs(200)
.headerExists('Content-Length')
.bodyLike(/Static file\r?\n/);
(await ua.getOk('/mount/full/does/not/exist')).statusIs(404);

(await ua.getOk('/mount/full/url?target=/foo')).statusIs(200).bodyIs('/mount/full/foo');
(await ua.getOk('/mount/full/url?target=websocket_echo'))
.statusIs(200)
.bodyLike(/ws:.+\d+\/mount\/full\/echo.json/);
});

await t.test('Full app (mounted again)', async () => {
Expand All @@ -35,6 +41,11 @@ t.test('Mount app', async t => {
.headerExists('Content-Length')
.bodyLike(/Static file\r?\n/);
(await ua.getOk('/mount/full/does/not/exist')).statusIs(404);

(await ua.getOk('/mount/full-two/url?target=/foo')).statusIs(200).bodyIs('/mount/full-two/foo');
(await ua.getOk('/mount/full-two/url?target=websocket_echo'))
.statusIs(200)
.bodyLike(/ws:.+\d+\/mount\/full-two\/echo.json/);
});

await t.test('Full app (extended)', async () => {
Expand Down
6 changes: 6 additions & 0 deletions test/support/js/full-app/controllers/foo.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ export default class FooController {
await ctx.render({view: 'variants', layout: 'variants', variant});
}

async url(ctx) {
const params = await ctx.params();
const target = params.get('target') ?? undefined;
await ctx.render({text: ctx.urlFor(target)});
}

async websocket(ctx) {
ctx.json(async ws => {
for await (const message of ws) {
Expand Down
4 changes: 3 additions & 1 deletion test/support/js/full-app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ app.get('/static').to(ctx => ctx.sendFile(ctx.home.child('public', 'test.txt')))

app.get('/default/view').to('foo#defaultView');

app.websocket('/echo.json').to('foo#websocket');
app.websocket('/echo.json').to('foo#websocket').name('websocket_echo');

app.get('/hooks').to('foo#hooks');

app.get('/session/login/:name').to('auth#login');
app.get('/session/logout').to('auth#logout');

app.get('/url').to('foo#url');

app.start();

0 comments on commit bc4e696

Please sign in to comment.