Nyholm / psr7

A super lightweight PSR-7 implementation

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to perform POST requests

HeavyThumper opened this issue · comments

I don't see any explicit examples for POST requests - particularly anything to show the correct usage for setting headers or the body. The *factory methods don't seem to expose anything for manipulating the headers or body. What I have below seems to function - but I'm certain this isn't correct form:

<?php
use Buzz\Client\Curl;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7\Request;

function Push_SSE( $channel, $message ) {
    $server_url = 'http://' . SSE_SERVER . "/pub?id=ip$channel";
    try {
        $psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
        $psr18Client = new \Buzz\Client\Curl( $psr17Factory );

        $pheaders = array(
                'Cache-Control' => 'no-cache',
                'Content-type' => 'text/event-stream; charset=utf-8'
                );

        $request = new \Nyholm\Psr7\Request( 'POST', $server_url, $headers = $pheaders, $body = $message );
        $response = $psr18Client->sendRequest( $request );
    } catch ( Exception $e ) {
        echo 'Error: ' . $e->getMessage();
    }
}
?>

Can you please show me the proper method?

Hi!

The code you have there is perfectly fine. You just lose a little bit of modularity because you are relying on the constructor of the Request object. This is not part of the PSR-7 standard, so you would not be able to swap it out for a different implementation down the line.

So instead of using \Nyholm\Psr7\Request directly, I would use the PSR-17 Factory like so:

<?php
use Buzz\Client\Curl;
use Nyholm\Psr7\Factory\Psr17Factory;

function Push_SSE( $channel, $message ) {
    $server_url = 'http://' . SSE_SERVER . "/pub?id=ip$channel";
    try {
        $psr17Factory = new Psr17Factory();
        $psr18Client = new Curl( $psr17Factory );

        // Assuming $message is a string, convert to Stream:
        $message = $psr17Factory->createStream($message);

        $request = $psr17Factory->createRequest('POST', $server_url)
            ->withHeader('Cache-Control', 'no-cache')
            ->withHeader('Content-type', 'text/event-stream; charset=utf-8')
            ->withBody($message);

        $response = $psr18Client->sendRequest( $request );
    } catch ( Exception $e ) {
        echo 'Error: ' . $e->getMessage();
    }
}
?>

Note that the output of the createRequest is a PSR-7 Request object, so we use the methods that have been defined for those to set the headers and body.

An other change: the factory is also used to create a Stream, because PSR-7 message bodies cannot be strings. Because the factory is used everywhere, you will not need a use statement for the Request anymore. (Also, because you use use there is no need to refer to classes by their full names in the rest of the code.)

The idea behind using the PSR standards is that you can swap out the Nyholm implementation for a different one. Maybe you prefer Diactoros, or Guzzle, or another framework down the line. So it is best to stick only to methods defined by the standard.

You could take this one step further and not include any specific implementations at all. I might rewrite your function to a class like this instead:

use \Psr\Http\Client\ClientInterface;
use \Psr\Http\Message\RequestFactoryInterface;
use \Psr\Http\Message\ResponseInterface;
use \Psr\Http\Message\StreamFactoryInterface;

final class SSEPusher {
    private string $serverUrlPattern;
    private ClientInterface $httpClient;
    private RequestFactoryInterface $requestFactory;
    private StreamFactoryInterface $streamFactory;

    public __constructor(string $serverUrlPattern, ClientInterface $httpClient, RequestFactoryInterface $requestFactory, StreamFactoryInterface $streamFactory) {
        $this->serverUrlPattern = $serverUrlPattern;
        $this->httpClient = $httpClient;
        $this->requestFactory = $requestFactory;
        $this->streamFactory = $streamFactory;
    }

    public push(string $channel, string $message): ResponseInterface {
        $requestUrl = \sprintf($this->serverUrlPattern, $channel);
        $message = $this->streamFactory->createStream($message);
        $request = $this->requestFactory->createRequest('POST', $requestUrl)
            ->withHeader('Cache-Control', 'no-cache')
            ->withHeader('Content-type', 'text/event-stream; charset=utf-8')
            ->withBody($message);

        return $this->httpClient->sendRequest($request);
    }
}

As you can see it only references the PSR interfaces. This means you could use any implementation, and mix and match depending on your need. This is the real power of the PSR standards.

If you want to use the Nyholm and Buzz PSR implementations, you can then call the class like this to get the same result as your code:

$client = new SSEPusher(
    'http://' . SSE_SERVER . '/pub?id=ip%s', // Using your URL here, we are using %s as a way for the class to insert the channel.
    new \Buzz\Client\Curl(new \Nyholm\Psr7\Factory\Psr17Factory()),
    new \Nyholm\Psr7\Factory\Psr17Factory(),
    new \Nyholm\Psr7\Factory\Psr17Factory()
);

$client->push('private_channel', 'First message.');
$client->push('private_channel', 'Second message.');
$client->push('public_channel', 'Not telling you what I pushed to the private channel!');

At any point could you decide to use a different PSR-17 or PSR-18 implementation, just by passing a different class into your own class. Modularity achieved!

I hope this gives you some ideas of how you can work with standard interfaces like PSR. Feel free to reply to this issue if you have any follow-up. I will be closing the issue, however, as there is no actual issue with the Nyholm implementation being raised.