-
Notifications
You must be signed in to change notification settings - Fork 1.1k
[WIP] feat(rest): allow bypassing http response writing and custom content type #1753
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
Conversation
7af3258 to
d986a61
Compare
virkt25
left a comment
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.
LGTM 🚀
df2a5fa to
21c4f41
Compare
|
@virkt25 I refined the PR to check if a controller method returns the |
21c4f41 to
b4808b0
Compare
virkt25
left a comment
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 like the new bypass much better!! Should we add a section in the README showing how to just set the content type and let the writer write the response
docs/site/Controllers.md
Outdated
|
|
||
| ## Custom Response Writing | ||
|
|
||
| By default, LoopBack serialize the return value from the controller methods into |
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.
LoopBack will serialize
OR
LoopBack serializes the ...
|
|
||
| if (result instanceof Readable || typeof result.pipe === 'function') { | ||
| response.setHeader('Content-Type', 'application/octet-stream'); | ||
| function setContentType(defaultType: string = 'application/json') { |
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.
the variable name should be changed to type as it can be overridden.
…type We discover an issue to return html from a controller: See loopbackio/loopback4-example-shopping#16
b4808b0 to
deb2f05
Compare
|
I am rather unhappy about the design changes proposed in this pull request. So far, the framework was offering pretty strong guarantees to LoopBack users: every HTTP response was produced either by The solution proposed by this pull request, where the controller sends the response directly via Express I understand we need a quick workaround to enable our shopping-cart example to serve a home page (loopbackio/loopback4-example-shopping#16) and I am fine to add an undocumented fix as a short-term workaround. I would personally use the following check to determine whether the response have been already produced:
What's wrong with letting the controller method return For a longer term solution that will be documented, I'd like us to use the approach described in #436, #436 (comment) and #788 (comment), where the controller method can return an object describing the response to be produced, e.g. To move this pull request forward, I am asking for the following changes:
|
| export type Send = ( | ||
| response: Response, | ||
| result: OperationRetval, | ||
| contentType?: string, |
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 don't understand the purpose of this new parameter. I don't see it used anywhere in production code, only in unit tests.
send is typically invoked from a Sequence. There is usually the same sequence of actions implemented for all endpoints. IMO, the decision which content-type to send back should be made in individual controller methods (or routes), not in the Sequence.
I am proposing to revert this change.
| // result returned back from invoking controller method | ||
| result: OperationRetval, | ||
| // content type for the response | ||
| contentType?: string, |
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.
Similarly here, let's revert this change please.
| if (result instanceof Readable || typeof result.pipe === 'function') { | ||
| response.setHeader('Content-Type', 'application/octet-stream'); | ||
| function setContentType(defaultType: string = 'application/json') { | ||
| if (response.getHeader('Content-Type') == null) { |
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.
Personally, I prefer to revert this change.
Ideally, there should be only two ways how a content-type is determined:
-
The controller method/route returns data to be serialized to the response. The content-type is determined by the framework depending on the type of data (object vs. string vs. stream or buffer), request headers like
Acceptsand endpoint OpenAPI spec. Right now, we have a very limited version of this algorithm implemented in the currentwriteResultToResponsecode (objects are sent as JSON, streams as binary, strings as text). -
The controller wants to set an explicit content type, status code or change any other aspect of the HTTP response. It returns an object describing response properties (status code, headers, body) and the
sendaction applies these instructions to theresponseobject. This will be enabled by Configurable response types/formats #436 in the future.
The change proposed here adds a third mode that makes it difficult to reason about the HTTP responses when reading code, because different aspects of the response (content type vs. actual response body) are produced by different pieces of code.
- The controller (or a sequence action in general) explicitly sets the content type, but it still expects the framework to convert the value returned by the controller method (or route handler) into HTTP response body.
There is of course also the fourth mode being added here, I think it should be the only scope of this pull request. It goes against the two modes I described above, but I ok with it as a temporary workaround:
- The controller (or a sequence action in general) takes full control of the response sent: it sets the status code & headers and writes the response body. The
sendaction becomes a no-op.
|
|
||
| if ( | ||
| result instanceof Readable || | ||
| typeof (result && result.pipe) === 'function' |
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.
https://loopback.io/doc/en/contrib/style-guide.html#indentation-of-multi-line-expressions-in-if
const isStream = result instanceof Readable ||
typeof (result && result.pipe) === 'function';
if (isStream) {
// ...
}|
@bajtos Thank you for the feedback. A few clarifications:
I see use cases for both modes and would love to allow both flavors. We can treat the The issues above need to be addressed in the long run. To enable our shopping example, I'll create a different (much simpler) PR to allow:
|
|
Isn't it better to allow the controller to just set the response type / response header? ... How much work is it for us to implement pulling the controller metadata to see what the response header should be and set it based on that? |
|
Close it to favor #1760 |
We discover an issue to return html from a controller:
See loopbackio/loopback4-example-shopping#16
Checklist
npm testpasses on your machinepackages/cliwere updatedexamples/*were updated