poundifdef
poundifdef

Reputation: 19370

Serve static files based on dynamic URLs with Flask+Nginx?

In Flask, if you place a file in a directory called static/, then any URL of the form http://localhost/static/foo.jpg will serve that file from static/foo.jpg.

This can also be accomplished via an nginx config:

    location /static {
        alias    /var/www/mywebsite/static;
    }

However, I want to do dynamic URL rewriting.

If someone requests the URL http://localhost/username/foo.jpg, I want to tell nginx to fetch the static file from an arbitrary URL, say, /var/www/assets/11235/1bcd5.jpg. I want the user to see a pretty url, and I want the location to be transparent to the user.

Is there an easy way to do this? Ideally, I would be able to do something so that nginx serves the file. However, if Flask needs to serve it, then that is fine too (it isn't like my project has any users yet!)

What am I missing here?

Upvotes: 4

Views: 2874

Answers (1)

roderickm
roderickm

Reputation: 191

If the files can be stored with the names that are directly referenced in the "pretty" URL, then you can do a simple rewrite in nginx.

However, it appears that you want to map URL path info to other representations on the disk, as in username -> 11235 and foo.jpg -> 1bcd5.jpg. If the content being served should be protected by authentication or sessions, then you should probably keep the mapping and rewriting inside your Flask app, since Flask provides the means to do that.

If the content can be treated as public and only needs the mapping done, then nginx can be configured to grab query string parameters, look them up in a datastore, and rewrite the URL.

Here's an example from agentzh that was originally posted on the nginx mailing list:

Consider you seo uri is /baz, the true uri is /foo/bar. And I have the following table in my local mysql "test" database:

create table my_url_map(id serial, url text, seo_url);
insert into my_url_map(url, seo_url)values('/foo/bar', '/baz');

And I build my nginx 0.8.41 this way:

./configure \
--add-module=/path/to/ngx_devel_kit \
--add-module=/path/to/set-misc-nginx-module \
--add-module=/path/to/ngx_http_auth_request_module-0.2 \
--add-module=/path/to/echo-nginx-module \
--add-module=/path/to/lua-nginx-module \
--add-module=/path/to/drizzle-nginx-module \
--add-module=/path/to/rds-json-nginx-module

Also, I have lua 5.1.4 and the lua-yajl library installed to my system.

And here's the central part in my nginx.conf:

upstream backend {
    drizzle_server 127.0.0.1:3306 dbname=test
    password=some_pass user=monty protocol=mysql;
    drizzle_keepalive max=300 mode=single overflow=ignore;
}

lua_package_cpath '/path/to/your/lua/yajl/library/?.so';

server {
    ...

    location /conv-mysql {
        internal;
        set_quote_sql_str $seo_uri $query_string; # to prevent sql injection
        drizzle_query "select url from my_url_map where seo_url=$seo_uri";
        drizzle_pass backend;
        rds_json on;
    }

    location /conv-uid {
        internal;
        content_by_lua_file 'html/foo.lua';
    }

    location /jump {
        internal;
        rewrite ^ $query_string? redirect;
    }

    # your SEO uri
    location /baz {
        set $my_uri $uri;
        auth_request /conv-uid;

        echo_exec /jump $my_uri;
    }
}

Contents of foo.lua, the essential glue:

local yajl = require('yajl')
local seo_uri = ngx.var.my_uri
local res = ngx.location.capture('/conv-mysql?' .. seo_uri)
if (res.status ~= ngx.HTTP_OK) then
ngx.throw_error(res.status)
end
res = yajl.to_value(res.body)
if (not res or not res[1] or not res[1].url) then
ngx.throw_error(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
ngx.var.my_uri = res[1].url;

Then let's access /baz from the client side:

$ curl -i localhost:1984/baz
HTTP/1.1 302 Moved Temporarily
Server: nginx/0.8.41 (without pool)
Date: Tue, 24 Aug 2010 03:28:42 GMT
Content-Type: text/html
Content-Length: 176
Location: http://localhost:1984/foo/bar
Connection: keep-alive

<html>
<head><title>302 Found</title></head>
<body bgcolor="white">
<center><h1>302 Found</h1></center>
<hr><center>nginx/0.8.41 (without pool)</center>
</body>
</html>

Upvotes: 1

Related Questions