[PYTHON] Créer un serveur REST (simple)

introduction

REST peut être utilisé de manière pratique pour la communication de données entre le serveur et le client. En ce qui concerne l'utilisation de REST, il existe des informations sur la création d'un client dans différents langages et frameworks. Cependant, du côté du serveur, il y a des informations telles que comment construire avec "JsonServer" ou Apache + PHP comme une maquette qui peut être facilement préparée, mais c'est une méthode pour accepter les inconvénients ou prendre du temps et des efforts. (Ce sera aussi loin que je pourrai trouver.)

Cette fois, j'ai essayé de créer un serveur qui se trouve au milieu de ce qui précède, ce qui vous permet de définir librement des services et de créer facilement avec un script. (Le but n'est pas d'exiger suffisamment de robustesse pour être publié sur le WEB, mais de créer votre propre service Raspberry Pi.)

Code source

C'est un code de ressource en direct.

restserver.py


#!/usr/bin/env python3

import http.server
import json
import threading
import sys,os
import time

class RestHandler(http.server.BaseHTTPRequestHandler):
    def do_OPTIONS(self):
        #Prend en charge la demande de contrôle en amont
        print( "options" )
        self.send_response(200)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE')
        self.send_header('Access-Control-Allow-Headers', '*')
        self.end_headers()

    def do_POST(self):
        print( "post" )
        local_path = self.path.strip("/").split("/")
        #Obtenir la demande
        content_len  = int(self.headers.get("content-length"))
        body = json.loads(self.rfile.read(content_len).decode('utf-8'))

        #Traitement des réponses
        if( local_path[0] == "dat" ):
            if(os.path.getsize('./dat.json')):
              with open('./dat.json', 'r') as json_open:
                json_load = json.load(json_open)

              json_load.update(**body)
              json_wraite = json.dumps(json_load, sort_keys=False, indent=4, ensure_ascii=False)
            else:
              json_wraite = json.dumps(body, sort_keys=False, indent=4, ensure_ascii=False)

            with open('./dat.json', 'w') as json_open:
                json_open.write(json_wraite)

            self.send_response(200)
            self.send_header('Access-Control-Allow-Origin', '*')
            self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE')
            self.send_header('Access-Control-Allow-Headers', '*')
            self.end_headers()
            return
        else:
            print( "no" )
            print( self.path )
            return

    def do_GET(self):
        print( "get" )
        local_path = self.path.strip("/").split("/")
        #Traitement des réponses
        if( local_path[0] == "dat" ):
            print( "dat" )
            if(os.path.getsize('./dat.json')):
              with open('./dat.json', 'r') as json_open:
                json_load = json.load(json_open)

            else:
              json_load = {}

            self.send_response(200)
            self.send_header('Access-Control-Allow-Origin', '*')
            self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE')
            self.send_header('Access-Control-Allow-Headers', '*')
            self.send_header('Content-type', 'application/json;charset=utf-8')
            self.end_headers()
            body_json = json.dumps(json_load, sort_keys=False, indent=4, ensure_ascii=False) 
            self.wfile.write(body_json.encode("utf-8"))
            return
        else:
            print( "no" )
            print( self.path )
            return

    def do_DELETE(self):
        print( "delete" )
        local_path = self.path.strip("/").split("/")
        if( local_path[0] == "dat" ):
            print( "dat" )

            with open('./dat.json', 'w') as file_open:
                pass

            self.send_response(200)
            self.send_header('Access-Control-Allow-Origin', '*')
            self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE')
            self.send_header('Access-Control-Allow-Headers', '*')
            self.end_headers()
            return
        else:
            print( "no" )
            print( self.path )
            return

def rest_server(port):
    httpd_rest = http.server.ThreadingHTTPServer(("", port), RestHandler)
    httpd_rest.serve_forever()


def main():
    #Démarrage du serveur
    port_rest  = 3333
    try:
        t1 = threading.Thread(target=rest_server,  args=(port_rest,),  daemon = True)

        t1.start()

        while True: time.sleep(1)

    except (KeyboardInterrupt, SystemExit):
        print("exit")
        sys.exit()

