From 63a12c12a053d8db4b655e09ebf5189e56fbce87 Mon Sep 17 00:00:00 2001 From: Yaw Anokwa Date: Wed, 6 Nov 2019 21:38:29 -0800 Subject: [PATCH] Switch to waitress to support chunked encoding --- Dockerfile | 6 ++-- README.md | 8 ++--- app/main.py | 92 +++++++++++++++++++++++++----------------------- app/uwsgi.ini | 3 -- requirements.txt | 3 +- 5 files changed, 58 insertions(+), 54 deletions(-) delete mode 100644 app/uwsgi.ini diff --git a/Dockerfile b/Dockerfile index 722a83c..ee489c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,11 @@ -FROM tiangolo/uwsgi-nginx-flask:python3.7-alpine3.8 +FROM python:3.8-alpine COPY requirements.txt /tmp/ -RUN pip install --upgrade pip RUN pip install --requirement /tmp/requirements.txt RUN apk --update add openjdk8-jre-base COPY ./app /app +WORKDIR /app + +CMD ["waitress-serve", "--port=80", "--call", "main:app"] diff --git a/README.md b/README.md index 406c4eb..f3f551f 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,20 @@ pyxform-http is a Flask-based web service that uses pyxform to convert a XLSForm # Run locally ``` pip install --requirement requirements.txt -FLASK_APP=app/main.py FLASK_DEBUG=1 flask run +FLASK_APP=app/main.py:app FLASK_DEBUG=1 flask run ``` # Run in Docker ``` docker build --tag pyxform-http . -docker run --detach --name pyxform-http --publish 5000:5000 pyxform-http +docker run --detach --name pyxform-http --publish 5000:80 pyxform-http ``` # Test forms -A form that converts successfully +A form that converts successfully (with chunked encoding!) ``` -curl --request POST --header "X-XlsForm-FormId-Fallback: pyxform-clean" --data-binary @test/pyxform-clean.xlsx http://127.0.0.1:5000/api/v1/convert +curl --request POST --header "X-XlsForm-FormId-Fallback: pyxform-clean" --header 'Transfer-Encoding: chunked' --data-binary @test/pyxform-clean.xlsx http://127.0.0.1:5000/api/v1/convert ``` A form that fails to convert and returns a pyxform error diff --git a/app/main.py b/app/main.py index ff6818a..397fc15 100644 --- a/app/main.py +++ b/app/main.py @@ -5,50 +5,54 @@ from flask import Flask, jsonify, request, escape from pyxform import xls2xform -app = Flask(__name__) -logger = logging.getLogger(__name__) - - -@app.route("/") -def index(): - return "Welcome to the pyxform-http! Make a POST request to '/api/v1/convert' to convert an XLSForm to an ODK XForm." - - -@app.route("/api/v1/convert", methods=["POST"]) -def post(): - - xlsform_formid_fallback = sanitize(request.headers.get("X-XlsForm-FormId-Fallback")) - if xlsform_formid_fallback is None: - xlsform_formid_fallback = "tmp" - - with TemporaryDirectory() as temp_dir_name: - try: - with open( - os.path.join(temp_dir_name, xlsform_formid_fallback + ".xml"), "w+" - ) as xform, open( - os.path.join(temp_dir_name, xlsform_formid_fallback + ".xlsx"), "wb" - ) as xlsform: - xlsform.write(request.get_data()) - convert_status = xls2xform.xls2xform_convert( - xlsform_path=str(xlsform.name), - xform_path=str(xform.name), - validate=True, - pretty_print=False, - ) - - if convert_status: - logger.warning(convert_status) - - if os.path.isfile(xform.name): - return response( - status=200, result=xform.read(), warnings=convert_status + +def app(): + app = Flask(__name__) + logger = logging.getLogger(__name__) + + @app.route("/") + def index(): + return "Welcome to the pyxform-http! Make a POST request to '/api/v1/convert' to convert an XLSForm to an ODK XForm." + + @app.route("/api/v1/convert", methods=["POST"]) + def post(): + + xlsform_formid_fallback = sanitize( + request.headers.get("X-XlsForm-FormId-Fallback") + ) + if xlsform_formid_fallback is None: + xlsform_formid_fallback = "tmp" + + with TemporaryDirectory() as temp_dir_name: + try: + with open( + os.path.join(temp_dir_name, xlsform_formid_fallback + ".xml"), "w+" + ) as xform, open( + os.path.join(temp_dir_name, xlsform_formid_fallback + ".xlsx"), "wb" + ) as xlsform: + xlsform.write(request.get_data()) + convert_status = xls2xform.xls2xform_convert( + xlsform_path=str(xlsform.name), + xform_path=str(xform.name), + validate=True, + pretty_print=False, ) - else: - return response(error=convert_status) - except Exception as e: - logger.error(e) - return response(error=str(e)) + if convert_status: + logger.warning(convert_status) + + if os.path.isfile(xform.name): + return response( + status=200, result=xform.read(), warnings=convert_status + ) + else: + return response(error=convert_status) + + except Exception as e: + logger.error(e) + return response(error=str(e)) + + return app def sanitize(string): @@ -60,5 +64,5 @@ def response(status=400, result=None, warnings=None, error=None): if __name__ == "__main__": - # Only for debugging while developing - app.run(host="0.0.0.0", debug=True, port=80) + app = app() + app.run() diff --git a/app/uwsgi.ini b/app/uwsgi.ini deleted file mode 100644 index 1abe35a..0000000 --- a/app/uwsgi.ini +++ /dev/null @@ -1,3 +0,0 @@ -[uwsgi] -module = main -callable = app diff --git a/requirements.txt b/requirements.txt index 01c2408..271e7e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -Flask==1.1.0 +Flask==1.1.1 +waitress==1.2.1 pyxform==0.15.1