-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
fix: fix static assets router blocking controller registration #1977
Conversation
cbdf1fa
to
936abe2
Compare
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.
I think the overall direction is okay, let's improve the implementation details please.
@@ -92,6 +127,19 @@ describe('RestApplication (integration)', () => { | |||
.expect('Hello'); | |||
}); | |||
|
|||
it('gives precedence to API controllers over static assets', async () => { |
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.
We already have a similar test in rest.server.integration.ts, see https://github.com/strongloop/loopback-next/blob/1bcdb5b69221501b3952bd70c0c78409b600d1ab/packages/rest/test/integration/rest.server.integration.ts#L150-L163
I think this test should be named differently: it('registers controllers defined later than static assets')
.
Also since all newly added tests in this file are related to RestServer functionality (they are not RestApplication specific), they should go to rest.server.integration.ts
. IMO, there is no need to duplicate RestServer tests in RestApplication integration test suite again, unless I am missing a good reason you found?
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.
... there is no need to duplicate RestServer tests in RestApplication integration test suite ...
If the implementation of app.static
is broken (for whatever reason) and we don't have tests to determine app.static
actually work, the problem will be found out only after trying to call app.static
in a project.
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.
True. On the other hand, app.static
is implemented as a simple wrapper delegating all work to this.restServer.static
. I agree it's important to have basic integration tests in place, but I don't think it's necessary to duplicate tests for all edge cases. Having too many tests is a maintenance burden.
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.
👍 removed them.
|
||
it('allows static assets to be mounted on the same path multiple times', async () => { | ||
const root = FIXTURES; | ||
const dirA = path.join(root, 'dir-a'); |
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.
Let's find a more descriptive directory name than dir-a
please.
I think it's time to reorganize the fixtures
directory, I am proposing the following structure:
fixtures
- cert.pem
- key.pem
- pfx.pfx
- assets
- index.html # moved from fixtures/index.html
- other-assets
- index.html
In the long term, I think it would be best to leverage TestSandbox
to create temporary asset directories for the tests, but I guess that's out of scope of this pull request.
8a95be2
to
b6ae45a
Compare
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.
Looks mostly good.
Please give @raymondfeng a chance to review too.
packages/rest/src/rest.server.ts
Outdated
@@ -614,7 +621,15 @@ export class RestServer extends Context implements Server, HttpServerLike { | |||
* @param options Options for serve-static | |||
*/ | |||
static(path: PathParams, rootDir: string, options?: ServeStaticOptions) { | |||
this.httpHandler.registerStaticAssets(path, rootDir, options); | |||
if (this._httpHandler) { | |||
this.httpHandler.registerStaticAssets(path, rootDir, options); |
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.
I find this a bit weird - you are checking for the existence of this._httpHandler
and then calling this.httpHandler.registerStaticAssets
. Shouldn't you use the same property in both places?
packages/rest/src/rest.server.ts
Outdated
|
||
this.staticAssets.forEach(assetEntry => { | ||
const {path, rootDir, options} = assetEntry; | ||
this.httpHandler.registerStaticAssets(path, rootDir, options); |
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.
I think you should call this._httpHandler.registerStaticAssets
here, instead of this.httpHandler....
?
packages/rest/src/rest.server.ts
Outdated
if (this._httpHandler) { | ||
this.httpHandler.registerStaticAssets(path, rootDir, options); | ||
} else { | ||
this.staticAssets.push({ |
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.
I think you should always create a new entry in this.staticAssets
, so that all static assets are preserved whenever the HTTP handler is rebuilt.
See #433 and
https://github.com/strongloop/loopback-next/blob/1bcdb5b69221501b3952bd70c0c78409b600d1ab/packages/rest/src/rest.server.ts#L262-L267
I think this is the simplest version of this method:
this.staticAssets.push(/*...*/);
delete this._httpHandler;
.expect(200, content); | ||
}); | ||
|
||
it('allows static assets to be mounted on the same path multiple times', async () => { |
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.
Let's improve this test name:
merges different static asset directories when mounted on the same path
|
||
let content = fs | ||
.readFileSync(path.join(root, 'index.html')) | ||
.toString('utf-8'); |
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.
This can be simplified, see https://nodejs.org/docs/latest-v8.x/api/fs.html#fs_fs_readfilesync_path_options
let content = fs.readFileSync(path.join(root, 'index.html'), {encoding: 'utf-8'});
It would be even better to extract a shared helper function to avoid repetition.
let content = await readFileFromDirectory(root, 'index.html');
// impl
const readFileAsync = util.promisify(fs.readFile);
function readFileFromDirectory(dirname: string, filename: string): Promise<string> {
return readFileAsync(path.join(dirname, filename), {encoding: 'utf-8'});
}
@hacksparrow Is this a regression? Please add a bit more context in the PR. |
@raymondfeng updated:
|
packages/rest/src/rest.server.ts
Outdated
|
||
this.staticAssets.forEach(assetEntry => { | ||
const {path, rootDir, options} = assetEntry; | ||
this._httpHandler.registerStaticAssets(path, rootDir, options); |
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.
Why don't we create an Express Router as the container so that registerStaticAssets
can just add a new entry to the router without rebuilding the handler?
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.
I am responding which some assumptions, please clarify if I misunderstood something.
We are ultimately using an Express Router - https://github.com/strongloop/loopback-next/blob/master/packages/rest/src/router/static-assets-route.ts#L35.
We have to register the static assets router with the Routing Table so that we have the ability to check the matching route for a request in the Routing Table, and return the static assets router, if none was matched.
If I misunderstood your statement, can you clarify what you mean by "container" and "rebuilding the handler".
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.
I'm saying we always create an instance of StaticAssetsRoute
as the place holder and use it to register static assets. This way we don't have to do the following:
this.staticAssets.push(/*...*/);
delete this._httpHandler; // I assume it's used to remove the cached _httpHandler to pick up new static assets
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.
I am still not getting it 😬.
One of the requirements of app.static()
is that it should be able to add new routes even after the app has started. Will it work with what you are suggesting?
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.
I am not very happy about the fact that we are effectively moving the registration of static assets out of the Routing table into StaticAssetsRoute maintained by RestServer, I feel it's creating inconsistency with the way how controllers are registered. Having said that, I hope this is mostly an internal implementation detail that will be easy to fix in the future if/when needed.
LGTM.
packages/rest/src/rest.server.ts
Outdated
/** | ||
* Options for handling static assets | ||
*/ | ||
export interface StaticAssetsEntry { |
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.
AFAICT, this interface is no longer used. Please remove.
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.
Removed.
69218f2
to
fd143cc
Compare
Fix static assets router blocking registration of controllers
fd143cc
to
1b725b3
Compare
This PR is to fix a regression, which prevented controllers and routes from being registered if
app.static()
was called before them.app.static()
was callingthis.httpHandler.registerStaticAssets(path, rootDir, options);
, which caused_setupHandlerIfNeeded()
to be called andthis._httpHandler
being set. Oncethis._httpHandler
is set, none of the bound controllers or routes were registered.The fix is to collect the details of
app.static()
in an arraythis.staticAssets
, and process them as part of_setupHandlerIfNeeded()
.Checklist
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated