You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is there any way to receive the context inside a controller?
Goal is to access request data (headers, ip, etc.)
Thanks
Acceptance criteria
a section in Controllers.html explaining how to access request and response headers or body by using decorators like @inject(RestBindings.Http.REQUEST) public request: Request for different cases:
A request header shared by most (or all) endpoints and processed by an express middleware/LB4 sequence action. Example: Authorization or X-API-Key.
A request header shared by many endpoints but used internally in the endpoint implementation (an express route handler/LB4 controller method). Maybe come up with a somewhat realistic example of such header.
A response header set by an express middleware/LB4 sequence action. For example X-Rate-Limit information provided by a rate-limiting extension in all responses.
A response header set by the endpoint (an express route handler/LB4 controller method). An example: Location header included ind 201 Created responses.
Goal is to access request data (headers, ip, etc.)
Ideally, you should decouple the code building request data from your controllers to enable you to test the controllers in isolation (unit-test style).
Typically, request headers are mapped to regular method parameters, OpenAPI spec supports in: header source.
I think it makes sense to expose the client IP via a custom binding that will allow you to receive the IP address in your controller methods via dependency injection (e.g. @inject(RestBindings.CLIENT_IP)). This is an addition to be implemented in @loopback/rest, would you like to contribute it yourself? We can help you along the way.
Is there any way to receive the context inside a controller?
Since you are interested in the Request object only, you can configure your controller to receive the request as follows:
@diegolacarta since you closed the issue, I assume my answer provided a solution for the problem you are facing. It makes me wonder how can we improve our documentation so that future LB4 users can more easily discover the information I have provided in my comment. Do you have any suggestions? I am assuming you have read our docs at https://loopback.io/doc/en/lb4/index.html but didn't find the answer. Where were you looking (which pages did you read)? Where would it makes most sense to document how to access the request from a controller method?
Yes, your answered was the perfect solution.
I'm struggling a bit with the documentation for v4, particularly since I'm
migrating a relatively complex express app into it. Things like accessing
headers, setting/clearing cookies, uploads, authentication, is not easy to
find (and I believe some of it doesn't exist yet).
I would expect to find your answer in here:
https://loopback.io/doc/en/lb4/Controllers.html but there's no mention to
how to access request/response from there.
I've gone through all pages in https://loopback.io/doc/en/lb4/index.html,
maybe I missed it.
Diego
On Mon, 22 Oct 2018 at 10:18 Miroslav Bajtoš ***@***.***> wrote:
@diegolacarta <https://github.com/diegolacarta> since you closed the
issue, I assume my answer provided a solution for the problem you are
facing. It makes me wonder how can we improve our documentation so that
future LB4 users can more easily discover the information I have provided
in my comment. Do you have any suggestions? I am assuming you have read our
docs at https://loopback.io/doc/en/lb4/index.html but didn't find the
answer. Where were you looking (which pages did you read)? Where would it
makes most sense to document how to access the request from a controller
method?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1881 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AGTtN2PznRJn21fEQDNl-REwFt8YIX_rks5unX9YgaJpZM4Xv7o5>
.
I'm struggling a bit with the documentation for v4, particularly since I'm
migrating a relatively complex express app into it. Things like accessing
headers, setting/clearing cookies, uploads, authentication, is not easy to
find (and I believe some of it doesn't exist yet).
Makes sense. Would a migration guide from Express to LB4 be helpful to you?
Could you please help me to define specific use cases to describe in our docs (e.g. Controllers.html), based on what you need in your app?
accessing headers
A request header shared by most (or all) endpoints and processed by an express middleware/LB4 sequence action. Example: Authorization or X-API-Key.
A request header shared by many endpoints but used internally in the endpoint implementation (an express route handler/LB4 controller method). Can we come up with a somewhat realistic example of such header?
A response header set by an express middleware/LB4 sequence action. For example X-Rate-Limit information provided by a rate-limiting extension in all responses.
A response header set by the endpoint (an express route handler/LB4 controller method). An example: Location header included ind 201 Created responses.
setting/clearing cookies
We haven't considered cookies yet. We have #1863 requesting support for sessions. What are looking for - HTTP sessions or generic support for cookies?
Authentication and authorization is in our backlog now. Could you please be more specific about your auth/auth requirements? Do you store user credentials locally (e.g. email + password) or rely on OAuth2, OpenID, SAML or another technology? What authorization style do you use - role-based, access-control lists, scopes, something else?
If you look back to the time you were reading our docs, would you find it helpful if the Controller docs was clearly spelling out which HTTP features are not implemented yet (cookies, file uploads), with pointers to the relevant GitHub issues?
I got here searching for a way to get the user IP. I hope @inject(RestBindings.CLIENT_IP) gets implemented soon. I can give a try to implement it in the coming few days. Any pointers?
I find "RestBindings" helpful in many things I could need in the future.
I am using PassportJS Basic Strategy based on the lb4 authentication example.
Another note is that I tried to get the request or the context inside an authentication provider file, but the request body was always not present in the request. Everything else was there (headers, connection, socket, ServerResponse,..) but not the body. For that reason, I moved my logic to a controller rather than the provider.
I don't want the method to be called first. I just want to include the body the same way the header is included in the req. Like the req is sent with the header, so why not the body too?
I have a question:
Ideally, you should decouple the code building request data from your controllers to enable you to test the controllers in isolation (unit-test style).
@bajtos What do you mean? If I am checking for the existence of something in the request body for example, I should not do this in the controller?
@diegolacarta sorry for a late response, I have too many GitHub notifications.
I got here searching for a way to get the user IP. I hope @inject(RestBindings.CLIENT_IP) gets implemented soon. I can give a try to implement it in the coming few days. Any pointers?
Here is the place where we are creating request-specific bindings:
Returned the error: Cannot start the application. ReferenceError: Request is not defined
I suspect this is a bug in TypeScript typings. One of the libraries we use in LoopBack is incorrectly assuming browser environment. To make TypeScript happy, we need to include dom library in tsconfig. As a result, Request and Response become globally defined types. Unfortunately they are browser types that don't work in Node.js! You need to import Request from @loopback/rest.
Another note is that I tried to get the request or the context inside an authentication provider file, but the request body was always not present in the request. Everything else was there (headers, connection, socket, ServerResponse,..) but not the body. For that reason, I moved my logic to a controller rather than the provider.
I don't want the method to be called first. I just want to include the body the same way the header is included in the req. Like the req is sent with the header, so why not the body too?
I am not entirely sure what is the cause of the behavior you are seeing. I think this may be caused by the fact that body must be parsed asynchronously and this happens only when parseParams sequence action is called. Providers are invoked before the sequence started, at the time when the request body was not parsed yet.
Can you try to use @inject.getter for the request body please?
Ideally, you should decouple the code building request data from your controllers to enable you to test the controllers in isolation (unit-test style).
What do you mean? If I am checking for the existence of something in the request body for example, I should not do this in the controller?
Your controller should describe the request body via OpenAPI spec and then receive the parsed (and validated!) body object as a parameter.
The decorator @requestBody allows you to provide your custom parameter spec if you don't want to use one inferred from a model (e.g. because there is no model for your parameter):
constUserSchema={// OpeAPI/JSON schema describing the payload};classMyController{
@put('/Users/{id}')asyncreplaceUser(
@param.path.string('id')id: string,
@requestBody({content: {'application/json': UserSchema,},})user: object,){// the implementation}}
In VSCode, Request is also recognized as a global.
This problem is caused by a problem in type definitions of supertest, where browser DOM objects are required even when running in Node.js :(
I found a neat solution how to fix this problem, see #2621
How to receive context in unit testing controllers?
import{AuthController}from'../../controllers/auth.controller';import{UserRepository}from'../../repositories';import{testdb}from'../../datasources/test.datasource';
export asyncfunctionauthController(): Promise<AuthController>{const userRepository =newUserRepository(testdb);returnnewAuthController(userRepository,// here should be context to access headers, etc);}
You can pass in context via the constructor for unit testing. The other option is to bind the controller to context and retrieve an instance via ctx.get(). For example:
@auxcoder As I explained in #1881 (comment), you shouldn't be injecting the entire context, that's an anti-pattern making unit tests difficult to write.
If your controller needs to access request headers, then you can inject the current request object only. Or even better, define bindings for the individual headers you are interested in.
Activity
hacksparrow commentedon Oct 19, 2018
@diegolacarta in a controller method you can access
this.req.bajtos commentedon Oct 19, 2018
Ideally, you should decouple the code building request data from your controllers to enable you to test the controllers in isolation (unit-test style).
Typically, request headers are mapped to regular method parameters, OpenAPI spec supports
in: headersource.I think it makes sense to expose the client IP via a custom binding that will allow you to receive the IP address in your controller methods via dependency injection (e.g.
@inject(RestBindings.CLIENT_IP)). This is an addition to be implemented in@loopback/rest, would you like to contribute it yourself? We can help you along the way.Since you are interested in the Request object only, you can configure your controller to receive the request as follows:
Accessing the context directly should be the last resort solution. You can receive the current context using
@inject.context().bajtos commentedon Oct 22, 2018
@diegolacarta since you closed the issue, I assume my answer provided a solution for the problem you are facing. It makes me wonder how can we improve our documentation so that future LB4 users can more easily discover the information I have provided in my comment. Do you have any suggestions? I am assuming you have read our docs at https://loopback.io/doc/en/lb4/index.html but didn't find the answer. Where were you looking (which pages did you read)? Where would it makes most sense to document how to access the request from a controller method?
diegolacarta commentedon Oct 22, 2018
bajtos commentedon Oct 26, 2018
I am reopening this issue. Let's improve our docs!
@diegolacarta
Makes sense. Would a migration guide from Express to LB4 be helpful to you?
Could you please help me to define specific use cases to describe in our docs (e.g.
Controllers.html), based on what you need in your app?A request header shared by most (or all) endpoints and processed by an express middleware/LB4 sequence action. Example:
AuthorizationorX-API-Key.A request header shared by many endpoints but used internally in the endpoint implementation (an express route handler/LB4 controller method). Can we come up with a somewhat realistic example of such header?
A response header set by an express middleware/LB4 sequence action. For example
X-Rate-Limitinformation provided by a rate-limiting extension in all responses.A response header set by the endpoint (an express route handler/LB4 controller method). An example:
Locationheader included ind201 Createdresponses.We haven't considered cookies yet. We have #1863 requesting support for sessions. What are looking for - HTTP sessions or generic support for cookies?
We have discussing file uploads here: #1863
Authentication and authorization is in our backlog now. Could you please be more specific about your auth/auth requirements? Do you store user credentials locally (e.g. email + password) or rely on OAuth2, OpenID, SAML or another technology? What authorization style do you use - role-based, access-control lists, scopes, something else?
If you look back to the time you were reading our docs, would you find it helpful if the Controller docs was clearly spelling out which HTTP features are not implemented yet (cookies, file uploads), with pointers to the relevant GitHub issues?
shadyanwar commentedon Jan 17, 2019
I got here searching for a way to get the user IP. I hope
@inject(RestBindings.CLIENT_IP)gets implemented soon. I can give a try to implement it in the coming few days. Any pointers?I find "RestBindings" helpful in many things I could need in the future.
By the way, the following:
Returned the error:
Cannot start the application. ReferenceError: Request is not definedNote: this error was not caught by TypeScript. It was returned only after
npm startreferencing the .js file not .ts.Had to go with:
I am using PassportJS Basic Strategy based on the lb4 authentication example.
Another note is that I tried to get the request or the context inside an authentication provider file, but the request body was always not present in the request. Everything else was there (headers, connection, socket, ServerResponse,..) but not the body. For that reason, I moved my logic to a controller rather than the provider.
I don't want the method to be called first. I just want to include the body the same way the header is included in the req. Like the req is sent with the header, so why not the body too?
I have a question:
@bajtos What do you mean? If I am checking for the existence of something in the request body for example, I should not do this in the controller?
bajtos commentedon Feb 8, 2019
@diegolacarta sorry for a late response, I have too many GitHub notifications.
Here is the place where we are creating request-specific bindings:
https://github.com/strongloop/loopback-next/blob/0e1b06f546aea97855556f985b39e50cd3e7956e/packages/rest/src/request-context.ts#L25-L37
I suspect this is a bug in TypeScript typings. One of the libraries we use in LoopBack is incorrectly assuming browser environment. To make TypeScript happy, we need to include
domlibrary intsconfig. As a result,RequestandResponsebecome globally defined types. Unfortunately they are browser types that don't work in Node.js! You need to importRequestfrom@loopback/rest.I am not entirely sure what is the cause of the behavior you are seeing. I think this may be caused by the fact that
bodymust be parsed asynchronously and this happens only whenparseParamssequence action is called. Providers are invoked before the sequence started, at the time when the request body was not parsed yet.Can you try to use
@inject.getterfor the request body please?Your controller should describe the request body via OpenAPI spec and then receive the parsed (and validated!) body object as a parameter.
See our Todo example:
https://github.com/sanadHaj/loopback-next/blob/2088fc1b3c64f9d8c3e10798d71a660c426efb7c/examples/todo-list/src/controllers/todo.controller.ts#L23-L33
That place does not provide any schema in
@requestBodydecorator because LB4 is able to infer the schema from model definition.The decorator
@requestBodyallows you to provide your custom parameter spec if you don't want to use one inferred from a model (e.g. because there is no model for your parameter):Further reading:
5 remaining items
[-]Receive context in controller[/-][+]Docs: How to receive context in controller[/+]bajtos commentedon Mar 21, 2019
I found a neat solution how to fix this problem, see #2621
auxcoder commentedon Sep 20, 2019
How to receive context in unit testing controllers?
raymondfeng commentedon Sep 20, 2019
You can pass in context via the constructor for unit testing. The other option is to bind the controller to context and retrieve an instance via
ctx.get(). For example:bajtos commentedon Sep 24, 2019
@auxcoder As I explained in #1881 (comment), you shouldn't be injecting the entire context, that's an anti-pattern making unit tests difficult to write.
If your controller needs to access request headers, then you can inject the current request object only. Or even better, define bindings for the individual headers you are interested in.
raymondfeng commentedon Sep 26, 2019
Reopening the issue to make sure docs are improved.