if __name__ == "__main__":
  main()

Le serveur HTTP est configuré sur le port 3333 en effectuant une analyse REST avec do_OPTIONS / do_POST / do_GET / do_DELETE de BaseHTTPRequestHandler de Python. L'opération est simple, elle met à jour (POST), récupère le contenu (GET), et initialise (DELETE) le fichier "dat.json" dans le répertoire courant.

OPTIONS? METTRE?

REST accepte généralement les quatre types de requêtes suivants.

Il devrait être créé en fonction de cela, mais j'ai décidé de ne pas préparer PUT car POST peut être utilisé comme substitut.

Pour les "OPTIONS" qui ne sont pas incluses dans les 4 types, en fonction des spécifications du navigateur, une demande OPTIONS sera émise avant la demande POST. Le "Access-Control-Allow-Methods" inclus dans l'en-tête de la réponse à cette requête OPTIONS décrit les méthodes que le serveur peut gérer et si POST est inclus, la requête POST est émise suite à la requête OPTIONS. Je vais. Cette opération s'appelle le contrôle en amont et il s'agit d'une opération de navigateur, il existe donc des solutions de contournement telles que la demande du côté serveur, mais si vous préparez votre propre serveur, il est plus facile de prendre en charge la requête OPTIONS, alors rendez-la compatible. J'ai fait.

Description de la fonction

Je vais expliquer chaque volume. do_OPTIONS

do_OPTIONS


    def do_OPTIONS(self):
        #Prend en charge la demande de contrôle en amont
        print( "options" )
        self.send_response(200)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE')
        self.send_header('Access-Control-Allow-Headers', '*')
        self.end_headers()

Créez une réponse à la requête OPTIONS décrite dans la section précédente. Il renvoie simplement une réponse.

do_POST

do_POST


    def do_POST(self):
        print( "post" )
        local_path = self.path.strip("/").split("/")
        #Obtenir la demande
        content_len  = int(self.headers.get("content-length"))
        body = json.loads(self.rfile.read(content_len).decode('utf-8'))

        #Traitement des réponses
        if( local_path[0] == "dat" ):
            if(os.path.getsize('./dat.json')):
              with open('./dat.json', 'r') as json_open:
                json_load = json.load(json_open)

              json_load.update(**body)
              json_wraite = json.dumps(json_load, sort_keys=False, indent=4, ensure_ascii=False)
            else:
              json_wraite = json.dumps(body, sort_keys=False, indent=4, ensure_ascii=False)

            with open('./dat.json', 'w') as json_open:
                json_open.write(json_wraite)

            self.send_response(200)
            self.send_header('Access-Control-Allow-Origin', '*')
            self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
            self.send_header('Access-Control-Allow-Headers', '*')
            self.end_headers()
            return
        else:
            print( "no" )
            print( self.path )
            return

Le processus POST écrit les données JSON reçues dans la demande POST dans un fichier. Nous suivrons le processus dans l'ordre du haut.

local_path = self.path.strip("/").split("/")

self.path contient les données URL de la requête. Divisez ceci par "/" et conservez-le dans le tableau. Par exemple, si la requête est "nom_serveur / aaa / bbb / ccc /", le chemin_local sera comme suit. local_path[0]='aaa' local_path[1]='bbb' local_path[2]='ccc'

content_len  = int(self.headers.get("content-length"))
body = json.loads(self.rfile.read(content_len).decode('utf-8'))

C'est un processus pour analyser les données json reçues avec la demande et les stocker dans les données de type dictionnaire.

