3

I am having trouble figuring out how to serve static files for my Flask web app through Nginx and Gunicorn. All documentation I have seen online points to adding some path to static folder alias when a client makes a request for static files. However, I am not sure what the full path should be since the app is hosted on Heroku. All requests to static files are returning 404 errors. I also noticed that the XHR request to a JSON file stored in the directory path /static/json/<file>.json is not returning a JSON object on success.

Project Hierarchy:

project/
|_ config/
   |_ gunicorn.conf.py
   |_ nginx.conf.erb
|_ flask_app/
   |_ __init__.py
   |_ static/
      |_ css/
      |_ js/
      |_ images/
      |_ json/
|_ Procfile
|_ run.py

project/config/gunicorn.conf.py:

def when_ready(server):
    open('/tmp/app-initialized', 'w').close()

bind = 'unix:///tmp/nginx.socket'

project/config/nginx.conf.erb:

daemon off;
#Heroku dynos have at least 4 cores.
worker_processes <%= ENV['NGINX_WORKERS'] || 4 %>;

events {
    use epoll;
    accept_mutex on;
    worker_connections <%= ENV['NGINX_WORKER_CONNECTIONS'] || 1024 %>;
}

http {
    server_tokens off;

    log_format l2met 'measure#nginx.service=$request_time request_id=$http_x_request_id';
    access_log <%= ENV['NGINX_ACCESS_LOG_PATH'] || 'logs/nginx/access.log' %> l2met;
    error_log <%= ENV['NGINX_ERROR_LOG_PATH'] || 'logs/nginx/error.log' %>;

    include mime.types;
    default_type application/octet-stream;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    #Must read the body in 5 seconds.
    client_body_timeout 5;

    upstream app_server {
        server unix:/tmp/nginx.socket fail_timeout=0;
    }

    server {
        listen <%= ENV["PORT"] %>;
        server_name _;
        keepalive_timeout 5;

        # Configure NGINX to deliver static content from the specified folder
      location /static {
        alias /static;
      }

        location / {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_pass http://app_server;
        }
    }
}

project/Procfile

web: bin/start-nginx gunicorn -c config/gunicorn.conf.py 'run:create_app()'
worker: python worker.py
clock: python clock.py

example.js

export function getData() {
  $.ajax({
    type: 'GET',
    url: '/static/json/data.json',
    async: false,
    success : function(data) {
      currentPageIndex = 0;
      numberOfPages = data.length; // Getting undefined error here on "data"
    }
  });
}
nosh
  • 620
  • 3
  • 14
  • 50
  • Is it working on your localhost? And which folder are your static files stored in? – Arno Deceuninck Jun 09 '20 at 06:00
  • 1
    Heroku places the git root folder into `/app` on their filesystem – Tin Nguyen Jun 09 '20 at 13:03
  • 1
    I updated my project hierarchy, the static files are stored in the static directory. So if Heroky places the root folder `/app` does that mean the Nginx config file should have `location /app/flask_app/static {}` instead of `location /static {}`? – nosh Jun 09 '20 at 19:35
  • To answer your question on if it works in localhost, yes it does but that is because Flask runs on Werkzeug in the development environment so I can't test it on Nginx. And I can't test it in the production environment on localhost because I don't have a certificate for localhost that the browser can recognize since the production environment uses HTTPS. – nosh Jun 09 '20 at 19:38

3 Answers3

0

In your given configuration, there are (at least) two ways of hosting static content. It is not clear to me for which of the two alternatives below you decided - I have the impression you want to have somehow both?

  1. I am sure you read https://flask.palletsprojects.com/en/1.1.x/tutorial/static/ which says

    Flask automatically adds a static view that takes a path relative to the flaskr/static directory and serves it.

    The URL would be the same as for your flask SPA with an additional static behind, see e.g. Link to Flask static files with url_for or https://flask.palletsprojects.com/en/1.1.x/quickstart/#url-building

    The file location for the static content would be /static within your flask-app-directory.

  2. Please also note How to serve static files in Flask which says

    The preferred method is to use nginx or another web server to serve static files; they'll be able to do it more efficiently than Flask.

    In this case, the Nginx docu on Serving Static Content is your friend:

    The URL will be simply www.example.com/whateverHerokuPutsHere/static.

    The file location can be anything you specify inside your nginx.conf, usually one would put absolute paths there.

    Disclaimer: I never worked with heroku, so I am not sure whether there will be actually a whateverHerokuPutsHere. It could well be that it is simply example.com as you configure somewhere on Heroku's UI. For the file-location I found a blog Nginx as a static site server on Heroku.

B--rian
  • 5,578
  • 10
  • 38
  • 89
  • This is basically the tutorial that I was trying to follow along for my flask application: https://medium.com/@eserdk/heroku-nginx-gunicorn-flask-f10e81aca90d. The issue was that the code stated in the tutorial wasn't working for how to serve static files through Nginx. I also tried it with my code above but the Heroku logs were telling me that the static directory wasn't correct so things weren't being loaded from the HTTP response. What I am trying to do is serve my web app and static files through Nginx so that loading is improved. – nosh Jun 10 '20 at 18:17
  • Understood. You are basically going for option 2. Most of the cases it is a file or directory permission issue which avoid nginx from serving files, i.a.w. `/static` should be world-readable. Did you double check that? – B--rian Jun 10 '20 at 18:33
  • Well all the files that get deployed to Heroku are stored in version control on Bitbucket so I assume that file and directory permissions are already world-readable in order for the app to be deployed. Just checked and the permissions for the static directory is "700" – nosh Jun 10 '20 at 20:59
  • Valid point. Well, it could also be a gunicorn issue, see e.g. https://stackoverflow.com/questions/13947326/gunicorn-not-serving-static-files – B--rian Jun 10 '20 at 22:50
  • 1
    So should I force the server in Nginx config file to listen on port 80? So change this line `listen <%= ENV["PORT"] %>;` to this `listen 80;`? I just don't know what I need to do to set up the static path correctly. The post you mentioned shows that in `location /static {}` the user set the root path to the static folder which on Heroku would be /app/flask_app/static/ for me. – nosh Jun 11 '20 at 00:01
  • 1
    I updated my project hierarchy if that helps with what I'm looking for. – nosh Jun 11 '20 at 01:09
  • You posted your full gunicorn config, right? Can you double check what is inside your environment `port` variable? You may indeed try overwriting it. – B--rian Jun 11 '20 at 07:32
  • 1
    Just as an experiment: Please change the `alias static` inside the nginx config to `alias /flask_app/static` or the respective full path, please? – B--rian Jun 11 '20 at 07:42
  • Also: How does the exact 404 error entry inside your access-log-file look like? – B--rian Jun 11 '20 at 07:44
  • Yes it was that, I had the wrong alias, it should be /app//flask_app/static – nosh Jun 11 '20 at 16:45
0

I found out that the issue was related to having an incorrect alias path. It was difficult to determine at first since Heroku has its root directory set up differently than I do on my local machine. The root directory for the Heroku application is /app so I changed the alias to this, based on my project hierarchy. This should work for anyone else facing similar issues.

location /static {
   alias /app/flask_app/static;
}
B--rian
  • 5,578
  • 10
  • 38
  • 89
nosh
  • 620
  • 3
  • 14
  • 50
-1

use python code to render files

@app.route("/")
def main():
    return render_template('main.html')

examples/flask/stat/templates/main.html

if you use static images use relative path

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport"
     content="width=device-width, initial-scale=1, user-scalable=yes">

  <link href="/static/css/style.css" rel="stylesheet">
</head>
<body>
<h1>Hello World</h1>

<img src="static/img/code_maven_128.png" />

</body>
</html>

The CSS itself is also very simple, its content is not relevant for our purposes.

examples/flask/stat/static/css/style.css

h1 { color: blue; } How to run this There are two simple ways to run this in developlent. For both you need to open your terminal (cmd window if you are on Windows), change to the directory where the application file (web.py in our case) can be found.

python web.py A better way, that provides more control over how to run this is to use:

https://code-maven.com/flask-serve-static-files

enter code here
Jin Thakur
  • 2,711
  • 18
  • 15