tiangolo / uwsgi-nginx-flask-docker

Docker image with uWSGI and Nginx for Flask applications in Python running in a single container.
https://hub.docker.com/r/tiangolo/uwsgi-nginx-flask/
Apache License 2.0
2.99k stars 608 forks source link

routing for flask_restful is not working #138

Closed davidwynter closed 4 years ago

davidwynter commented 5 years ago

Subsequent to my last issue I now have SPA React pages served from my docker container.

But the flask_restful implementation seems to hide the route information normally exposed using the @app.route annotation. My main.py (webapp.py in my case) has the following:

from flask_restful import Api
...
api = Api(app)

api.add_resource(resources.Login, '/api/login', methods=['POST'])
api.add_resource(resources.Logout, '/api/logout', methods=['POST'])
api.add_resource(resources.RegisterOtp, '/api/register_otp', methods=['POST'])
...
@app.route('/', methods=['POST', 'GET'])
def index():
    index_path = os.path.join(app.static_folder, 'index.html')
    return send_file(index_path)

@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify({'error': 'Not found'}), 404)

@app.errorhandler(500)
def server_error(e):
    logging.exception('An error occurred during a request. %s', e)
    return "An internal error occurred", 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True, port=80, use_reloader=False)

The additional nginx config, derived from what I has when running native with werkzurg serving on port 5000 is as follows:

