Ton API      plus rapide qu'Usain Bolt

👆Ton API après ce talk

Qu'est-ce qu'une

API ?

Application Programming Interface

ensemble normalisé de classes, de méthodes, de fonctions et de constantes qui sert de façade par laquelle un logiciel offre des services à d'autres logiciels

API c'est aussi le sigle de allocation parent isolé

Les avantages d'une API

Spécialisée dans un domaine et sur un use case particulier

Pas de couplage avec le code source

Détachement de la consommation de la donnée

Ouverture de fonctionnalités à des tiers

Les différents types d'API

  • REST

  • RPC

  • GRAPHQL

Remote Procedure Call

POST /sayHello HTTP/1.1
HOST: api.example.com
Content-Type: application/json

{"name": "Racey McRacerson"}
/* Signature */
function sayHello(name) {
  // ...
}

/* Usage */
sayHello("Racey McRacerson");
  • XML-RPC

  • JSON-RPC

  • Simple Object Access Protocol (SOAP)

Spécifications

REpresentational State Transfer

GET /myresource HTTP/1.1
HOST: api.example.com
Content-Type: application/json

[]
  • JSON
  • XML

Formats

  • JSON-API

  • OData

  • OpenAPI

Spécifications

Verbes HTTP

ressource == route

QraphQL

RPC-like

Et si on faisait notre API en PHP

Partons d'une application existante

https://github.com/Darkweak/UsainBoltAPI

L'authentification

composer req jwt-auth
jwt_passphrase=${JWT_PASSPHRASE:-$(grep ''^JWT_PASSPHRASE='' .env | cut -f 2 -d ''='')}
    echo "$jwt_passphrase" | openssl genpkey -out config/jwt/private.pem -pass stdin \
    -aes256 -algorithm rsa -pkeyopt rsa_keygen_bits:4096
    echo "$jwt_passphrase" | openssl pkey -in config/jwt/private.pem -passin stdin \
    -out config/jwt/public.pem -pubout
    setfacl -R -m u:www-data:rX -m u:"$(whoami)":rwX config/jwt
    setfacl -dR -m u:www-data:rX -m u:"$(whoami)":rwX config/jwt
authentication_token:
    path: /authentication_token
    methods: ['POST']
# ...
    firewalls:
        # ...
        main:
            anonymous: true
            lazy: true
            provider: app_user_provider
            json_login:
                check_path: /authentication_token
                username_path: email
                password_path: password
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure

    access_control:
        # ...
        - { path: ^/docs, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/authentication_token, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/, roles: IS_AUTHENTICATED_FULLY }

Mise en place de l'API

composer req api

On expose nos endpoints

// ...

use ApiPlatform\Core\Annotation\ApiResource;
// ...

/**
 * @ApiResource()
 * ...
 */
class MyClass

Et la sécurité ?

 * @ApiResource(
 *      collectionOperations={
 *          "post"={"security"="is_granted('IS_AUTHENTICATED_FULLY')"}
 *      },
 *      itemOperations={
 *          "put"={"security"="is_granted('ROLE_ADMIN') or user == object.author"}
 *      }
 * )

Sécurité

> Par resource

> Par endpoint

> Par rôles

Exemple

| Nom                      | Méthode | Operation  | Sécurité                                                 |
|:------------------------:|:-------:|:----------:|:--------------------------------------------------------:|
| La liste                 | GET     | collection | `-`                                                      |
| La création              | POST    | collection | L'utilisateur doit être authentifié                      |
| L'objet simple           | GET     | item       | `-`                                                      |
| La mise à jour           | PUT     | item       | L'utilisateur est admin ou est le créateur de la recette |
| La mise à jour partielle | PATCH   | item       | L'utilisateur est admin ou est le créateur de la recette |
| La suppression           | DELETE  | item       | L'utilisateur est admin ou est le créateur de la recette |
 * @ApiResource(
 *      collectionOperations={
 *          "get",
 *          "post"={"security"="is_granted('IS_AUTHENTICATED_FULLY')"}
 *      },
 *      itemOperations={
 *          "get",
 *          "patch"={"security"="is_granted('ROLE_ADMIN') or user == object.author"},
 *          "put"={"security"="is_granted('ROLE_ADMIN') or user == object.author"},
 *          "delete"={"security"="is_granted('ROLE_ADMIN') or user == object.author"}
 *      }
 * )
 * ...
 * @ApiResource(
 *      security="is_granted('ROLE_ADMIN')",
 *      collectionOperations={
 *          "get"={"security"="is_granted('IS_AUTHENTICATED_ANONYMOUSLY')"},
 *          "post",
 *      },
 *      itemOperations={
 *          "get"={
 *              "controller"=NotFoundAction::class,
 *              "read"=false,
 *              "output"=false,
 *          },
 *          "patch",
 *          "put"
 *      }
 * )
 * ...
