FastAPI, nginx, Docker Explicitly Add Each Endpoint - python

I have a simple FastAPI that returns a string based on a query parameter at the endpoint /day. Trying to deploy using Docker and nginx as a reverse proxy. In order to get http://localhost:3000/api/day?day_num=5 to work, I needed to explicitly add location /api/day to my nginx-setup.conf. I would receive an error if I only had location /api/ and tried to visit http:localhost:3000/api/day.
How can I avoid having to explicitly add each endpoint? Is there a way to make /api/day, /api/docs and any other valid endpoints for localhost:8000 (port Docker exposes for the backend service) work when I visit http:localhost:3000/api/{valid endpoints}?
main.py
from fastapi import FastAPI
app = FastAPI()
#app.get("/day", tags=["Dates"])
async def get_day_of_week(day_num: int = 0):
"""
Get the current day of week
"""
days = ['Sun', 'M', 'T', 'W', 'Th', 'F', 'S']
return days[day_num]
I am trying to use nginx as a reverse proxy.
nginx-setup.conf
upstream api {
server backend:8000;
}
server {
listen 8080;
location /api/ {
proxy_pass http://api;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/day {
proxy_pass http://api/day;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
}
}
docker-compose.yml
version: '3'
services:
backend:
build:
context: ./backend
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --root-path /api
ports:
- "8000:8000"
nginx:
image: nginx:latest
ports:
- 3000:8080
volumes:
- ./nginx/nginx-setup.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- backend

If you don't have a trailing slash in the proxy_pass entry, the path will be included verbatim. So:
location /api/ {
proxy_pass http://api;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
}
Will result in the backend seeing /api/day as the request path. Since your application only expects /day, you have to modify your proxy_pass directive to include a trailing slash - this tells nginx to strip the matching part from the location /api/ entry (and you should have a trailing slash here as well).
location /api/ {
proxy_pass http://api/; # <- trailing slash
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
}

