REST

Dasbhoard

  • Les définitions réutilisables des canaux REST et des connexions sortantes sont d'abord créées dans le tableau de bord Web de la plateforme avant que votre code Python ne les utilise.

  • Grâce à cette approche, votre code se concentre exclusivement sur la logique métier au lieu de s'occuper des détails de bas niveau.

Accepter les appels REST

  • Utilisez self.request.payload pour accéder aux données d'entrée - c'est un objet dictionnaire créé par Zato à partir de la requête JSON analysée.
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class LogInputData(Service):
    """ Logs input data.
    """
    def handle(self):

        # Read input received
        user_id = self.request.payload['user_id']
        user_name = self.request.payload['user_name']

        # Store input in logs
        self.logger.info('uid:%s; username:%s', user_id, user_name)

L'invoquer:

$ curl localhost:11223/api/log-input-data -d '{"user_id":"123", "user_name":"my.user"}'

Dans les logs du serveur:

INFO - uid:123; username:my.user

Appeler les API REST

  • Toutes les données peuvent être préparées sous forme d'objets dict, y compris les données utiles, les paramètres de la chaîne de requête, les paramètres du chemin et les en-têtes HTTP.

  • Par exemple, si le chemin est /api/billing/{phone_no}, le code ci-dessous remplacera phone_no par 271637517 et le reste des paramètres par la chaîne de requête.

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class SetBillingInfo(Service):
    """ Updates billing information for customer.
    """
    def handle(self):

        # Python dict representing the payload we want to send across
        payload = {'billing':'395.7', 'currency':'EUR'}

        # Python dict with all the query parameters, including path and query string
        params = {'cust_id':'39175', 'phone_no':'271637517', 'priority':'normal'}

        # Headers the endpoint expects
        headers = {'X-App-Name': 'Zato', 'X-Environment':'Production'}

        # Obtains a connection object
        conn = self.out.rest['Billing'].conn

        # Invoke the resource providing all the information on input
        response = conn.post(self.cid, payload, params, headers=headers)

        # The response is auto-deserialised for us to a Python dict
        json_dict = response.data

        # Assign the returned dict to our response - Zato will serialise it to JSON
        # and our caller will get a JSON message from us.
        self.response.payload = json_dict

Réagir aux verbes REST

  • Implémenter handle_<VERB> pour réagir à des verbes HTTP spécifiques lors de l'acceptation de requêtes.
  • Si le service est invoqué avec un verbe qu'il n'implémente pas, le client API reçoit le statut "405 Method Not Allowed".
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class MultiVerb(Service):
    """ Logs input data.
    """
    def handle_GET(self):
        self.logger.info('I was invoked via GET')

    def handle_POST(self):
        self.logger.info('I was invoked via POST')

Ces deux-là recevront HTTP 200 :

$ curl -XGET localhost:11223/api/multi-verb -d '{"user_id":"123"}'
$ curl -XPOST localhost:11223/api/multi-verb -d '{"user_id":"123", "user_name":"my.user"}'

Mais celui-ci recevra HTTP 405 :

$ curl -XDELETE localhost:11223/api/multi-verb -d '{"user_id":"123"}'

Choisir les verbes REST à appeler

  • Lors de l'invocation d'API REST, chaque objet de connexion possède des méthodes représentant un verbe HTTP spécifique, par exemple .post, .get, .delete et les autres. Cela signifie qu'un seul objet de connexion peut être utilisé pour invoquer le même endpoint en utilisant plusieurs verbes.

  • La méthode ".send" est un alias de ".post"

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class MultiVerbCaller(Service):

    def handle(self):

        # Data to send
        payload = {'user_id': '123'}

        # Obtains a connection object
        conn = self.out.rest['REST Endpoint'].conn

        # Invoke the endpoint with POST
        response = conn.post(self.cid, payload)

        # Invoke the endpoint with GET
        response = conn.get(self.cid, payload)

        # Invoke the endpoint with DELETE
        response = conn.delete(self.cid, payload)

        # This is the same as .post
        response = conn.send(self.cid, payload)