server {
    listen       80;
    server_name  localhost;
    root         /app/ui;

    index index.html index.htm;
    # Load configuration files for the default server block.
    include /etc/nginx/default.d/*.conf;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api {
            proxy_redirect          off;
            proxy_pass_header       Server;
            proxy_set_header        X-Real-IP $remote_addr;
            proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header        X-Scheme $scheme;
            proxy_set_header        Host $http_host;
            proxy_set_header        X-NginX-Proxy true;
            proxy_connect_timeout   5;
            proxy_read_timeout      240;
            proxy_intercept_errors  on;
            proxy_pass http://127.0.0.1:5000;
        }

    error_page 404 /404.html;
            location = /40x.html {
        }

    error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
}

I realise that uwsgi is not serving on port 5000, but it is not clear on what changes I need to make.

tiangolo commented 5 years ago

Check the docs here: https://github.com/tiangolo/uwsgi-nginx-flask-docker#serve-indexhtml-directly

For instructing Nginx to serve your static files directly. That's more efficient than going through your Flask app.

Then here for setting a custom directory in where those static files are stored: https://github.com/tiangolo/uwsgi-nginx-flask-docker#custom-static-path

davidwynter commented 5 years ago

Thank you for that. It will improve efficiency to have nginx serve them.

But more important to me currently is getting my dynamically produce json from flask_restful served. I cannot see anything in the documentation that covers this

tiangolo commented 5 years ago

But more important to me currently is getting my dynamically produce json from flask_restful served. I cannot see anything in the documentation that covers this

Sorry, I'm not sure I understand what you refer to with this.

davidwynter commented 5 years ago

The following does not work, i.e. the classes I have created to respond to individual restul api calls are not being reached. Sorry but flask_restful is not clear, more like magic.

from flask_restful import Api
...
api = Api(app)

api.add_resource(resources.Login, '/api/login', methods=['POST'])

The 1st argument sets up the class that responds to the restful api call, the 2nd and 3rd are equivalent to the @app.post('api/login') annotation in normal flask. The resources.Login method in resources.py module looks like this:

class Login(Resource):
    def post(self):
        ...
                return {
                    'message': 'Logged in as {}'.format(email),
                    'access_token': access_token,
                    'refresh_token': refresh_token,
                    'requires_totp': str(user.totp),
                    'layout': app_layout,
                    'anon': anon,
                    'role': str(user.role)
                }

It is not clear if this uses werkzurg or if uwsgi is suitable. Maybe I cannot use flask_restful? Actually, maybe I am completely off track? Originally I used a location in nginx.conf to pass the /api resful calls to my app

    location /api {
            proxy_redirect          off;
            proxy_pass_header       Server;
            proxy_set_header        X-Real-IP $remote_addr;
            proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header        X-Scheme $scheme;
            proxy_set_header        Host $http_host;
            proxy_set_header        X-NginX-Proxy true;
            proxy_connect_timeout   5;
            proxy_read_timeout      240;
            proxy_intercept_errors  on;
            proxy_pass http://127.0.0.1:5000;
        }

I include this now except use port 80 as uwsgi is listening on that port, whereas werkzurg listened on port 5000

tiangolo commented 5 years ago

Hmm, this shouldn't have anything to do with this image.

Independent of how flask-restful works, it would expose a single Flask app (technically, a WSGI app).

You shouldn't need to add any specific additional configurations to Nginx for it to work.

Try with the simplest example you can imagine. And see if it works correctly. Then increase that example slowly to what you have in your actual app. At the point that it breaks, you should check what might be wrong in your code (or even in flask-restful itself).

But if the simplest example works, there's a very low chance that additional changes to your app would need changes in the image configurations.


As a side note, if you don't already have your app built but you need to use Flask, I would suggest using Flask-apispec. I tested Flask-restful and several others before finding it and it ended up working better.

If you don't have any code yet and you don't depend on Flask, I would suggest you try FastAPI. It includes a lot of the learnings from several previous frameworks. And is designed from the beginning to have autocomplete everywhere and be super easy to learn, understand, and use.

edmjenkins commented 5 years ago

I have a big hint here in the log. When my flask app starts up it has to build a large in memory cache of pre-calculated analytical data used for graphs in the React front end. I think that something in uwsgi is terminating (SIGTERM) while I am building the cache:

DEBUG:apscheduler.scheduler:Next wakeup is due at 2019-05-31 06:00:00+00:00 (in 70367.536472 seconds)
WSGI app 0 (mountpoint='') ready in 10 seconds on interpreter 0x563ac96c4f70 pid: 14 (default app)
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** 
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 14)
spawned uWSGI worker 1 (pid: 45, cores: 1)
spawned uWSGI worker 2 (pid: 46, cores: 1)
running "unix_signal:15 gracefully_kill_them_all" (master-start)...
2019/05/30 10:30:22 [alert] 15#15: 1024 worker_connections are not enough
2019/05/30 10:30:22 [error] 15#15: *1023 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 127.0.0.1, server: localhost, request: "POST /api/login HTTP/1.0", upstream: "http://127.0.0.1:80/api/login", host: "localhost", referrer: "http://localhost/login"
2019/05/30 10:30:22 [error] 15#15: *1023 open() "/app/ui/50x.html" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "POST /api/login HTTP/1.0", upstream: "http://127.0.0.1:80/api/login", host: "localhost", referrer: "http://localhost/login"
127.0.0.1 - - [30/May/2019:10:30:22 +0000] "POST /api/login HTTP/1.0" 404 555 "http://localhost/login" "Mozilla/5.0 (X11; L
inux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36 OPR/60.0.3255.27" "172.17.0.1, 127.0.
0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.
1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1,
 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 1
27.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127
.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0
...

There are pages and pages of those 127.0.0.1, entries in the log, any way I can cut them down so the log is more easily readable?

Something else concerns me this message in the log "POST /api/login HTTP/1.0" 404 2293 "http://localhost/login" when the flask_restful is listening on api.add_resource(resources.Login, '/api/login', methods=['POST']) i.e. http://localhost/api/login

edmjenkins commented 5 years ago

I did what you asked and created a minimal application using flask-restful, it gets this error:

(general) (base) david@david-desktop:~/gitlab/docker_restful_test$ curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT
curl: (56) Recv failure: Connection reset by peer

Using this Dockerfile:

FROM tiangolo/uwsgi-nginx-flask:python3.7
# Copy app
COPY ./*.py /app/
COPY conf/uwsgi.ini /app
COPY requirements.txt /tmp/
RUN pip install -U pip && pip install -U sphinx && pip install -r /tmp/requirements.txt
EXPOSE 5000

requirements.txt:

flask
flask_restful
Flask-Cors

from this code

from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

todos = {}

class TodoSimple(Resource):
    def get(self, todo_id):
        return {todo_id: todos[todo_id]}

    def put(self, todo_id):
        todos[todo_id] = request.form['data']
        return {todo_id: todos[todo_id]}

api.add_resource(TodoSimple, '/<string:todo_id>')

if __name__ == '__main__':
    app.run(debug=True)

I use this docker run command docker run --name restfultest -d -e URL=http://localhost -p 5000:5000 --rm -e WORKERS_PER_CORE="0.125" -e MODULE_NAME="webapp" fairgo/dockerrestfultest:v01. The docker logs restfultest shows no errors, last 2 lines

2019-06-03 15:58:54,993 INFO success: uwsgi entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)

The example is straight out of https://flask-restful.readthedocs.io/en/0.3.5/quickstart.html

keko950 commented 4 years ago

This keep happening! Ping!

dgroh commented 4 years ago

+1

I get the following error when using this image:

Not Found

The requested URL was not found on the server. If you entered the URL manually please check > your spelling and try again.

Here the config I'm using:

.Dockerfile

FROM tiangolo/uwsgi-nginx-flask:python3.6-alpine3.7

LABEL Name=myappname Version=0.0.1
EXPOSE 3000
ENV LISTEN_PORT=3000

WORKDIR /app
ADD . /app

RUN python -m pip install -r requirements.txt

docker-compose.yml

version: '3.3'

services:
  myappname:
    image: myappname
    build: .
    ports:
      - 3000:3000
    links:
      - db
    depends_on: 
      - db
    environment:
      - MONGO_DB_HOST=db

  db:
      image: mongo

main.py

from flask import Flask
from flask_restful import Api
from event import Event, EventList

app = Flask(__name__)
api = Api(app)

##
# Actually setup the Api resource routing here
##
api.add_resource(EventList, '/api/v1/events')
api.add_resource(Event, '/api/v1/events/<string:event_id>')

if __name__ == '__main__':
    app.run(debug=True)
tiangolo commented 4 years ago

I don't use Flask-Restful, as I would instead use FastAPI :man_shrugging: , but anyway, here's a working example:

.
├── app
│   ├── main.py
│   └── uwsgi.ini
├── Dockerfile
└── requirements.txt

app/main.py:

from flask import Flask, request
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

todos = {}

class TodoSimple(Resource):
    def get(self, todo_id):
        return {todo_id: todos[todo_id]}

    def put(self, todo_id):
        todos[todo_id] = request.form['data']
        return {todo_id: todos[todo_id]}

api.add_resource(TodoSimple, '/rest/<string:todo_id>')

@app.route("/hello")
def hello():
    return "Hello"

if __name__ == '__main__':
    app.run(debug=True)

app/uwsgi.ini:

[uwsgi]
module = main
callable = app

Dockerfile:

FROM tiangolo/uwsgi-nginx-flask:python3.7

COPY requirements.txt /tmp/
RUN pip install -r /tmp/requirements.txt
COPY ./app /app

requirements.txt:

flask
flask_restful

$ docker build -t restfully .

---> 100%

$ docker run -d --name restfully -p 80:80 restfully

c61078da16d22

// Check that standard Flask works
$  curl http://localhost/hello

Hello

$ curl http://localhost/rest/todo1 -d "data=Remember the milk" -X PUT

{"todo1": "Remember the milk"}

$ curl http://localhost/rest/todo1

// Error because it was probably served by one of the processes that don't have the dict in memory, still, you wouldn't store that in memory but in a DB
{"message": "Internal Server Error"}

// But try again
$ curl http://localhost/rest/todo1

// It's probably served by the process that has the dict in memory
{"todo1": "Remember the milk"}

I hope that can help to guide you to solve your own issues.

noktus commented 4 years ago

Hi! I'm facing the same issue and I made some test that maybe can help. I think the problem, at least for me, is with structured projects. The last test I did is with a tree like this:

├── app │ ├── app │ │ ├── init.py │ │ ├── main.py │ └── uwsgi.ini └── Dockerfile

Using a plain flask @app.route, it works perfect. But when I change the plain flask to the flask_restplus way, then I have a 404 error.

Can the issue be related to that?

tiangolo commented 4 years ago

@noktus I wouldn't know for sure.

I personally don't use Flask-Restful or Flask-Resplus, when I tried them the best combination for me was Fask-apispec with Marshmallow and Webargs.

But after that, I built FastAPI heavily inspired by those ideas, and improving them with the new tools in modern Python, like type annotations.

But it seems that similar errors would probably not be related to this image but to the framework configuration directly...

Maybe you can check the example I provided above and adapt it to your use case and it might help you find where's the problem.


@davidwynter if you solved your problem, then you can close the issue.

github-actions[bot] commented 4 years ago

Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.