You should remove the trailing slash in your location configuration:
server {
listen 8080;
location /api/ {
-----------------^ remove this one
proxy_pass http://api;
Nginx replaces the location with a non-trailing-slash uri. So, http://yourserver/api/something will become http://apisometing (note the concatenation).

Related

How can I connect to InfluxDB through Nginx reverse proxy?

I have Nginx running as a reverse proxy on a computer with only one open port. Through this port and Nginx I redirect the received requests to several internal servers. Now I need to run InfluxDB on this computer, but the client writing to InfluxDB is on another computer.
My first idea was to add a new location to redirect input requests since port 8086 is closed, for example:
location /databasets {
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_set_header X-NginX-Proxy true;
proxy_pass http://localhost:8086;
}
and then, with Python, I use:
client = InfluxDBClient(host='https://myurl', port=10000, 'root', 'root', dbname='mydb', path='databasets', ssl=True, proxies={"https": "https://myurl:10000/databasets"})
But so far it doesn't work, I have tried a couple of ways of configuring the nginx.conf file that I have seen on the internet and also changing the host / port in the Python client. I don't know if this is not possible, or on which side is the error, any ideas?
Thanks in advance
Add the following config in your nginx config
location /databasets/ {
proxy_pass http://localhost:8086;
rewrite `^/databasets/(.*) /$1 break`;
proxy_set_header Host $host;
}
The input url needs to be rewritten

how to change www to something else in Django urls?

I've created a rest API for my Django app but how I go to api.website.com rather than something like www.website.com/api
Btw I'm using nginx if that has to do anything with this
In your nginx configuration add something like this. This passes all requests on api.website.com to your gunicorn socket -> your django app.
server {
listen *:80;
server_name api.website.com;
location ~ ^/api(.*)$ {
try_files $uri $1 /$1;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://gunicorn_socket/;
}
}

Remove unwanted leading slash from nginx

I have the following config (inside the server tag) for my nginx server:
location / {
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_set_header X-Forwarded-Proto $scheme;
# Fix the “It appears that your reverse proxy set up is broken" error.
proxy_pass http://localhost:5000;
proxy_read_timeout 90;
}
location /api {
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_set_header X-Forwarded-Proto $scheme;
# Fix the “It appears that your reverse proxy set up is broken" error.
proxy_pass http://localhost:2233/;
proxy_read_timeout 90;
proxy_redirect default;
}
I now try to access /api/auth/login/ via my webbrowser. At port 2233 I have a python server with Flask running. Now in the python console i get:
"GET //auth/login/ HTTP/1.0" 404 -
In my oppinion this path is messy and also not configured in flask, thats why there is a 404 response (for /auth/login i have a route).
How do I get rid of the leading slash nginx produces?
You are using the proxy_pass directive to alias /api/foo to /foo. Alias tends to work best if both source and target URIs end with a / or neither end with a /.
So:
location /api/ {
proxy_pass http://localhost:2233/;
...
}
Will correctly map /api/foo to /foo without adding the double-/ at the beginning. See this document for details.
This may also mean that the bare URI /api may not work correctly now.
Alternatively, perform the alias using rewrite ... break; instead of proxy_pass:
location /api {
rewrite ^/api(?:/(.*))?$ /$1 break;
proxy_pass http://localhost:2233;
...
}
See this document for details.

websockets proxied by nginx to gunicorn over https giving 400 (bad request)

I am having trouble establishing a websocket in my Flask web application.
On the client side, I am emitting a "ping" websocket event every second to the server. In the browser console, I see the following error each second
POST https://example.com/socket.io/?EIO=3&transport=polling&t=LOkVYzQ&sid=88b5202cf38f40879ddfc6ce36322233 400 (BAD REQUEST)
GET https://example.com/socket.io/?EIO=3&transport=polling&t=LOkVZLN&sid=5a355bbccb6f4f05bd46379066876955 400 (BAD REQUEST)
WebSocket connection to 'wss://example.com/socket.io/?EIO=3&transport=websocket&sid=5a355bbccb6f4f05bd46379066876955' failed: WebSocket is closed before the connection is established.
I have the following nginx.conf
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
upstream app_server {
# for UNIX domain socket setups
server unix:/pathtowebapp/gunicorn.sock fail_timeout=0;
}
server {
listen 443 ssl;
server_name example.com www.example.com;
keepalive_timeout 5;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
charset utf-8;
client_max_body_size 30M;
location / {
try_files $uri #proxy_to_app;
}
location /socket.io {
proxy_pass http://app_server;
proxy_redirect off;
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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Upgrade websocket;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
proxy_buffering off;
proxy_headers_hash_max_size 1024;
}
location /static {
alias /pathtowebapp/webapp/static;
}
location #proxy_to_app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# enable this if and only if you use HTTPS
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
#proxy_buffering off;
proxy_pass http://app_server;
}
}
I have been looking all over for examples of a websocket working with https using nginx in front of gunicorn.
My webpage loads, although the websocket connection is not successful.
The client side websocket is established using the following javascript:
var socket = io.connect('https://' + document.domain + ':' + location.port + namespace);
Here is my gunicorn.conf
import multiprocessing
bind = 'unix:/pathtowebapp/gunicorn.sock'
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'eventlet'
[EDIT] if I configure nginx the way it is in the Flask-IO documentation and just run (env)$ python deploy_app.py then it works. But I was under the impression that this was not as production-ideal as the setup I previously mentioned
The problem is that you are running multiple workers on gunicorn. This is not a configuration that is currently supported, due to the very limited load balancer in gunicorn that does not support sticky sessions. Documentation reference: https://flask-socketio.readthedocs.io/en/latest/#gunicorn-web-server.
Instead, run several gunicorn instances, each with one worker, and then set up nginx to do the load balancing, using the ip_hash method so that sessions are sticky.
Also, in case you are not aware, if you run multiple servers you need to also run a message queue, so that the processes can coordinate. This is also covered in the documentation link above.

How to setup Nginx to proxy to two service on one machine?

I have written two micros services with python and ruby. The python one serves some api requests. and the ruby one serves the other api requests.
the python one listens port 80 and can handle /users /feeds requests
the ruby one listens port 4567 and can handle /orders /products requests.
the following is my config file .but it does not work with nginx .
upstream midgard_api_cluster
{
server unix:/tmp/midgard_api.sock;
}
upstream tradeapi {
server 127.0.0.1:4567;
}
server {
listen 80;
server_name my.domain.name;
client_max_body_size 20M;
set $x_remote_addr $http_x_real_ip;
if ($x_remote_addr = "") {
set $x_remote_addr $remote_addr;
}
access_log /var/log/nginx/midgard/access_log ;
error_log /var/log/nginx/midgard/error_log ;
charset utf-8;
location /static/ {
root /opt/www/templates/;
expires 30d;
}
location / {
error_page 502 503 504 /500.html;
uwsgi_pass midgard_api_cluster;
include uwsgi_params;
# proxy_redirect default;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $x_remote_addr;
proxy_set_header Host $http_host;
proxy_set_header Range $http_range;
proxy_connect_timeout 10;
proxy_send_timeout 10;
proxy_read_timeout 11;
}
location /products {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://tradeapi;
proxy_redirect off;
}
location /orders {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://tradeapi;
proxy_redirect off;
}
}
Now , when i use
curl http://my.domain.name/products
It got a 404 error and the request was directed to the python service .
and
curl http://my.domain.name:3000/products
can get the right response .
How can i setup the nginx configuration file and route the request to the ruby service ?
locations are processed in order. The /-location matches before /products so the later is never reached. Put / at the end of the config file.

Categories