Chapitre "Créer une classe Request / Response / View pour améliorer la visibilité" Le a été mis à jour.
Si vous voulez en savoir plus, veuillez "aimer" ou "me suivre" dans Book ;-)
Ce qui suit est un extrait du contenu du livre.
Il y a maintenant trois points de terminaison qui génèrent des réponses dynamiques, et workerthread.py
est maintenant proche de 200 lignes.
Même à ce stade, je fais beaucoup de choses différentes avec un seul fichier, donc même 200 lignes sont devenues un module désordonné avec beaucoup de mauvaise visibilité.
De plus, au fur et à mesure que vous faites évoluer cette application Web, vous aurez de plus en plus de points de terminaison.
Il est évident que la maintenance ne sera pas possible si vous l'ajoutez à chaque fois à workerthread.py
.
On peut dire qu'il est devenu nécessaire d'améliorer la visibilité de workerthread.py
en séparant les responsabilités et en divisant les fichiers.
En d'autres termes, il est temps ** que la saison du refactoring soit arrivée **.
Dans ce chapitre, nous allons supprimer «le traitement qui génère dynamiquement un corps de réponse pour chaque point final» vers un module externe.
Tout d'abord, découpons simplement le processus de génération HTML pour chaque point de terminaison dans un autre module.
Le nom du module à découper est «vues». En effet, il s'agit d'un module dont la responsabilité est uniquement de générer la partie vue (= corps de la requête), quelle que soit la situation HTTP, telle que la connexion ou l'analyse de l'en-tête.
study/workerthread.py
https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter16/workerthread.py#L50-L59
study/views.py
https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter16/views.py
study/workerthread.py
if path == "/now":
response_body, content_type, response_line = views.now()
elif path == "/show_request":
response_body, content_type, response_line = views.show_request(
method, path, http_version, request_header, request_body
)
elif path == "/parameters":
response_body, content_type, response_line = views.parameters(method, request_body)
Jusqu'à la dernière fois, j'ai écrit que le processus de génération de HTML était collant pour chaque chemin, mais j'ai d'abord décidé de couper cette partie à la fonction du module views
.
par ça,
--workerthread.py
reçoit la requête HTTP, l'analyse, obtient le contenu de la réponse de la fonction du module views
en fonction du chemin, construit la réponse HTTP et la renvoie au client.
--views.py
a une fonction pour chaque chemin, reçoit le contenu de la demande et renvoie le contenu de la réponse générée dynamiquement.
La tâche de "générer dynamiquement le contenu de la réponse en fonction du chemin" a été découpée en "vues".
study/views.py
import textwrap
import urllib.parse
from datetime import datetime
from pprint import pformat
from typing import Tuple, Optional
def now() -> Tuple[bytes, Optional[str], str]:
"""
Générer du HTML pour afficher l'heure actuelle
"""
html = f"""\
<html>
<body>
<h1>Now: {datetime.now()}</h1>
</body>
</html>
"""
response_body = textwrap.dedent(html).encode()
# Content-Spécifiez le type
content_type = "text/html; charset=UTF-8"
#Générer une ligne de réponse
response_line = "HTTP/1.1 200 OK\r\n"
return response_body, content_type, response_line
def show_request(
method: str,
path: str,
http_version: str,
request_header: dict,
request_body: bytes,
) -> Tuple[bytes, Optional[str], str]:
"""
Générer du HTML pour afficher le contenu de la requête HTTP
"""
html = f"""\
<html>
<body>
<h1>Request Line:</h1>
<p>
{method} {path} {http_version}
</p>
<h1>Headers:</h1>
<pre>{pformat(request_header)}</pre>
<h1>Body:</h1>
<pre>{request_body.decode("utf-8", "ignore")}</pre>
</body>
</html>
"""
response_body = textwrap.dedent(html).encode()
# Content-Spécifiez le type
content_type = "text/html; charset=UTF-8"
#Générer une ligne de réponse
response_line = "HTTP/1.1 200 OK\r\n"
return response_body, content_type, response_line
def parameters(
method: str,
request_body: bytes,
) -> Tuple[bytes, Optional[str], str]:
"""
Afficher le HTML affichant les paramètres POST
"""
#Renvoie 405 pour les requêtes GET
if method == "GET":
response_body = b"<html><body><h1>405 Method Not Allowed</h1></body></html>"
content_type = "text/html; charset=UTF-8"
response_line = "HTTP/1.1 405 Method Not Allowed\r\n"
elif method == "POST":
post_params = urllib.parse.parse_qs(request_body.decode())
html = f"""\
<html>
<body>
<h1>Parameters:</h1>
<pre>{pformat(post_params)}</pre>
</body>
</html>
"""
response_body = textwrap.dedent(html).encode()
# Content-Spécifiez le type
content_type = "text/html; charset=UTF-8"
#Générer une ligne de réponse
response_line = "HTTP/1.1 200 OK\r\n"
return response_body, content_type, response_line
Ce n'est pas non plus particulièrement difficile.
Je viens de lancer le processus de génération dynamique de la réponse qui a été initialement écrite dans workerthread.py
.
Il est bon de couper la fonction vues, mais comme c'est le cas maintenant, le nombre d'arguments est différent pour chaque fonction, "Le pseudonyme qui traite ce chemin a besoin des arguments de ceci et cela, et la fonction qui traite ce chemin a besoin des arguments de ceci et cela et cela ..." Et ainsi de suite, l'appelant doit connaître les détails de l'appelant.
Dans le monde de la programmation, on sait que le code source devient simple lorsqu'un module est fait de sorte que les détails de l'autre module ne soient pas connus autant que possible.
Dans la prochaine ÉTAPE, procédons un peu plus à la refactorisation et réalisons cela.
Maintenant, la classe WorkerThread
a besoin de connaître les détails de la fonction views
car elle ne peut pas être appelée sans savoir de quoi et combien d'arguments elle a besoin pour chaque fonction.
Un moyen simple de se débarrasser de cette situation est de ** "Je ne sais pas quel paramètre chaque fonction utilise, mais je vais tout passer quand même" **.
Jetons un coup d'œil au code source.
study/workerthread.py
https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter16-2/workerthread.py
study/views.py
https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter16-2/views.py
study/views.py
Commençons par views.py
def now(
method: str,
path: str,
http_version: str,
request_header: dict,
request_body: bytes,
) -> Tuple[bytes, Optional[str], str]:
def parameters(
method: str,
path: str,
http_version: str,
request_header: dict,
request_body: bytes,
) -> Tuple[bytes, Optional[str], str]:
Les arguments sont unifiés dans toutes les fonctions d'affichage afin que toutes les informations de demande puissent être reçues. Nous n'utilisons pas ces arguments dans la fonction, mais en les rendant disponibles, l'appelant n'a pas à penser à "ce qui est nécessaire et ce qui n'est pas nécessaire".
study/workerthread.py
Vient ensuite le côté qui appelle la fonction de vue.
#Correspondance entre les fonctions de chemin et de vue
URL_VIEW = {
"/now": views.now,
"/show_request": views.show_request,
"/parameters": views.parameters,
}
La correspondance entre les fonctions de chemin et de vue est définie comme une constante. Il s'agit d'un ** dictionnaire avec chemin comme clé et ** fonction de vue correspondant au chemin comme valeur.
Selon la langue, vous serez peut-être surpris de "définir une" fonction "comme valeur de dictionnaire (ou tableau associatif)" ou "d'assigner une" fonction "à une variable" comme décrit ci-dessus.
Mais en python, c'est un traitement légitime.
Les objets qui peuvent être traités comme des valeurs, comme l'affectation à des variables et le passage à des opérations et des fonctions (en tant qu'arguments et valeurs de retour), sont appelés ** objets de première classe **. En python ** tous les objets sont des objets primaires ** et les fonctions ne font pas exception.
Par conséquent, il est possible d'affecter une fonction à une variable ou de créer une fonction qui reçoit une fonction et renvoie une fonction.
Ce dernier est connu sous le nom de "métaprogrammation" et toute personne intéressée devrait le vérifier.
#S'il existe une fonction de vue correspondant au chemin, récupérez la fonction et appelez-la pour générer une réponse
if path in self.URL_VIEW:
view = self.URL_VIEW[path]
response_body, content_type, response_line = view(
method, path, http_version, request_header, request_body
)
path in self.URL_VIEW
vérifie si la clé dans le dictionnaire self.URL_VIEW
contient path
.
En d'autres termes, nous vérifions si la fonction de vue correspondant au chemin est enregistrée.
S'il a été enregistré, la valeur du dictionnaire correspondant à cette clé est acquise et affectée à la variable «view». Autrement dit, la variable «view» est affectée à la ** fonction de vue ** (plutôt que la valeur de retour de l'appel de la fonction de vue).
Dans la dernière ligne, view (~~)
est utilisé pour appeler la fonction affectée à la variable view
et obtenir la valeur de retour.
Il est intéressant de noter que ** toutes les fonctions de vue acceptent désormais les mêmes arguments (méthode, chemin, http_version, request_header, request_body
), ce qui fait abstraction de la fonction de vue. ** **
Auparavant, les arguments étaient différents pour chaque fonction, donc même si vous disiez "appeler la fonction de vue", vous ne pouviez pas l'appeler correctement à moins de savoir "quel type de fonction est la fonction". Cependant, en unifiant les arguments (= unifiant l'interface), ** "Je ne sais pas quelle est la fonction, mais je peux l'appeler quand même" **.
Cela élimine le besoin de branchement if selon le chemin (ou la fonction) dans workerthread
.
De cette manière, «il n'est pas nécessaire de traiter des choses concrètes en extrayant seulement une partie des propriétés communes des choses concrètes» s'appelle ** abstracting **. , C'est une technique très importante en programmation.
Dans ce cas, en unifiant l'interface des fonctions concrètes telles que now ()
show_rewuest ()
parameters
"Prend cinq arguments méthode, chemin, http_version, request_header, request_body
et renvoie deux valeurs response_body, response_line
"
En extrayant (= abstrait) uniquement la propriété, l'appelant
"Je ne sais pas combien de fonctions il s'agit, mais je l'appelle avec 5 arguments."
Cela signifie que cela peut être géré comme ça.
Ou vous pourriez dire "interface unifiée pour l'abstraction".
C'est bien que la fonction de vue ait une interface commune et que l'appelant ait une meilleure vue, mais il y a cinq arguments.
Le fait qu'une requête HTTP contienne beaucoup d'informations est un fait inévitable, mais il est difficile de les distribuer et de les stocker dans des variables séparées.
Alors, créons une classe qui exprime la requête HTTP et rassemblons les informations.
Cela simplifie également l'interface de la fonction d'affichage.
Chapitre "Créer une classe Request / Response / View pour améliorer la visibilité"
Recommended Posts