Serving precompiled assets with nginx

2019-01-30 11:42发布

问题:

Is it possible to serve precompiled assets with nginx directly? Serving assets dynamically with Rails is like 20 times slower (4000 req/sec vs 200 req/sec in my virtualbox).

I guess it can be done with some rewrite rule in nginx.conf. The problem is, however, that these filenames include md5 hash of the content, so I don't really understand what can be done with this.

If its not possible I don't get the whole idea with Rails 3.1 asset pipelines. Reducing client bandwidth and page load time at the cost of x20 server load?

Any ideas?

UPD: So, I managed to setup my nginx and Rails in a way, when everything in my application is served at the speed of ~3500-4000 requests/sec.

First of all I added two virtual hosts, with one serving as a caching proxy to another and discovered that assets are served at the speed I wanted (4k). Then I connected my Rails application with memcached (nothing special so far, just one line in application.rb: ActionController::Base.cache_store = :mem_cache_store, "localhost")

Then I added things like expires_in 1.hour, :public => true if !signed_in?; to my controllers to change default caching policy of Rails content and got speed boost around 500 requests/per second for my dynamic pages (before that it was something close to 200, and it was ~50 before I ever started this all).

Now, when my nginx config files look like this:

nginx.conf:

...
proxy_cache_path  /tmp/blog keys_zone=one:8m max_size=1000m inactive=600m;
proxy_temp_path /tmp;
gzip  off;
include /opt/nginx/conf/sites-enabled/*;

sites-enabled/blog:

server {
        listen   8080;
        server_name  blindsight;

        root   /home/mike/rails/blog/public;
        rails_env production;

        # serve static content directly
        location ~* \.(ico|jpg|gif|png|swf|html)$ {
          if (-f $request_filename) {
            expires max;
            break;
          }
        }

        passenger_enabled on;

        location ~ /\.ht {
          deny  all;
        }
}

sites-enabled/main:

server {

    listen   80;
    server_name  blindsight;

    location /authorize
    {
       proxy_pass_header Cookie;
       proxy_pass_header Set-Cookie;
       proxy_pass http://127.0.0.1:8080;
    }

    location /admin
    {
       proxy_pass_header Set-Cookie;
       proxy_pass_header Cookie;
       proxy_pass http://127.0.0.1:8080;
    }

    location / {
    root /home/mike/rails/blog/public;

        # All POST requests go directly
        if ($request_method = POST) {
          proxy_pass http://127.0.0.1:8080;
          break;
        }

    proxy_redirect off;
    proxy_pass_header Cookie;
    proxy_ignore_headers Set-Cookie;
    proxy_hide_header Set-Cookie;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_cache one;
    proxy_cache_key blog$request_uri;
    proxy_cache_valid 200 302  5s;
    proxy_cache_valid 404      1m;
    proxy_pass http://127.0.0.1:8080;

    }

Everything is fast like a bloody lightning :) Thank you, guys.

回答1:

Although I don't have experience working with rails, my guess is you're using nginx + passenger with the proxy_pass directive. It sounds like your "static assets" have dynamic urls to serve the assets, which prevents you from configuring nginx to serve the content from directly from nginx through specialized location paths like the follow snippet:

#  static content
location ~* ^.+.(jpg|jpeg|gif|css|png|js|ico|xml)$ {
  # access_log        off;
  expires           15d;
}

If this is correct, my advice to you is to try using nginx's proxy_cache directive. This will let you control how often nginx goes to passenger to "regenerate" the response nginx saved previously requested and cached. This server fault answer should help you should demonstrate the use. With proxy_cache, you can cache any response such as dynamically generated images or even just json/javascript/html content.

You could also try the memcached module, which will give you a more fine-grained control over caching. The down side to this is you have to actually push your files into memcache with code to populate it. The upside is, you can centrally cache your content in some sort of memcached cluster.



回答2:

Following on from above with some extra bits I gleaned from the interweb:

For Rails 3.1:

location ~* ^/assets/ {
    # Per RFC2616 - 1 year maximum expiry
    # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
    expires 1y;
    add_header Cache-Control public;

    # Some browsers still send conditional-GET requests if there's a
    # Last-Modified header or an ETag header even if they haven't
    # reached the expiry date sent in the Expires header.
    add_header Last-Modified "";
    add_header ETag "";
    break;
}

For Rails 3.0 use

location ~* ^/(images|javascripts|stylesheets)/ {
    ... copy block from above ...
}


回答3:

Try adding this to your NGINX config:

server {

  ...

  location ~* ^/assets {
    expires max;
    add_header Cache-Control public;
    break;
  }

  ...

}


回答4:

Well, I know this is an old question, but the Passenger standalone does it as follows:

    # Rails asset pipeline support.
    location ~ ^/assets/ {
        error_page 490 = @static_asset;
        error_page 491 = @dynamic_request;
        recursive_error_pages on;

        if (-f $request_filename) {
            return 490;
        }
        if (!-f $request_filename) {
            return 491;
        }
    }
    location @static_asset {
        gzip_static on;
        expires max;
        add_header Cache-Control public;
        add_header ETag "";
    }
    location @dynamic_request {
        passenger_enabled on;
    }


回答5:

maybe you should run rake assets:precompile It will stick precompiled assets under /public/assets/