#Traitement des réponses
if( local_path[0] == "dat" ):
    if(os.path.getsize('./dat.json')):
      with open('./dat.json', 'r') as json_open:
        json_load = json.load(json_open)

      json_load.update(**body)
      json_wraite = json.dumps(json_load, sort_keys=False, indent=4, ensure_ascii=False)
    else:
      json_wraite = json.dumps(body, sort_keys=False, indent=4, ensure_ascii=False)

    with open('./dat.json', 'w') as json_open:
        json_open.write(json_wraite)

    self.send_response(200)
    self.send_header('Access-Control-Allow-Origin', '*')
    self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
    self.send_header('Access-Control-Allow-Headers', '*')
    self.end_headers()
    return

Dans l'étape suivante, nous vérifierons local_path [0] et définirons l'API si c'est "dat". Dans l'exemple, seul local_path [0] est utilisé, mais l'API sera définie en vérifiant local_path [1] et local_path [2] dans l'ordre. Le reste est de simples opérations sur les fichiers. La raison pour laquelle vous vérifiez d'abord la taille du fichier est que si vous chargez un fichier vide dans json.load (), une erreur se produira, donc si le fichier est vide, les données reçues seront écrites dans le fichier telles quelles. (Les opérations sur les fichiers ici sont redondantes et confuses.)

La partie qui crée la réponse finale est la même que OPTIONS et la gestion des erreurs n'est pas prise en compte.

else:
    print( "no" )
    print( self.path )
    return

C'est à ce moment qu'une URL inattendue arrive. Normalement, il devrait renvoyer une erreur 404, mais comme ce n'est pas un service public, il est clairement omis.

do_GET

do_GET


    def do_GET(self):
        print( "get" )
        local_path = self.path.strip("/").split("/")
        #Traitement des réponses
        if( local_path[0] == "dat" ):
            print( "dat" )
            if(os.path.getsize('./dat.json')):
              with open('./dat.json', 'r') as json_open:
                json_load = json.load(json_open)

            else:
              json_load = {}

            self.send_response(200)
            self.send_header('Access-Control-Allow-Origin', '*')
            self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE')
            self.send_header('Access-Control-Allow-Headers', '*')
            self.send_header('Content-type', 'application/json;charset=utf-8')
            self.end_headers()
            body_json = json.dumps(json_load, sort_keys=False, indent=4, ensure_ascii=False) 
            self.wfile.write(body_json.encode("utf-8"))
            return
        else:
            print( "no" )
            print( self.path )
            return

La partie de base est la même que POST. Il élimine l'acquisition de données JSON à partir de la demande, lit le fichier et place ces données dans la réponse.

self.send_response(200)
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE')
self.send_header('Access-Control-Allow-Headers', '*')
self.send_header('Content-type', 'application/json;charset=utf-8')
self.end_headers()
body_json = json.dumps(json_load, sort_keys=False, indent=4, ensure_ascii=False) 
self.wfile.write(body_json.encode("utf-8"))

La différence avec POST est que Content-type est ajouté à l'en-tête et que les données sont écrites dans la partie du corps.

do_DELETE

do_DELETE


    def do_DELETE(self):
        print( "delete" )
        local_path = self.path.strip("/").split("/")
        if( local_path[0] == "dat" ):
            print( "dat" )

            with open('./dat.json', 'w') as file_open:
                pass

            self.send_response(200)
            self.send_header('Access-Control-Allow-Origin', '*')
            self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE')
            self.send_header('Access-Control-Allow-Headers', '*')
            self.end_headers()
            return
        else:
            print( "no" )
            print( self.path )
            return

DELETE ouvre simplement le fichier cible avec l'attribut w et le ferme sans rien faire pour en faire un fichier vide.

rest_server

rest_server


def rest_server(port):
    httpd_rest = http.server.ThreadingHTTPServer(("", port), RestHandler)
    httpd_rest.serve_forever()

C'est une fonction qui démarre simplement le serveur sur le port spécifié par l'argument.

main

main


def main():
    #Démarrage du serveur
    port_rest  = 3333
    try:
        t1 = threading.Thread(target=rest_server,  args=(port_rest,),  daemon = True)

        t1.start()

        while True: time.sleep(1)

    except (KeyboardInterrupt, SystemExit):
        print("exit")
        sys.exit()

