[PYTHON] Publiez votre site Web avec responder + Gunicorn + Apache

introduction

Il existe un framework Web Python relativement nouveau (octobre 2018-) appelé responder. L'auteur est celui qui a fait des requêtes etc., et il semble que ce soit un framework comme les bons points de Flask et Falcon. Quand j'ai pensé à créer une simple page Web avec Python, j'ai découvert le répondeur et j'étais curieux à ce sujet, alors j'ai décidé de l'utiliser. Pour autant que je sache, il n'y avait aucune explication en utilisant Apache, donc je publierai ma propre méthode de construction au lieu d'un mémo. (S'il vous plaît laissez-moi savoir si vous en avez ...)

environnement

Il semblait que Nginx était plus facile à construire, mais j'ai décidé de le faire tel quel parce que c'était un serveur sur lequel Apache était installé à l'origine.

À propos, seul venv est utilisé pour l'outil de construction d'environnement Python. Après avoir créé un environnement avec venv, appliquer le fichier de paramétrage direnv suivant au répertoire de travail est pratique car il sera «activé» en même temps que «cd» dans le répertoire de travail.

.envrc


source <Chemin complet du fichier d'activation de venv>

STEP0: Ecrire un programme qui renvoie une réponse

L'objectif est de publier et d'exécuter les programmes suivants à l'échelle mondiale. Pour le programme lui-même, rendez-vous sur le site officiel 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()

Dans ce cas, par exemple, si vous accédez à / world avec GET, vous verrez Hello, world!, Ou si vous accédez à / testtesttest avec GET, vous verrez Hello, test test!.

ÉTAPE 1: Exécutez avec le serveur intégré du répondeur

Le répondeur a Uvicorn intégré en tant que serveur intégré. Tout d'abord, essayez de commencer par 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!

Vous pouvez voir que le serveur démarre automatiquement et peut être connecté simplement en exécutant le programme. Lorsque vous arrêtez le serveur, vous pouvez utiliser Ctrl + C comme écrit.

ÉTAPE 2: Déplacez-vous avec Gunicorn

Page officielle d'Uvicorn Il semble qu'il soit préférable d'utiliser Gunicorn pour l'environnement de production, alors déplaçons-le en se référant aux paramètres officiels. (Cela fait un moment que j'ai essayé d'écrire cet article, alors ne vous inquiétez pas, l'horodatage date d'il y a quelques mois.)

$ 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!

Les arguments lors du lancement de Gunicorn sont à peu près les suivants.

---k uvicorn.workers.UvicornWorker: Spécifiez Uvicorn comme classe de travail --main: api: Spécifiez le module à démarrer. La notation est "nom du module (nom du programme): nom de variable de responder.API () "

Créer un fichier de configuration Gunicorn

Les éléments de paramétrage Gunicorn peuvent également être lus à partir du fichier de paramétrage. Il est pratique d'avoir un fichier de paramètres plus tard, alors créez-le. Les arguments utilisés dans la commande ci-dessus sont minimes. Créez un fichier de paramètres en ajoutant ici l'emplacement d'enregistrement du fichier journal.

gunicorn.py


import multiprocessing
import os

name = "gunicorn"

accesslog = "<Nom de fichier pour écrire le journal d'accès>"
errorlog = "<Nom de fichier pour écrire le journal des erreurs>"

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

Voir Documents officiels pour chaque élément. La valeur du paramètre est imitée de celle de [Site de référence](# site de référence).

Pour appliquer ce fichier de configuration et lancer Gunicorn, utilisez la commande suivante.

$ gunicorn --config gunicorn.py main:api

ÉTAPE 3: exécutez Apache en tant que serveur proxy inverse

Enfin, configurez-vous pour vous connecter via Apache. Une fois connecté à http://example.com/ sur lequel Apache est en cours d'exécution, configurez Gunicorn pour qu'il utilise le proxy vers http: // localhost: 8000.

Paramètres côté Apache pour proxy vers Gunicorn

Commencez par créer un fichier de configuration pour le proxy inverse.

/etc/apache2/conf-available/responder.conf


ProxyRequests Off
ProxyPass "/" "http://localhost:8000/"
ProxyPassReverse "/" "http://localhost:8000/"

Activez le fichier de configuration et les modules liés au proxy.

$ sudo a2enconf responder
$ sudo a2enmod proxy_http proxy

Rechargez après avoir vérifié que le fichier de paramètres est correctement décrit.

$ sudo apache2ctl configtest
Syntax OK

$ sudo systemctl reload apache2ctl

Paramètres de démarrage automatique de Gunicorn

Pour faciliter le démarrage automatique, configurez le démarrage de Gunicorn pour qu'il soit géré par systemd. Commencez par créer un fichier de paramètres.

/etc/systemd/system/webapp.service


[Unit]
Description=gunicorn - responder
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=<gunicorn.py et main.Chemin complet du répertoire où se trouve py>
ExecStart=<Chemin complet de Gunicorn> --config <gunicorn.chemin complet de py> main:api
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

J'expliquerai également les éléments difficiles à comprendre.

--Nom de fichier: .service est très bien. Un exemple d'utilisation dans une commande est «systemctl restart ».

Après la création, configurez le service pour qu'il démarre et démarre automatiquement.

$ sudo systemctl start webapp.service
$ sudo systemctl enable webapp.service

Juste au cas où, assurez-vous que cela fonctionne correctement.

$ sudo systemctl status webapp.service
● webapp.service - gunicorn - responder
   Loaded: loaded (/etc/systemd/system/webapp.service; enabled; vendor preset: enable
   Active: active (running) 
(Omis ci-dessous)

Le démarrage et le démarrage automatique ont réussi.

Achevée!

Si les paramètres sont corrects, vous devriez pouvoir vous connecter avec curl ou un navigateur.

$ curl http://example.com/world
Hello, world!

non résolu? problème

Pour une raison quelconque, l'en-tête html Content-Type disparaît lors du passage par Apache. (Il est correctement attaché lorsque le serveur est démarré depuis Gunicorn. Phénomène mystérieux) En guise de mesure provisoire, j'ajoute du code qui force Content-Type: text / html; charset = UTF-8 lors du renvoi d'une réponse.

Note bonus

En passant, j'aimerais écrire la grammaire du répondeur que j'ai personnellement trouvé utile / googlé et difficile à trouver.

Comment écrire le routage

Il semble y avoir plus de routage (et de traitement) que d'écrire simplement au début.

Créer une classe+Configurer le routage collectivement plus tard.py


import responder

api = responder.API()


class Who:
	def on_get(self, req, resp, *, who):
		#Lorsque GET, ce processus se fait automatiquement
    	resp.text = f"Hello, {who}!"
    
    async def on_post(self, req, resp, *, who):
    	#Au moment du POST, ce processus se fait automatiquement
    	data = await req.media()
    	resp.text = f"{data}"

#Paramètres de routage
api.add_route("/{who}", Who)

if __name__ == '__main__':
    api.run()

Le style d'écriture au début est-il semblable à Flask, et le style d'écriture qui vient d'être écrit est le style Falcon? J'ai écrit dans "Description en utilisant ʻon_get` etc. dans la classe + Paramètres de routage avec décorateur", mais c'est peut-être une mauvaise idée ...

Ajouter un filtre Jinja2

Il peut être utilisé lorsque vous souhaitez définir un chemin de fichier statique ou écrire un processus répété plusieurs fois.

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()
#Ajouter un filtre
# v1.Pour x
api.jinja_env.filters.update(
	css = jinja_myfilter.css_filter, 
	html_list = jinja_myfilter.list_filter
)
# v2.Pour x(2020/05/12 Addendum)
# (Parce qu'il y a un trait de soulignement_env semble être traité comme une valeur interne,
#Je n'ai pas trouvé d'autre moyen de le spécifier en regardant la 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 = ["Objet 1", "Point 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 }}">
<!--Il est traité par Jinja2 et devient comme suit
  <link rel="stylesheet" type="text/css" href="./static/css/form.css">
-->

{% autoescape false %}
{{ param | html_list }}
{% endautoescape %}
<!--Il est traité par Jinja2 et devient comme suit
  <ul>
  <li>Objet 1</li>
  <li>Point 2</li>
  </ul>
-->

Si une chaîne de caractères contenant une balise html est renvoyée, l'échappement automatique fonctionnera à moins qu'il ne soit inclus entre {% autoescape false%} à {% endautoescape%}. Cependant, si les paramètres que vous passez sont des entrées utilisateur, les paramètres seront bien sûr sortis sans être échappés, alors soyez prudent. Est-il sûr de le traiter en utilisant html.escape () etc. dans le filtre?

Site de référence

Comment déployer Responder avec Uvicorn ou Gunicorn - Je veux parler de technologie et de bodge Pour une introduction au répondeur Python ... Recherche préliminaire --Qiita Lancer l'application avec Django + Nginx + Gunicorn | WEB Curtain Call [[1er] Créons une application Web d'apprentissage automatique en utilisant Responder et Keras [Création d'une vue d'ensemble] - Light Code Co., Ltd.](https://rightcode.co.jp/blog/information-technology/responder- keras-make-machine-learning-web-appsz9) Filtre personnalisé de Jinja2 pour convertir les sauts de ligne en --- Un journal destiné à déprogrammer les débutants avec Google App Engine + Python

Recommended Posts

Publiez votre site Web avec responder + Gunicorn + Apache
Travailler avec des sites Web à l'aide de Python_Webbrowser
Publiez votre propre bibliothèque Python sur Homebrew
Essayez de créer un site Web simple avec responder et sqlite3
Visualisez les fluctuations des nombres sur les sites Web avec Datadog
Premiers pas avec apache2