-
-
Notifications
You must be signed in to change notification settings - Fork 13
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
Split broccoli-middleware into two different middlewares #21
Conversation
I think this is going to help with some problems I've been having with the errors page. |
var watcherMiddleware = require('./watcher-middleware'); | ||
|
||
module.exports = { | ||
'watcherServerMiddleware': watcherServerMiddleware, |
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.
What's the motivation for retaining a combined middleware?
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.
It was more for maintaining backward compatibility. If we need to do a point release of ember-cli that is using the combined middleware and requires a bug fix in broccoli-middleware.
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.
Seems reasonable. Personally I would not include it in master
but instead create a minor version branch if a patch version became necessary. Not sure what the maintainer preference here is though. Seems like a decision for @joliss @rwjblue @stefanpenner
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.
Yeah I am fine any path. I don't have much experience on how it works in ember-cli.
}; | ||
request.headers['x-broccoli'] = broccoliInfo; | ||
|
||
var updatedFileName = setResponseHeaders(request, response, 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.
A couple of points here:
- I'm surprised to see so much of the response header set here. eg
setResponseHeaders
sets things likecontent-length
which seems to be a concern of theserveAssetMiddleware
not thewatcherMiddleware
. In this comment the only headers set up in the watcher middleware were thex-broccoli
request header for tracking the output path and theCache-Control
response header as that's a pretty reasonable header to set as a default irrespective of how the request is actually fulfilled.
Why are file-specific headers set in this middleware rather than the asset serving middleware?
- It seems weird for
setResponseHeaders
to not be a middleware but to still acceptnext
. I'm not suggesting that it should be a middleware: I'm worried that it's currently hiding some of the control flow. Is there some way we can split up this 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.
-
I am not adding to set any extra headers. These headers were also set previously. I think the comment was more of an example to explain the workflow. The reason file specific headers are set in the middleware is because we want to provide these as the default implementation and let anyone override it downstream if needed rather than anyone who wants to insert itself before the asset serving middleware to almost copy over those header settings.
-
I thought about splitting it but it wasn't very clean since it has dependencies on
stat
object and other things. I could do a small refactor such that it doesn't takenext
.
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 adding to set any extra headers. These headers were also set previously. I think the comment was more of an example to explain the workflow. The reason file specific headers are set in the middleware is because we want to provide these as the default implementation and let anyone override it downstream if needed rather than anyone who wants to insert itself before the asset serving middleware to almost copy over those header settings.
Ah sorry I did not make my point very clearly. I totally agree with all the headers: what I'm surprised about is that they're set in the watcher middleware and not the serve asset middleware.
I thought about splitting it but it wasn't very clean since it has dependencies on stat object and other things. I could do a small refactor such that it doesn't take next.
Seems reasonable although this is relatively minor to me in comparison to figuring out why we're doing this in this middleware rather than serve 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.
Ah sorry I did not make my point very clearly. I totally agree with all the headers: what I'm surprised about is that they're set in the watcher middleware and not the serve asset middleware.
I would disagree here since there is a usecase where this will not work as expected (if we have serve asset middleware handler those headers). The purpose of the watcher middleware was to set all the generic headers and the serve file asset middleware to actually just serve from disk. The reason being: Imagine usecase where someone wants to insert a middleware between the watcher and serve asset middleware in order to serve assets themselves. If the file headers are set by the serve asset only then the custom header inserted in between will need to duplicate the behavior of setting headers. IMO that is undesirable. The custom middleware or any downstream middleware should only overwrite the headers.
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.
Seems reasonable although this is relatively minor to me in comparison to figuring out why we're doing this in this middleware rather than serve assets.
I went ahead and refactored the code a bit so that next
is not leaked into the utility 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.
The purpose of the watcher middleware was to set all the generic headers and the serve file asset middleware to actually just serve from disk
I would have imagined the purpose of the watcher middleware to just be the error handling and to block the request until the watcher has finished.
The reason being: Imagine usecase where someone wants to insert a middleware between the watcher and serve asset middleware in order to serve assets themselves. If the file headers are set by the serve asset only then the custom header inserted in between will need to duplicate the behavior of setting headers.
So a couple of things:
-
If someone else is serving assets presumably there's something different about them, which is why they can't just fall back to the asset serving middleware. This means that at a minimum headers like Content-Length and Last-Modified are likely to be wrong.
-
It seems weird that they can only sometimes rely on the default headers. The headers set in
setResponseHeaders
depend on being able to actually find the file on disk. Someone who wants to write a middleware between the watcher and asset serving may or may not actually be serving assets from disk or not. The reason this isn't a problem with the asset serving middleware is that serving files found on disk takes precedence over the proxy, but for someone who wanted to insert their middleware before asset serving, they would want their middleware to take precedence.
if the file headers are set by the serve asset only then the custom header inserted in between will need to duplicate the behavior of setting headers.
Maybe we could move that logic into lib/utils/serve-asset.js
and make it public? This way it would be still be pretty easy to write a middleware that wanted to serve a static asset just like serve assets, but whose url -> file on disk algorithm was different (eg as with fastboot which wants /
-> /index.fastboot.html
instead of /
-> /index.html
or something similar)
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.
Sure we can do that. I'll update this PR.
if (updatedFileName) { | ||
// set the request header file name to be the updated one (could be a null if asset is not found) | ||
broccoliInfo.filename = updatedFileName; | ||
request.headers['x-broccoli'] = broccoliInfo; |
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.
In what ways can the file name be updated? This code looks general and the comment makes it sound general, but from what I could tell of setResponseHeaders
the only change is that index.html
may be appended. Did I miss something?
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.
It can be updated when index.html
is appended. Nope you didn't miss anything.
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.
Okay cool, do we need this in the watcher middleware? This is related to my point above, where it feels to me like this stuff is the concern of the asset serving middleware rather than the watcher middleware.
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.
Please see my response above.
}); | ||
} else { | ||
// bypass this middleware if the filename is not defined | ||
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.
What's the use case for noop
ing here?
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.
In ember-cli
the last middleware in the chain is the proxy-server-middleware which is invoked when a file is not found in disk or if index.html and autoIndex is not true. Basically the case when someone uses ember serve --proxy http://localhost:3000
for example. In this case, when we know the file is not found we want to bypass this middleware (ie not even serve it) and let the proxy server handle the request.
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.
👍
2d73225
to
6e055a5
Compare
@hjdivad Updated per your suggestion. Please let me know if I misunderstood anything. |
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 have a number of very minor nits but nothing substantial. I think it would be fine to fix them before or after merge. High level everything looks great.
Thanks @kratiahuja 👍
var serveAsset = require('./utils/serve-asset'); | ||
var setResponseHeaders = require('./utils/response-header').setResponseHeaders; | ||
|
||
module.exports = function serveAssetsMiddleware() { |
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 think serveAssetsMiddleware
needs to be a function that returns a middleware. We can just export the middleware directly.
The reason the previous implementation had a function that returned a middleware was to close over the watcher
argument, but that's not necessary here.
ie we can just module.exports = function(request, response, 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.
I was just following the format of other middlewares for consistency. Fixed to just not have the closure 😄
var broccoliInfo = req.headers['x-broccoli']; | ||
if (broccoliInfo) { | ||
broccoliInfo.filename = filename; | ||
req.headers['x-broccoli'] = broccoliInfo; |
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 line seems to be a no-op; if the above conditional matches then req.headers['x-broccoli'] == broccoliInfo
already
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.
Sorry about that. Fixed.
var url = require('url'); | ||
var path = require('path'); | ||
|
||
//var setResponseHeaders = require('./utils/response-header'); |
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.
dead code
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.
Fixed.
@@ -111,11 +110,10 @@ describe('broccoli-middleware', function() { | |||
}); | |||
}); | |||
|
|||
it('responds with index.html if request is a directory and autoIndex is set to false', function() { | |||
it('responds with index.html if request is a directory and autoIndex is set to true', 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.
👍
Always good to make tests tell the truth 😄
|
||
|
||
server = new TestHTTPServer(middleware); | ||
server.addMiddleware(wrapperMiddleware); |
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.
What does the wrapper achieve? Can't we just server.addMiddleware(serveAssetMiddleFn)
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 wrapperMiddleware creates a closure to allow doing assertions. I could otherwise just create a another middleware to do the assertions after serveAssetMiddleware
is finished executing.
|
||
server = new TestHTTPServer(middleware); | ||
server.addMiddleware(wrapperMiddleware); | ||
|
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.
as above, unclear on what we get out of the wrapper here
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 wrapperMiddleware creates a closure to allow doing assertions. I could otherwise just create a another middleware to do the assertions after serveAssetMiddleware is finished executing.
var serveMiddleware = serveAssetMiddleware(); | ||
|
||
server = new TestHTTPServer(middleware); | ||
server.addMiddleware(serveMiddleware); |
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.
as above, unsure about the wrapper
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 wrapperMiddleware creates a closure to allow doing assertions. I could otherwise just create a another middleware to do the assertions after serveAssetMiddleware is finished executing.
}) | ||
}; | ||
|
||
server = new TestHTTPServer(wrapperMiddleware); |
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.
what's the point of the wrapper here? can't we use middleware
directly?
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 wrapperMiddleware creates a closure to allow doing assertions. I could otherwise just create a another middleware to do the assertions after serveAssetMiddleware is finished executing.
Implementation of ember-cli RFC # 80.
6e055a5
to
3e12781
Compare
@hjdivad Thanks for your feedback. I apologize for the delay in updating the PR. I believe I have addressed the nitpicks too with the latest push. Could you please take another quick look? 😄 If this is good, could we please merge it in (only |
Looks great, thanks @kratiahuja! |
@hjdivad @stefanpenner @rwjblue need another favor. Can we do a release of |
releasing now |
should be released, let me know if there is an issue. |
Thank you @stefanpenner ! I'll let you know if I hit any issues. |
Split serving assets into two different in-repo addons Implementation of [RFC # 80 of ember-cli](ember-cli/rfcs#80). This PR primarily does the following: - [x] Introduces `ember-cli:broccoli:watcher` addon that is responsible for setting the response headers for every asset request being served. - [x] Introduces `ember-cli:broccoli:serve-files` addon that is responsible for serving the assets - [x] Removes `serve-files` addon since the above two addon do the same work - [x] Release and update `broccoli-middleware` version after [PR](ember-cli/broccoli-middleware#21) is merged. - [x] Fix broken tests here after the `broccoli-middleware` version is updated. - [x] Update `ember-cli-inject-livereload` from `serve-files` to `ember-cli:broccoli:serve-files` [here](ember-cli/ember-cli-inject-live-reload#39) **Note**: `broccoli-middleware` output format is now changed but to maintain backward compatibility it still has the original one middleware that does both setting response headers and serving assets. TODO: - [ ] Release a major version of `ember-cli-inject-live-reload` that is compatible with the first release of ember-cli containing this PR. - [ ] Update change log?
Implementation of ember-cli RFC # 80. This PR is only the
broccoli-middleware
side of changes.