J'essaye de démarrer le serveur dans un thread séparé. En outre, le programme se termine lorsqu'il reçoit une interruption de fin (Ctrl + C) du clavier.

Contrôle de fonctionnement

Lorsque vous démarrez le script, le serveur démarre sur le port 3333, essayez donc d'exécuter la commande suivante.

curl -X POST -H 'Content-Type:application/json' -d '{"key":"val"}' localhost:3333/dat
curl -X GET localhost:3333/dat
curl -X DELETE localhost:3333/dat

La commande est définie sur localhost car elle est exécutée à partir de la même machine que le serveur. Si vous utilisez une autre machine, essayez d'utiliser l'adresse IP de la machine qui sera le serveur.

la fin

J'ai pu construire un serveur REST pour le moment. Si vous souhaitez ajouter une API, vous pouvez de plus en plus imbriquer l'analyse de chemin. De plus, comme vous pouvez facilement appeler une autre commande, vous pouvez l'étendre afin qu'elle puisse également être utilisée pour la gestion du système.

Essayez-le.

Recommended Posts

Créer un serveur REST (simple)
Créer un serveur textlint simple
Créer un pseudo serveur d'API REST à l'aide de pages GitHub
Comment créer un simple script serveur / client TCP
Créer un serveur de socket de domaine Unix
Ecrire un serveur TCP super simple
Créer une application GUI simple en Python
Construisez un serveur WebDAV simple sur Linux
Créez une application Web simple avec Flask
Configurer un serveur HTTPS simple avec Python 3
Configurer un serveur HTTPS simple avec asyncio
Créons une API REST en utilisant SpringBoot + MongoDB
Démarrez un serveur Web Python simple avec Docker
Créer un modèle d'investissement dynamique simple en Python
Configurer un serveur SMTP simple en Python
Comment créer une API Rest dans Django
Créer un serveur "Hello World" (HTTP) dans Tornado
Créer un module Python
Créer un LV amorçable
Créer un environnement Python
Créer un bot slack
Créer une application Todo avec Django REST Framework + Angular
Configurer un serveur local simple sur votre Mac
Essayez de créer une application Todo avec le framework Django REST
Créez un serveur de musique domestique avec Centos8 + Universal Media Server
Créer un serveur Web en langage Go (net / http) (1)
Créez un faux serveur Minecraft en Python avec Quarry
[Vagrant] Configurer un serveur API simple avec python
Créer un plugin Wox (Python)
Créer un référentiel pypi local
Créer une fonction en Python
Créer un dictionnaire en Python
Un simple exemple de pivot_table.
Les débutants de Django créent des applications simples 3
Les débutants de Django créent des applications simples 1
Lancez un serveur WEB simple qui peut vérifier l'en-tête
Créer une page d'accueil avec django
Comment spécifier un serveur HTTP simple Python de répertoire public
[Python Kivy] Comment créer une simple fenêtre pop-up
Créer un tableau numpy python
Créer un fichier de données factice
Les débutants de Django créent des applications simples 2
Créez un outil d'analyse vidéo simple avec python wxpython + openCV
Créer un écran de connexion Django
Créez un environnement de développement Python simple avec VSCode et Docker Desktop
Créer une salle de classe sur Jupyterhub
Serveur HTTP simple pour python
Créer un répertoire avec python
Créez une application CRUD simple en utilisant la vue de classe générique de Django
Les débutants de Django créent des applications simples 5
Créer un packer ELF rudimentaire
J'ai essayé de créer un environnement serveur qui fonctionne sous Windows 10
[Langage C] [Linux] Essayez de créer une simple commande Linux * Ajoutez simplement! !!
Présentation de la création d'un socket serveur et de la création d'un socket client
J'ai essayé de créer un pointage de crédit simple avec régression logistique.
Les utilisateurs de Rails essaient de créer un moteur de blog simple avec Django
Créez un lot planifié simple à l'aide de l'image Python de Docker et de parse-crontab