Skip to content
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

Configurable cache-control headers? #57

Open
ianks opened this issue Feb 8, 2019 · 7 comments
Open

Configurable cache-control headers? #57

ianks opened this issue Feb 8, 2019 · 7 comments
Milestone

Comments

@ianks
Copy link
Contributor

ianks commented Feb 8, 2019

Hey there!

So far, Iodine has been amazing. There one issue we have is that all of our assets are fingerprinted, so we would effectively like the browser to cache the forever. Currently, the cache-control header is set to max-age=3600, is there a way to adjust the cache-control header for static assets?

@boazsegev
Copy link
Owner

Hi @ianks ,

At the moment, the max-age header has a cached compile-time static value. However, you raise a valid point about the possible desire to accommodate longer / shorter cache re-validation times.

However, I wonder... facil.io, and iodine (by extension) follow Google's guidelines for incorporating an etag header to the response. So once the max-age validation period expires, a new request should result in an etag comparison and a short 304 response value (unchanged).

Since the assets won't get re-sent (only a 304 response)... I wonder if the extra load is really significant?

I ask this because I wonder how high a priority this extra feature might have.

@boazsegev
Copy link
Owner

@ianks , I jumped right in to the practicalities, but I also want to thank you for opening this issue and this discussion.

@ianks
Copy link
Contributor Author

ianks commented Feb 8, 2019

It's not that big of a deal, but the 304 still means a server round trip, when it could be avoided :)

@evanleck
Copy link

I'd love to see generally configurable headers for static content. By that I mean I'd like to be able to send a high max-age as well as immutable for JS/CSS (something like public, max-age=31536000, immutable) and Access-Control-* headers for e.g. fonts. It complicates things, but the best alternative I can think of is falling back to Rack::Static which isn't great IMO.

The other downside to the shorter max-age is that proxying caches like CloudFront will also have to re-request objects as they expire because the edge caches respect the same max-age value unless you're able to send s-maxage in the cache-control header (see the "Expiration" section). You can manually override the cache values in e.g. CloudFront, but that doesn't seem like an ideal solution.

@boazsegev
Copy link
Owner

@evanleck , Thank you for your input!

Making the static headers configurable using a "default" header hash is a good idea and would make for a sound approach when updating the API.

I'll add this approach to the upcoming facil.io 0.8.0 version, which is the C framework I use to power iodine. When the version is done, the feature will be added to iodine (along with the rest of the changes).

Having different headers for different static file types (JS/CSS vs. fonts) is more than a simple / automatic solution could provide. I have no plan to make the static server that complicated... this is what the X-Sendfile support is for.

It doesn't solve the whole issue, because some headers, such as ETag and Cache-Control, can't be overridden (I'll need to fix that, perhaps fixing this will allow for an interim solution using X-Sendfile).

On the other hand, complicated custom headers can be added using an X-Sendfile scheme while still retaining some of the performance benefits related to iodine's file sending logic.

If all static files should pass through an X-Sendfile middleware, it would make sense to point the iodine static server to /dev/null as the public folder (so no files are ever found).

Here's a quick and dirty example (config.ru):

class XFileMiddleware
  PUBLIC_FOLDER = "./public"
  def initialize(app)
    @app = app
  end

  def call env
    path = File.join(PUBLIC_FOLDER, env['PATH_INFO']);
    return @app.call(env) if path.include?('..') || path.include?('//') || !File.file?(path)
    headers = {"X-Sendfile" => path}
    headers["cache-control"] = "max-age=3600000" # <= ERR? doesn't replace the final data, just adds to it
    case File.extname(path)
    when ".js"
      headers["X-Custom"] = "was javscript"
    when ".ru"
      headers["X-Custom"] = "was a Ruby application"
      headers["Content-Type"] = "text/plain"
    end
    [200, headers, []]
  end
end

module APP
  HTTP_RESPONSE = [200, { 'Content-Type' => 'text/html' }, ["Hello World"]]
  def self.call(env)
    HTTP_RESPONSE
  end
end
use XFileMiddleware
run APP

I ran the example using:

$ iodine -www /dev/null

@evanleck
Copy link

@boazsegev thanks for the detailed response! I totally get that configuring the headers per file-type is probably beyond what a simple static server needs to do. I didn't really know what sendfile was all about but your explanation makes sense and seems promising for what I need, thanks for that! Any idea how the performance of sendfile compares to the default static server? No worries if not, just curious if you knew off hand.

@boazsegev
Copy link
Owner

@evanleck ,

Thanks for the question, it was really interesting to test it out.

The performance difference depends on the size of the file vs. the overhead of entering the Ruby GIL (Global Interpreter Lock) and running the Ruby code.

For small files, you could see a 50% slowdown relative to the static file server, since the overhead is a larger chunk of the total request handling time.

On bigger files you won't see any difference (the performance noise is bigger than the extra overhead).

Then again, iodine's static file server runs in C, doesn't call any Ruby code and can be as fast as nginx (and sometimes faster, especially for small files... at least on my machine).

Both options will be faster than a dynamic application and are likely to outperform many alternatives.

@boazsegev boazsegev added this to the 0.8.0 milestone Jul 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants