How to Make Nginx Serve Static Content Like .Js, .Css, .Html

Serving static content using Nginx after successful NodeJS authentication

I figured out the solution that involves below steps:

  1. Serve static login page using Nginx
  2. Forward the login credentials to the NodeJS server where authentication is handled and auth-token is stored in the response cookie (http-only). Send this response with the cookie set to the client
  3. Once the client receives authentication message, on success request the secure webapp page. This request will carry the auth-cookie along with itself.


    1. Again forward the request to the NodeJS server from Nginx, validate the token, set X-Accel-Redirect header with the path where to look for the secure file on NodeJS server.

While using X-Accel-Redirect, special care needs to be taken for Mime-Type of the response.

  • CSS needs to be served with text/css
  • images needs to be served with images/jpeg
  • HTML content should be served with text/html

Nginx fails to load css files

I found an workaround on the web. I added to /etc/nginx/conf.d/default.conf the following:

location ~ \.css {
add_header Content-Type text/css;
}
location ~ \.js {
add_header Content-Type application/x-javascript;
}

The problem now is that a request to my css file isn't redirected well, as if root is not correctly set.
In error.log I see

2012/04/11 14:01:23 [error] 7260#0: *2 open() "/etc/nginx//html/style.css"

So as a second workaround I added the root to each defined location.
Now it works, but seems a little redundant. Isn't root inherited from / location ?

How to serve static files in Flask

In production, configure the HTTP server (Nginx, Apache, etc.) in front of your application to serve requests to /static from the static folder. A dedicated web server is very good at serving static files efficiently, although you probably won't notice a difference compared to Flask at low volumes.

Flask automatically creates a /static/<path:filename> route that will serve any filename under the static folder next to the Python module that defines your Flask app. Use url_for to link to static files: url_for('static', filename='js/analytics.js')

You can also use send_from_directory to serve files from a directory in your own route. This takes a base directory and a path, and ensures that the path is contained in the directory, which makes it safe to accept user-provided paths. This can be useful in cases where you want to check something before serving the file, such as if the logged in user has permission.

from flask import send_from_directory

@app.route('/reports/<path:path>')
def send_report(path):
return send_from_directory('reports', path)

Do not use send_file or send_static_file with a user-supplied path. This will expose you to directory traversal attacks. send_from_directory was designed to safely handle user-supplied paths under a known directory, and will raise an error if the path attempts to escape the directory.

If you are generating a file in memory without writing it to the filesystem, you can pass a BytesIO object to send_file to serve it like a file. You'll need to pass other arguments to send_file in this case since it can't infer things like the file name or content type.

Ingress Nginx - how to serve assets to application

TL;DR

To diagnose the reason why you get error 404 you can check in nginx-ingress controller pod logs. You can do it with below command:

kubectl logs -n ingress-nginx INGRESS_NGINX_CONTROLLER_POD_NAME

You should get output similar to this (depending on your use case):

CLIENT_IP - - [12/May/2020:11:06:56 +0000] "GET / HTTP/1.1" 200 238 "-" "REDACTED" 430 0.003 [default-ubuntu-service-ubuntu-port] [] 10.48.0.13:8080 276 0.003 200 
CLIENT_IP - - [12/May/2020:11:06:56 +0000] "GET /assets/styles/style.css HTTP/1.1" 200 22 "http://SERVER_IP/" "REDACTED" 348 0.002 [default-ubuntu-service-ubuntu-port] [] 10.48.0.13:8080 22 0.002 200

With above logs you can check if the requests are handled properly by nginx-ingress controller and where they are sent.

Also you can check the Kubernetes.github.io: ingress-nginx: Ingress-path-matching. It's a document describing how Ingress matches paths with regular expressions.


You can experiment with Ingress, by following below example:

  • Deploy nginx-ingress controller
  • Create a pod and a service
  • Run example application
  • Create an Ingress resource
  • Test
  • Rewrite example

Deploy nginx-ingress controller

You can deploy your nginx-ingress controller by following official documentation:

Kubernetes.github.io: Ingress-nginx

Create a pod and a service

Below is an example definition of a pod and a service attached to it which will be used for testing purposes:

apiVersion: apps/v1
kind: Deployment
metadata:
name: ubuntu-deployment
spec:
selector:
matchLabels:
app: ubuntu
replicas: 1
template:
metadata:
labels:
app: ubuntu
spec:
containers:
- name: ubuntu
image: ubuntu
command:
- sleep
- "infinity"
---
apiVersion: v1
kind: Service
metadata:
name: ubuntu-service
spec:
selector:
app: ubuntu
ports:
- name: ubuntu-port
port: 8080
targetPort: 8080
nodePort: 30080
type: NodePort