Objets de demande et de réponse

  • Toutes les données et métadonnées sont disponibles via les attributs self.request et self.response. Les détails relatifs à la sécurité se trouvent dans self.channel.security.

Objet de la demande :

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class RequestObject(Service):

    def handle(self):

        # Here is all input data parsed to a Python object
        self.request.payload

        # Here is input data before parsing, as a string
        self.request.raw_request

        # Correlation ID - a unique ID assigned to this request
        self.request.cid

        # A dictionary of GET parameters
        self.request.http.GET

        # A dictionary of POST parameters
        self.request.http.POST

        # REST method we are invoked with, e.g. GET, POST, PATCH etc.
        self.request.http.method

        # URL path the service was invoked through
        self.request.http.path

        # Query string and path parameters
        self.request.http.params

        # This is a method, not an attribute,
        # it will return form data in case we were invoked with one on input.
        form_data = self.request.http.get_form_data()

        # Username used to invoke the service, if any
        self.channel.security.username

        # A convenience method returning security-related details
        # pertaining to this request.
        sec_info = self.channel.security.to_dict()

Objet de la réponse:

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class ResponseObject(Service):

    def handle(self):

        # Returning responses as a dict will make Zato serialise it to JSON
        self.response.payload = {'user_id': '123', 'user_name': 'my.user'}

        # String data can also be always be returned too,
        # e.g. because you already have data serialised to JSON or to another data format
        self.response.payload = '{"my":"response"}'

        # Sets HTTP status code
        self.response.status_code = 200

        # Sets HTTP Content-Encoding header
        self.response.content_encoding = 'gzip'

        # Sets HTTP Content-Type - note that Zato itself
        # sets it for JSON, you do not need to do it.
        self.response.content_type = 'text/xml; charset=UTF-8'

        # A dictionary of arbitrary HTTP headers to return
        self.response.headers = {
            'Strict-Transport-Security': 'Strict-Transport-Security: max-age=16070400',
            'X-Powered-By': 'My-API-Server',
            'X-My-Header': 'My-Value',
        }

Configuration CORS

  • Implémentez handle_OPTIONS et définissez les en-têtes CORS comme requis dans votre application.

  • L'implémentation réelle du service va vers d'autres méthodes, handle_POST, handle_GET, selon les besoins du service.

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class ConfiguringCORS(Service):

    def handle_POST(self):

        # Actual implementation goes here
        pass

    def handle_OPTIONS(self):

        # We only allow requests from this particular origin
        allow_from_name = 'Access-Control-Allow-Origin'
        allow_from_value = 'https://www.example.com'

        self.response.headers[allow_from_name] = allow_from

Returning responses other than JSON

  • Si les données affectées à self.response.payload sont une chaîne, Zato n'essaiera jamais de la sérialiser ou de l'inspecter de quelque manière que ce soit. De cette manière, vous pouvez renvoyer n'importe quel type de réponse autre que JSON, il suffit de le sérialiser vous-même en chaîne et de l'affecter à self.response.payload.

  • L'attribut self.response.content_type peut être utilisé pour définir le type de contenu correct pour le payload retourné.

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class ServiceCSV(Service):

    def handle(self):

        # We return CSV here
        csv_data = '1,2,3\n4,5,6'

        # Assign data to our response
        self.response.payload = csv_data

        # Let the caller know what we are returning
        self.response.content_type = 'text/csv'

Renvoi des pièces jointes

Utilisez self.response.payload pour définir le corps de la pièce jointe et l'en-tête HTTP Content-Disposition pour signaler aux clients que vous renvoyez une pièce jointe et pour indiquer son nom.

Dans l'exemple, une chaîne statique est renvoyée mais le contenu de la pièce jointe pourrait tout aussi bien être lu à partir d'un fichier, de S3, de SFTP ou de toute autre source de données.

# -*- coding: utf-8 -*-

from zato.server.service import Service

class MyService(Service):

    def handle(self):
        self.response.payload = 'Hello, this is an attachment'
        self.response.headers['Content-Disposition'] = 'attachment; filename=hello.txt'

Les prochaines étapes