-
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
[RFC] Introduce express middleware extension point #2434
Conversation
ee92c41
to
2e58dc3
Compare
2dac020
to
b71eedb
Compare
2e58dc3
to
8dc57b3
Compare
cc5ace0
to
9874848
Compare
I am not sure if it's a good idea to re-introduce middleware phases in LB4. From the comments I seen in the past, many LB3 users are not "getting it", they don't understand why should they use middleware phases instead of vanilla express style of registration. Do we still want to purpose Sequence as the tool for orchestrating request handling pipeline? I think we should focus more on Sequence and treat compatibility with Express middleware as a short-term workaround only. As I see it, there are principally four middleware phases:
In LoopBack 3, the primary motivation for introducing middleware phases was to ensure correct order of invocation of these different middleware types. IIRC, the concept of finer-grained phases for request pre-processing was a kind of a nice side effect. In LoopBack 4, there is already infrastructure in place to enforce these four phases. What we are missing is API that would allow app developers to register their own request pre-processing middleware. As I see understand the proposal in your PR, it is splitting the request pre-processing phase into multiple finer-grained sub-phases. While I see how that can be useful, I am not convinced that the benefits justify the additional complexity (both for our users and the framework codebase). |
In fact, I would like to refactor this PR into a separate module such as
It's my intent to create an For LoopBack itself, I personally don't think the sequence of actions is good enough to take over the Express middleware pipeline and we should keep the door open for developers to use existing middleware. |
9874848
to
9200f41
Compare
8dc57b3
to
2366aa4
Compare
eb4c08e
to
2daf3f1
Compare
I refactored the code into |
I would like to port |
38b11a4
to
e9cc630
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.
This patch is adding a new package. Have you followed all steps in our
checklist? DEVELOPING.md#adding-a-new-package
How do you envision error handling for errors reported from Express middleware? At the moment, LB4 pretty much guarantees that all errors will be routed to reject
sequence action. IIUC, your proposal is changing that - some errors are handled by reject
, errors from middleware is handled by the non-customizable final error handler in the RestServer.
On a similar topic: at the moment, it's easy to observe when request handling started and finished, because everything happens inside sequence. For example, to log duration of a request (including failed requests!), one can write similar sequence:
export class MySequence implements SequenceHandler {
// constructor with @inject decorators
async handle(context: RequestContext) {
const {request, response} = context;
const start = process.hrtime();
try {
const route = this.findRoute(request);
const args = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
} catch (error) {
this.reject(context, error);
}
const delta = process.hrtime(start);
console.log(`Handled "${request.method} ${request.url}" in ${delta[0]} seconds.`);
}
}
This won't be possible with your new extension in place.
IMO, we should move middleware invocation into a new sequence action.
export class MySequence implements SequenceHandler {
// constructor with @inject decorators
async handle(context: RequestContext) {
const {request, response} = context;
const start = process.hrtime();
try {
this.executeMiddleware();
const route = this.findRoute(request);
const args = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
} catch (error) {
this.reject(context, error);
}
const delta = process.hrtime(start);
console.log(`Handled "${request.method} ${request.url}" in ${delta[0]} seconds.`);
}
}
Anyhow. I think the most important task now is to find a different way how to extend @loopback/rest
with support for custom middleware, an extension point that will allow LB4 developers to experiment with different approaches for middleware registration.
|
||
```ts | ||
const expressCtx = new ExpressContext(); | ||
expressCtx.middleware(cors(), {phase: 'cors'}); |
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.
Do you expect users to call middleware
with a single argument only? If not, then I think the following API may be more ergonomic:
expressCtx.middleware({
handler: cors(),
phase: 'cors',
// path, etc.
});
packages/rest/src/rest.server.ts
Outdated
@@ -218,6 +219,12 @@ export class RestServer extends Context implements Server, HttpServerLike { | |||
this._applyExpressSettings(); | |||
this._requestHandler = this._expressApp; | |||
|
|||
const expressCtx = new ExpressContext(this); | |||
expressCtx.setMiddlewareRegistryOptions({ |
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 comfortable forcing Express context on all LoopBack4 users.
Can you find a way how to make this extension an optional addition please?
this._expressApp.get(p, (req, res) => | ||
this._serveOpenApiSpec(req, res, mapping[p]), | ||
expressCtx.middleware( | ||
(req, res) => this._serveOpenApiSpec(req, res, mapping[p]), |
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 that by now, our REST layer should be providing enough features to allow us to implement _serverOpenApiSpec
routes as regular LB4 routes with x-visibility: undocumented
. Instead of relying on your new extension, can we rework this part to use existing LB4 means?
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.
Ah, it looks like it was an intentional decision to implement these routes at REST API layer, bypassing the sequence.
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.
Uh oh, this is even more complex than I thought. It turns out that OpenAPI endpoints like /openapi.json
do not honor basePath
configuration set at server level.
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.
You can see the RouteEntry-based implementation of /openapi.json
here: 2b5be22
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.
Uh oh, this is even more complex than I thought. It turns out that OpenAPI endpoints like
/openapi.json
do not honorbasePath
configuration set at server level.
This problem is already tracked here: #2329
this._expressApp.get(explorerPaths, (req, res, next) => | ||
this._redirectToSwaggerUI(req, res, next), | ||
expressCtx.middleware( | ||
(req, res, next) => this._redirectToSwaggerUI(req, res, next), |
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 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.
2beb2a0
to
cb12520
Compare
cb12520
to
69bec51
Compare
89c03be
to
ab241f6
Compare
ab241f6
to
680838e
Compare
680838e
to
969be21
Compare
969be21
to
6587a8a
Compare
6587a8a
to
537f21b
Compare
6751ef0
to
81626cc
Compare
81626cc
to
fe54778
Compare
0ce50d3
to
7360f76
Compare
Add @loopback/express-middleware as a new package
7360f76
to
36233c8
Compare
Close to favor #5118 |
This PR introduces an extension point for express middleware so that they can be added and sorted by phase to force a hierarchical router.
Doc: https://github.com/strongloop/loopback-next/blob/express-middleware/packages/express-middleware/README.md
See also #1293
Checklist
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated