-
Notifications
You must be signed in to change notification settings - Fork 54
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
Added CacheControl headers #124
Conversation
@@ -5,6 +5,7 @@ | |||
require 'webmachine' | |||
require 'rspec' | |||
require 'logger' | |||
require 'webmachine/adapters/rack' |
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 seems to be an artifact of local testing, same for the changes to .gitignore and Gemfile. I'm in favor of the latter two, but that should be dealt with in a separate pull 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.
Artifact of trying to run a single spec file - spec_helper explicitly uses Webmachine::Adapters::Rack in a before(:suite). Having the require happen accidentally in a spec file seemed like an oversight
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 not run the entire suite? It takes less than 2 seconds for me. 😉
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.
Well, for whatever reason in my env, I get a fail. Also, single file takes <.1sec so...
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 point remains: spec_helper makes use of constants that are defined in that file - it should therefore require the file itself. As it stands, the whole suite works properly only because of how rspec loads files (which might change?) as opposed to how Ruby loads files.
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 agree with nyarly. We use the Rack adapter in spec_helper.rb, we should require it in spec_helper.rb. What confuses me is why we use the Rack adapter in spec_helper.rb.
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 right I didn't see that when I made the remark about this line. @samwgoldman it should be rather easy to move these adapter references away, think config.let(:options) { ... }
and using that in the adapter specs. Different topic/PR though.
Also agree that it's useful being able to run individual spec files. When running the whole suite, the overhead is not just from the rest of the suite, but also from booting Rake (in the case of bundle exec rake
). You also get to enjoy RSpec's CLI options. But again, different topic, and should be dealt with in another PR, for the sake of this PR's clarity.
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 just make it consistent and clean, however that works. I'm of little opinion on the matter. 😄
Very cool work, can't wait to give it a try on master! |
Any more comment? I think I've got clear through lines on everything. |
That was everything from me 🚢 🌟 |
directives[:extensions].merge!( name => parsed_value ) | ||
end | ||
end | ||
cache_control = self.new(directives) |
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 method is really big. can it be condensed into smaller responsibilities? (even with private methods)
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.
Since it's a class method, I'd lean towards composing into a separate class and have ::parse instantiate that... Seems a little heavyweight unless it's going to get other use parsing 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.
fair...
I guess I see two parts in this method. strings.each
and down might be better as its own method.
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.
heavyweight in what way? i'd be fine with a class too, but i think a method is fine also.
it might make it easier to reason about, and possibly even test, because you can test each part individually if you want, which can let you get more specific in some tests. I don't think a class is needed, but that'd be fine too for me. parsing isn't usually something that fits into one method.
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.
Heh - I'm used to the god method being a giant switch parser 😄
As I've been working with this today, I'm thinking that the best refactor would be to create per-directive objects, classed based on their values - parse, render, validate all on those. Which thought led to the "is there a HTTP header value parser" question.
looks good, +1 |
Could we get some more documentation on this? Install |
@@ -16,6 +16,10 @@ group :webservers do | |||
gem 'hatetepe', '~> 0.5.2' | |||
end | |||
|
|||
group :test do | |||
gem "fuubar" |
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 is this dependency?
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.
That's my local testing setup. I'll pull that out. Nice rspec formatter though - progress bar except for failing specs.
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.
Indeed, using it as well, it's sweet.
I've got yard installed - I'll need to add some docs for sure. Should yard be in the Gemfile, though? |
it's a development/developer dependency, i think it should be. |
As I'm working though this, I'm also wondering: is there not a ready-made HTTP Header parse/generate gem? If so, is it a good idea to be adding a bunch of code here rather than depending on that? |
i haven't used it, don't know how well it will apply to what we're doing here, but i've seen a few projects use https://github.com/tmm1/http_parser.rb |
Will parse entire HTTP messages, but not the individual header fields, i.e. you'll get a hash like |
might be useful to use in |
Yeah - I'm thinking the value parsing - maybe there's not that many HTTP headers with complex values? Authorization comes to mind as needing parsing. |
Doc stats at branch point: 87%. This PR definitely hurts that - working on that now. |
Back up to 87%. I believe I've addressed all comments Ready to merge? Or did I miss something? |
# | ||
#@return [true|false] | ||
def valid? | ||
validate! |
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.
is there any reason to not implement validate!
as a private method and return true/false from there?
seems strange to implement valid?
on top of a rescue from a method that could return true/false instead.
is there a use case for the raise(validate!) method?
@nyarly looks good to me. |
directives[:extensions].merge!( name => parsed_value ) | ||
end | ||
end | ||
return self.new(directives) |
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 this method actually bigger, but i won't hold that against you :D
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 I added to it :)
should we be worried about the failure on jruby? |
# ) | ||
def initialize(hash=nil) | ||
@directives = {} | ||
unless hash.nil? |
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.
hash = {}
. update_from_hash(hash)
. it'd be a no-op and remove the special-casing 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.
Any time you use an object (including {}) as a default value for an argument, the object is instantiated on every call to that method. So using nil here reduces allocation somewhat.
I also find that uniformly using nil as a default value has some benefits in terms passing arguments down the chain - you don't have to remember what the default value is if you want to mirror arguments down.
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 see that argument as having a strong basis, but happy to subside on this one.
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 see any reason to guard this, one could simply call update_from_hash(hash || {})
.
Cursory look makes it seem like the jruby and 1.9.3 fails have to do with Adapters - does Reel work in those environments? |
Have you updated your bundle or local branch? Those both pass for me. Sean Cribbs
|
Was responding to @robgleeson about Travis. They pass for me as well - I just have the random single fail. But that's on the current master as well. |
Merge? Or something else needs fixing? |
I think we can disregard the build failure as unrelated, and handle it in #126. There are a couple of code comments without spacing left, and it'd be good to squash the commits into one. 👍 from my side then, good work! |
Finished spaces in comments. How strong is the feeling that the commits need to be squashed? (It'll take me a bit since it's a process I don't normally use) |
Nevermind, that was much easier than I thought it'd be. |
raise ArgumentError, "Invalid Cache Control directive value: #{name.inspect} can only be 'true' not #{value.inspect}" | ||
end | ||
if not (ALL_DIRECTIVES.has_key?(name) or | ||
[:cache_extension, :extension, :extensions, :cache_extensions].include?(name)) |
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 could cache this array in a constant.
can we wrap the has_key?
/include?
calls in a descriptive method name?
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.
maybe, directive?()
, and extension?()
.
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.
👍 @robgleeson
@nyarly I merged your other PR and it brought conflicts into this one. should be easy to fix, but FYI. |
@robgleeson I'd vote for addressing these in a future PR and merging this one with the conflicts resolved, I'm seeing us run down the rabbit-hole. |
@lgierth +1 |
# Parse the value of a Cache-Control header into a CacheControl object | ||
# @param [String] string The string-format header value | ||
# @param [Array|nil] expected_extension_tokens Any tokens expected in a | ||
# Cache Control 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.
Indent this line by 2-4 spaces from the comment hash or it will appear on its own in the docs and look strange.
I have to say, I'm agreeing with @lgierth that this is starting to feel like a rabbit hole. It's clear that there are strong feelings about how it should be integrated with the rest of Webmachine, but some of those motivations are opaque to me. So: this patch is available to merge and improve as-is or not. If not, and I need the functionality in my client app, I can clone the code there and make it fit that project. |
@nyarly the concerns may seem opaque but some of the code does not fit well with ruby idioms, it's not a big deal, it can be addressed later. |
the implementation of |
I can see fixing up the issue on set_cache_control. Something like: coerce all values to a CacheControl Regarding the to_i in to_s: validating the well-formedness of every directive value is definitely past the limit of the amount of work I had in mind for this PR. |
@nyarly |
This PR adds a CacheControl object that aids in the manipulation of HTTP CacheControl headers, and a method Response#set_cache_control to make it easy to use. CacheControl::parse takes a list of acceptable "tokens" which will be parsed as symbols if they were provided as unquoted tokens.
So, can I merge this now? |
if github doesn't go down… |
Year old PR, can we close this if nobody wants it/is working on it? (We can always reopen it if somebody wants it.) |
Sorry @nyarly for the long wait here. I know how this must feel, but i am closing it now. What webmachine currently lacks is a stable way to extend it i believe. |
I appreciate the sympathy. It's been some time since I used ruby-webmachine, to be honest - I remember I was a little irritated at the time... If I come back to webmachine (in any incarnation) I'll look into it again. |
Was considering a more flexible API for Response#set_cache_control, but that can wait, seems to me.