| Nom                      | Méthode | Operation  | Sécurité                |
|:------------------------:|:-------:|:----------:|:-----------------------:|
| La liste                 | GET     | collection | `-`                     |
| La création              | POST    | collection | L'utilisateur est admin |
| La mise à jour           | PUT     | item       | L'utilisateur est admin |
| La mise à jour partielle | PATCH   | item       | L'utilisateur est admin |
| La suppression           | DELETE  | item       | L'utilisateur est admin |

Améliorer les perfs

Parce que PHP c'est leeeeeeent !!!

Cache

github.com/Darkweak/Souin

default_cache:
  port:
    web: 80
    tls: 443
  ttl: 10
reverse_proxy_url: 'http://reverse-proxy'
default_cache:
  headers:
    - Authorization
  providers:
    - all
  redis:
    url: 'redis:6379'
  regex:
    exclude: 'ARegexHere'
ssl_providers:
  - traefik
urls:
  'https:\/\/domain.com\/first-.+':
    ttl: 1000
  'https:\/\/domain.com\/second-route':
    ttl: 10
    headers:
    - Authorization
  'https?:\/\/mysubdomain\.domain\.com':
    ttl: 50
    headers:
    - Authorization
    - 'Content-Type'

Configuration

RESULTS
================================================================================
---- Global Information --------------------------------------------------------
> request count                                     101000 (OK=101000 KO=0     )
> min response time                                      0 (OK=0      KO=-     )
> max response time                                     56 (OK=56     KO=-     )
> mean response time                                     8 (OK=8      KO=-     )
> std deviation                                          4 (OK=4      KO=-     )
> response time 50th percentile                          8 (OK=8      KO=-     )
> response time 75th percentile                         10 (OK=10     KO=-     )
> response time 95th percentile                         15 (OK=15     KO=-     )
> response time 99th percentile                         21 (OK=21     KO=-     )
> mean requests/sec                                3884.615 (OK=3884.615 KO=-  )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                        101000 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Les problèmes avec REST

Under-fetching

Problème N+1

Over-fetching

Configuration

3 variables d'environnement à passer

CERT_FILE=/certs/localhost.crt

KEY_FILE=/certs/localhost.key

UPSTREAM=http://liendevotreapi

Vous utilisez websocket pour la synchro avec votre API ?

composer require mercure

Ça doit être super compliqué à mettre en place non ?

 * @ApiResource(
 *      ...
 *      mercure=true
 * )
 * ...

Configuration

6 variables d'environnement à passer au serveur

JWT_KEY=superkey
DEMO=1
ALLOW_ANONYMOUS=1
CORS_ALLOWED_ORIGINS=*
PUBLISH_ALLOWED_ORIGINS=http://localhost
ADDR=http://localhost:4000

Configuration

3 variables d'environnement à passer à l'api

MERCURE_PUBLISH_URL=http://mercure-url-innner
MERCURE_SUBSCRIBE_URL=https://mercure.domain.com
MERCURE_JWT_SECRET=superkey

Et on en fait quoi après ?

const mercureHub = 'https://mercure.domain.com/.well-known/mercure'
const eventSource = new EventSource(
    `${mercureHub}?topic=${encodeURIComponent('http://example.com/recipes/1')}`
);

eventSource.onmessage = event => {
    console.log(JSON.parse(event.data));
}

Promis c'est la seule fois que l'on fera du JS

Reste à faire

Le client

Tout

Sur l'API

Rien

Bon courage

Merci de votre attention

Ton API plus rapide qu'Usain Bolt(sans trop d'effort)

By darkweak

Ton API plus rapide qu'Usain Bolt(sans trop d'effort)

  • 487