Example page

I created a basic index.html with one css to simulate the request process. You need to create this files inside of a pod (manually or copy them to pod).

The file tree looks like this:

  • index.html
  • assets/styles/style.css

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="assets/styles/style.css">
<title>Document</title>
</head>
<body>
<h1>Hi</h1>
</body>

Please take a specific look on a line:

  <link rel="stylesheet" href="assets/styles/style.css">

style.css:

h1 {
color: red;
}

You can run above page with python:

  • $ apt update && apt install -y python3
  • $ python3 -m http.server 8080 where the index.html and assets folder is stored.

Create an Ingress resource

Below is an example Ingress resource configured to use nginx-ingress controller:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-ingress-example
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host:
http:
paths:
- path: /
backend:
serviceName: ubuntu-service
servicePort: ubuntu-port

After applying above resource you can start to test.

Test

You can go to your browser and enter the external IP address associated with your Ingress resource.

As I said above you can check the logs of nginx-ingress controller pod to check how your controller is handling request.

If you run command mentioned earlier python3 -m http.server 8080 you will get logs too:

$ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
10.48.0.16 - - [12/May/2020 11:06:56] "GET / HTTP/1.1" 200 -
10.48.0.16 - - [12/May/2020 11:06:56] "GET /assets/styles/style.css HTTP/1.1" 200 -

Rewrite example

I've edited the Ingress resource to show you an example of a path rewrite:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-ingress-example
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- host:
http:
paths:
- path: /product/(.*)
backend:
serviceName: ubuntu-service
servicePort: ubuntu-port

Changes were made to lines:

    nginx.ingress.kubernetes.io/rewrite-target: /$1

and:

      - path: /product/(.*)

Steps:

  • The browser sent: /product/
  • Controller got /product/ and had it rewritten to /
  • Pod got / from a controller.

Logs from thenginx-ingress controller:

CLIENT_IP - - [12/May/2020:11:33:23 +0000] "GET /product/ HTTP/1.1" 200 228 "-" "REDACTED" 438 0.002 [default-ubuntu-service-ubuntu-port] [] 10.48.0.13:8080 276 0.001 200 fb0d95e7253335fc82cc84f70348683a
CLIENT_IP - - [12/May/2020:11:33:23 +0000] "GET /product/assets/styles/style.css HTTP/1.1" 200 22 "http://SERVER_IP/product/" "REDACTED" 364 0.002 [default-ubuntu-service-ubuntu-port] [] 10.48.0.13:8080 22 0.002 200

Logs from the pod:

10.48.0.16 - - [12/May/2020 11:33:23] "GET / HTTP/1.1" 200 -
10.48.0.16 - - [12/May/2020 11:33:23] "GET /assets/styles/style.css HTTP/1.1" 200 -

Please let me know if you have any questions in that.

Nginx reverse-proxy route returns its index.html for static asset requests (ex. .css files)

For anyone running into the same issue later on. This was the fix:

nginx-server (root) nginx.config

http {
server {
...
location /dashboard/ {
proxy_pass http://dashboard:80/;
...
}
}
}

You have to add the / at the end for your proxy_pass value ( http://dashboard:80/ ). Then it worked :)

Using express.js to serve html file along with scripts, css, and images

You should do the following things:

  1. Serve your static files
  2. Create an API server that will listen for the requests coming from your frontend app

1. Serve your static files

To serve static files with Express, read this link.

You'll basically add it to your express app:

app.use( express.static( __dirname + '/client' ));

Where '/client' will be the name of the folder with your frontend app files.

2. Create an API server

You can see how to create an API server here.

For the entry point of your application, you should send/render a file.

This could be accomplished with the following code:

app
.get( '/', function( req, res ) {
res.sendFile( path.join( __dirname, 'client', 'index.html' ));
});

This will send a static file every time that a user request a file at the root path of your application.

You can use the asterisk * (wildcard) instead of / too. That symbol meaning that for whatever route requested, you will respond with the same file/action.

More about the responses here.

Sum up

Those are the things that you should seek to build your app.

You can see a simple app with those things implemented here.

Nginx: how to let rewrite rules ignore files or folders

Put rewrite into one location and use other locations for assests/dynamic urls/etc.

server {
listen 80 default;
server_name my.domain.com;
root /path/to/app/root;

location / {
rewrite ^ /index.html break;
}

location /assets/ {
# Do nothing. nginx will serve files as usual.
}
}


Related Topics



Leave a reply



Submit