There is a relatively new (October 2018-) Python web framework called responder. The author is the one who made requests etc., and it seems that it is a framework like Flask and Falcon's good points. When I thought about creating a simple web page with Python, I found out about responder and was curious about it, so I decided to use it. As far as I can see, there was no explanation using Apache, so I will post my own construction method instead of a memo. (Please let me know if you have any ...)
It seems that Nginx is easier to build, but I decided to make it as it is because it is a server that originally contained Apache.
By the way, only venv is used for Python environment construction tool.
After creating an environment with venv, applying the following direnv configuration file to the working directory is convenient because it will be activated
at the same time as cd
to the working directory.
.envrc
source <Full path of venv activate file>
The goal is to publish and run the following programs globally. For the program itself, go to the official Quick Start.
main.py
import responder
api = responder.API()
@api.route("/{who}")
def greet_world(req, resp, *, who):
resp.text = f"Hello, {who}!"
if __name__ == '__main__':
api.run()
In this case, for example, if you access / world
with GET, it will be displayed asHello, world!
, Or if you access / testtesttest
with GET, it will be displayed as Hello, testtest test!
.
The responder has Uvicorn built-in as a built-in server. First, try starting with responder (+ Uvicorn).
$ python main.py
INFO: Started server process [693]
INFO: Waiting for application startup.
INFO: Uvicorn running on http://127.0.0.1:5042 (Press CTRL+C to quit)
$ curl http://127.0.0.1:5042/world
Hello, world!
You can see that the server starts automatically and can be connected just by executing the program.
When you shut down the server, you can use Ctrl + C
as written.
Uvicorn official page It seems that it is better to use Gunicorn for the production environment, so let's move it referring to the official settings. (It's been a while since I tried to write this article, so don't worry, the timestamp is a few months ago.)
$ pip install gunicorn
$ gunicorn -k uvicorn.workers.UvicornWorker main:api
[2019-10-31 09:39:11 +0900] [1227] [INFO] Starting gunicorn 19.9.0
[2019-10-31 09:39:11 +0900] [1227] [INFO] Listening at: http://127.0.0.1:8000 (1227)
[2019-10-31 09:39:11 +0900] [1227] [INFO] Using worker: uvicorn.workers.UvicornWorker
[2019-10-31 09:39:11 +0900] [1230] [INFO] Booting worker with pid: 1230
[2019-10-31 09:39:12 +0900] [1230] [INFO] Started server process [1230]
[2019-10-31 09:39:12 +0900] [1230] [INFO] Waiting for application startup.
$ curl http://127.0.0.1:8000/world
Hello, world!
The arguments when launching Gunicorn are roughly as follows.
---k uvicorn.workers.UvicornWorker
: Specify Uvicorn as the worker class
--main: api
: Specify the module to start. The notation is "module name (program name): variable name ofresponder.API ()
"
Gunicorn configuration items can also be read from the configuration file. It is convenient to have a configuration file later, so create it. The arguments used in the above command are minimal. Create a configuration file by adding the save location of the log file here.
gunicorn.py
import multiprocessing
import os
name = "gunicorn"
accesslog = "<File name to write access log>"
errorlog = "<File name to write the error log>"
bind = "localhost:8000"
worker_class = "uvicorn.workers.UvicornWorker"
workers = multiprocessing.cpu_count() * 2 + 1
worker_connections = 1024
backlog = 2048
max_requests = 5120
timeout = 120
keepalive = 2
user = "www-data"
group = "www-data"
debug = os.environ.get("DEBUG", "false") == "true"
reload = debug
preload_app = False
daemon = False
See Official Docs for each item. The setting value is imitated from that of [Reference site](#Reference site).
To apply this configuration file and launch Gunicorn, use the following command.
$ gunicorn --config gunicorn.py main:api
Finally, set up to connect through Apache. Set to proxy to http: // localhost: 8000 where Gunicorn is waiting when you connect to http://example.com/ where Apache is running.
First, create a configuration file for the reverse proxy.
/etc/apache2/conf-available/responder.conf
ProxyRequests Off
ProxyPass "/" "http://localhost:8000/"
ProxyPassReverse "/" "http://localhost:8000/"
Enable the configuration file & proxy related modules.
$ sudo a2enconf responder
$ sudo a2enmod proxy_http proxy
Reload after confirming that the configuration file is described correctly.
$ sudo apache2ctl configtest
Syntax OK
$ sudo systemctl reload apache2ctl
To make it easier to start automatically, set Gunicorn startup to be managed by systemd. First, create a configuration file.
/etc/systemd/system/webapp.service
[Unit]
Description=gunicorn - responder
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=<gunicorn.py and main.Full path of the directory where py is located>
ExecStart=<Full path of Gunicorn> --config <gunicorn.full path of py> main:api
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target
I will also explain the items that are difficult to understand.
--File name: systemctl restart <set service name>
.
-[Service]-User, Group: I'm using Apache, but I'm not sure if this is correct ... You may also want to change the owner of the files under Working Directory.
-[Service]-ExecStart: This is the full path of the command in "Create a Gunicorn configuration file" in STEP2.
After creating, set the service to start and start automatically.
$ sudo systemctl start webapp.service
$ sudo systemctl enable webapp.service
Just in case, make sure it works properly.
$ sudo systemctl status webapp.service
● webapp.service - gunicorn - responder
Loaded: loaded (/etc/systemd/system/webapp.service; enabled; vendor preset: enable
Active: active (running)
(Omitted below)
Both startup and automatic startup have been successful.
If the settings are correct, you should be able to connect with curl or a browser.
$ curl http://example.com/world
Hello, world!
For some reason, the html Content-Type
header disappears when passing through Apache.
(When you start the server from Gunicorn, it is attached properly. Mysterious phenomenon)
As a tentative measure, I'm adding code that forces Content-Type: text / html; charset = UTF-8
when returning a response.
By the way, I would like to write down the grammar of responder, which I personally found useful / googled and hard to find.
There seems to be more routing (and processing) than just the beginning.
Create class+Set up routing collectively later.py
import responder
api = responder.API()
class Who:
def on_get(self, req, resp, *, who):
#When GET, this process is done automatically
resp.text = f"Hello, {who}!"
async def on_post(self, req, resp, *, who):
#At the time of POST, this process is done automatically
data = await req.media()
resp.text = f"{data}"
#Routing settings
api.add_route("/{who}", Who)
if __name__ == '__main__':
api.run()
Is the writing style at the beginning Flask style, and the writing style just written Falcon style? I myself wrote in "Description using ʻon_get` etc. in the class + Routing setting with the decorator", but maybe it's a bad idea ...
It can be used when you want to define a static file path or write a process that is repeated many times.
jinja_myfilter.py
def css_filter(path):
return f"./static/css/{path}"
def list_filter(my_list):
return_text = "<ul>\n"
for l in my_list:
return_text += f"<li> {l} </li>\n"
return_text += "</ul>"
return return_text
main.py
import responder
import jinja_myfilter
api = responder.API()
#Add filter
# v1.For x
api.jinja_env.filters.update(
css = jinja_myfilter.css_filter,
html_list = jinja_myfilter.list_filter
)
# v2.For x(2020/05/12 Addendum)
# (Because there is an underscore_env seems to be treated as an internal value,
#I couldn't find any other way to specify it by looking at the source ...)
api.templates._env.filters.update(
css = jinja_myfilter.css_filter,
html_list = jinja_myfilter.list_filter
)
@api.route("/")
def greet_world(req, resp):
param = ["Item 1", "Item 2"]
resp.content = api.template("index.html", param=param)
if __name__ == '__main__':
api.run()
index.html
<link rel="stylesheet" type="text/css" href="{{ 'form.css' | css }}">
<!--It is processed by Jinja2 and becomes as follows
<link rel="stylesheet" type="text/css" href="./static/css/form.css">
-->
{% autoescape false %}
{{ param | html_list }}
{% endautoescape %}
<!--It is processed by Jinja2 and becomes as follows
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
-->
If a character string containing an html tag is returned, automatic escaping will work unless it is enclosed in {% autoescape false%} to {% endautoescape%}
. However, if the parameter you are passing is user-input, the parameter will be output without being escaped, so be careful. Is it safe to process it using html.escape ()
etc. in the filter?
How to deploy Responder with Uvicorn or Gunicorn-I want to talk about technology and bodge For an introduction to Python responder ... Preliminary research --Qiita Launch your application with Django + Nginx + Gunicorn | WEB Curtain Call [[1st] Let's create a machine learning web application using Responder and Keras [Overview] – Light Code Co., Ltd.](https://rightcode.co.jp/blog/information-technology/responder- keras-make-machine-learning-web-appsz9) Jinja2's custom filter to convert line breaks to --- A diary aimed at deprogramming beginners with Google App Engine + Python
Recommended Posts