Writing HTTP clients
 in PHP

David Buchmann

@dbu

@dbu

Márk Sági-Kazár

@sagikazarmark

@sagikazarmark

Introduction

  • HTTPlug
  • Client Library
  • Symfony Integration

HTTP

Request / Response

  • HTTP Server: Get request, reply with response.
  • HTTP Client: Send request, get response.

PSR-7

Interfaces for Request & Response

Immutable, withX methods return a copy

$request = new GuzzleHttp\Psr7\Request();
$request = $request
    ->withMethod('POST')
    ->withUri($uri)
    ->withBody($body)
;

var_dump(json_decode($response->getBody()));

?

?

Server

PSR-7 flow

create request

send request

HTTP Message Factory

Interface for message factory

 => decouple from message implementation

$request = $requestFactory->createRequest(
    $method,
    $url,
    $headers,
    $body
);
$request->withHeader(...);

?

Server

PSR-7 flow

create request

send request

Message Factory

HTTPlug

Interface for HTTP clients

 => decouple from client implementation

$response = $client
    ->sendRequest($request);

Server

PSR-7 flow

create request

send request

HTTPlug

TODO API

Simplistic API for the tutorial

Exercise: Simplistic Client

 

  • In clients/trivial-skeleton/bin/client
  • Bootstrap HTTPlug
  • Create an item (hardcoded content)
  • Then list all existing items

HTTPlug

Why?

  • Do not couple code to message/client implementation
  • Use guzzle 5 or guzzle 6
  • Or something else
  • Shared libraries: Do not force implementation on user

HTTPlug Plugins

  • Client independent plugin system
  • Only depends on PSR-7 request/response
  • Plugins provided for:
    • authentication
    • caching
    • logging
    • generic header manipulations
    • etc

HTTPlug Asynchronous

  • AsyncClient
  • Returns promise
$promise = $client->sendAsync($request);
$promise->then(
    [$this, 'onSuccess'],
    [$this, 'onFailure']
);
...
$promise->wait();

HTTPlug Discovery

  • Find implementation for HTTPlug client, message factory and stream factory implementations
  • Please allow to inject instance!
public function __construct(
    HttpClient $client = null, 
    MessageFactory $messageFactory = null
) {
    $this->client = $client ?:
        HttpClientDiscovery::find();
    $this->messageFactory = $messageFactory ?: 
        MessageFactoryDiscovery::find();
}

API Client Library

API client library

  • API over API
  • Custom or exactly the same domain model

API client library + HTTPlug

  • Use HTTP as transport layer
  • Don't force HTTP client implementation
  • If you need plugins, provide a client factory

Transport: HTTP

Serialization: JSON/XML/etc

Error handling (HTTP Status)

Domain model

Error handling (Domain)

How does it work?

Composer in a library

  • Require php-http/client-implementation
    Virtual package provided by all HTTPlug compliant clients
  • Recommended php-http/message factory-implementation
    Virtual package provided by php-http/message
  • Optionally php-http/discovery
  • require-dev e.g. php-http/guzzle6-adapter to test the library

Composer in an application

  • Require a client implementation (e.g. php-http/guzzle6-adapter)
  • Require a message factory implementation (php-http/message)

Exercise 1: Configuration

  • client/library-skeleton:
    • src/HttpClientFactory.php
    • src/TodoClient.php::__construct

Exercise 2: Get methods

  • client/library-skeleton/src/TodoClient.php:
    • getTodo
    • getTodoPage
  • Try using the command bin/get and bin/list

Exercise 3: Modify methods

  • client/library-skeleton/src/TodoClient.php:
    • createTodo
    • updateTodo
    • deleteTodo
  • Try using the commands bin/

Testing

What to test?

  • Domain operations
  • Domain exceptions
  • HTTP calls behind operations
  • Domain models

Exercise 1: Test get methods

  • client/library-skeleton/tests/TodoClientTest.php

Exercise 2: Test modify methods

  • client/library-skeleton/tests/TodoClientTest.php
Symfony is a trademark of Fabien Potencier. All rights reserved.

Bundle for Library

  • Configure services
  • Integration code (form types, validation, ...)

HttplugBundle

  • Configure clients
  • Configure plugins
  • Debug toolbar integration

Exercise 1: Symfony Bundle

  • clients/symfony-skeleton
  • src/Wsc/TodoBundle
    • Resources/config/services.xml
    • DependencyInjection/Configuration.php
    • DependencyInjection/WscTodoExtension.php

Exercise 2: Symfony Application

  • clients/symfony-skeleton
    • src/AppBundle/Controller/DefaultController
    • app/Resources/views/default/*

Outlook

PSR-15

HTTP factories

  • Upcoming FIG standard for HTTP factories
  • request, response, uri and stream body
<?php

$request = $factory->createReques('GET', '/api/user')
    ->withHeader('Authentication', 'Bearer token')
    ->WithHeader()
    //...
    ;

PSR-?

HTTP Client

  • Eventually, the client interface should be FIG standard
  • Guzzle et al could simply implement that interface

API Client Generator

OpenAPI spec to PHP code

https://github.com/jolicode/jane-openapi

Puli

  • Can be used as discovery strategy
  • Allows to have your custom client discovered
{
    "version": "1.0",
    "name": "php-http/guzzle6-adapter",
    "bindings": {
        "04b5a002-71a8-473d-a8df-75671551b84a": {
            "_class": "Puli\\Discovery\\Binding\\ClassBinding",
            "class": "Http\\Adapter\\Guzzle6\\Client",
            "type": "Http\\Client\\HttpClient"
        }
    }
}

Get help

They already use HTTPlug

Thank you!

We are glad for any feedback!

Thank you!

Writing HTTP clients in PHP

By David Buchmann

Writing HTTP clients in PHP

PSR-7 defines common interfaces for HTTP requests and responses but the libraries that need to send HTTP requests need more than that. HTTPlug defines an interface for HTTP clients and provides adapters for existing clients like Guzzle, as well as a plugin system operating directly on PSR-7. This tool chain allows building API clients that do not depend on a specific HTTP client at all.

  • 5,042