-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
State of async HTTP clients and promises #165
Comments
my understanding is that the PSR for promises would need to be restarted. promises might need to consider event loop, though "we" (as in, the async http client) is not really concerned with event loop, async http requests are useful in classic PHP applications as well to just parallelize several http requests. when promises get accepted, we can pick up the async http client topic again. for the state of a promises PSR, i guess it would be best to inquire with the fig, maybe on their mailing list. afaik its a tricky topic to get right, but maybe ideas/opinions/technology has evolved since the last attempt? |
I think we can close this, with fiber in php 8.1 async or sync can have the same API so this is no longer necessary, I believe next goal would be to deprecate async interface, create an async implementation of PSR 18 (with amp http client v3 by example) and add an example on how to do parallel request with fiber + psr 18 |
that sounds very interesting! i did not look into fiber yet. is it some sort of "threads"? so we could have the requests block but people use multiple fiber threads to do parallel requests? why would we need a specific client for it to work? afaik PSR-18 can not return a promise but only the real result. what does a client such as amp do different than e.g. guzzle that we can leverage here? |
Fibers can deal with async IO internally without having to deal with promises. This means that the existing PSR-18 interface can be used for both async and sync requests. Amp is working on v3 of amp and v5 of their http client which is based on fibers. This means that it will probably also be included in Symfony/http-client at some point, since they use amp internally. Resulting in an async fiber based psr-18 client. See examples of new http client here: I'dd say that the async client interface might still make sense for non-fiber based clients like e.g. guzzle at this point in time. Fiber based clients could even make the So even if there would be fiber based psr-18 clients, it might make sense to keep the interface nevertheless. |
thanks for that update @veewee it seems to me like we should eventually rework the doc section on promises now that fibers exist. just to make sure i understand correctly: when using fibers and PSR-18, from the PHP point of view, you'd start a fiber for each request and have that fiber blocked until the request is finished? |
It depends on the implementation. You indeed start a fiber for every request. After sending the request, there is a moment that the server must wait for a response. At that moment, the fiber is suspended until there is something to read on the socket. The implementation can be suspended again for example for chunked responses etc. The idea is that the event-loop keeps track of all these suspended states. After sending multiple requests, you can wait for all requests to be finished. None of these waits is blocking the other requests. Meaning that you can actually parse some responses before an other one has finished responding. This parsing can be done whilst the event-loop is waiting for a socket to become readable again. |
i stumbled over https://packagist.org/packages/symplely/hyper which implements psr-18. i did not have time to investigate much, but if i get the idea correctly, fibers might allow async programs to interact with a psr-18 client. |
oh, if i get it correctly, symplely/hyper depends on a php extension libuv and is not using fibers, so not really a general solution. |
https://github.com/veewee/reactphp-soap/blob/php-soap-psr18/src/Protocol/Psr18Browser.php This is an example implementation of a fully async fiber based react psr-18 client. It will probably be sufficient to provide a similar looking adapter somewherein a httplug package in order to make it composer requireable. |
one of these days i really need to catch up on fibers. but actually, do we even need to provide a special client? or would it be enough for the caller to call or what would the job of a psr-18 fiber adapter be? |
Both react and amp have their own fiber based http-client implementation, with their own response / request objects. use Http\Client\HttpClient;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use React\Http\Browser;
use function React\Async\await;
final class Psr18Browser implements HttpClient
{
public function __construct(
private Browser $browser
){
}
public function sendRequest(RequestInterface $request): ResponseInterface
{
return await(
$this->browser->request(
$request->getMethod(),
(string) $request->getUri(),
$request->getHeaders(),
(string) $request->getBody()
)
);
}
} As you can see, the interface looks as-is you were calling the API in a sync way. $response = $client->sendRequest(xxx); It will return a response, just like in a sync API - but react will make sure that it happens in a concurrent non-blocking way underneath. This is done by fibers. React provides an event-loop that waits for I/O and resumes the fiber when the data is available. This makes it possible to do multiple requests at once (since most of the time is just waiting for the response). The main take-away from above, is that it does I/O operations in a non-blocking way in a single thread. Since most of the time you are waithing for the request stream to be sent or the response stream being received, fibers make sure that other code can be executed whilst you are waiting for that I/O. Yet all happens in a single thread, so no code gets executed concurrently. It's all about waiting for the I/O. You can transform the API to an async (promise based) API like this: $promise = async(fn () => $client->sendRequest(xxx)); Yet, since it is all fiber based, I/O in both examples will be non-blocking. You will just have another way of dealing with the response from a PHP code point of view. This indeed means that there is no need for a new client interface in httplug and you could even remove the async and promise implementations in favour for the fiber based approach. To give an example, plugins/middleware might look like this: interface Plugin
{
public function handleRequest(RequestInterface $request, callable $next, callable $first): Response;
}
class SomePlugin implements Plugin
{
public function handleRequest(RequestInterface $request, callable $next, callable $first): Response
{
try {
$response = $next($request->withX());
} catch (NetworkException $e) {
log('xxx');
throw $e;
}
return $response->withX();
}
} This makes it possible to use the same plugins in both sync and async psr-18 implementations. $promise = async(fn() => $next($request->withX())); From this package's view, you dont really have to care if the implementation is fiber based or just regular PSR-18. It depends on the implementation if it behaves async or not. For fiber based implementations, this would run in "parallel". For regular PSR-18 implementations, this will run in "series" (from a I/O point of view of course - we dont have threads) $responses = parallel([
async(fn() => $client->request($a)),
async(fn() => $client->request($b)),
async(fn() => $client->request($c)),
]); More info about functions and components used above:
I hope this comment clearifies things. Because it's quite confusing, yet very easy :) |
Also see php-http/react-adapter#49 |
@veewee Thanks for the details explanation on PSR-18, we're looking into it at the moment, no promises, but with fibers PSR-18 and such are very much likely to be in reach. |
I'd say PSR-18 was pretty successful. Guzzle, Buzz, Symfony and others support it natively, and some others support it via adapters.
However, this still doesn't help you very much if you need async HTTP requests. Although there is
HttpAsyncClient
in this package, it is still not standard, not many clients support it without adapters and some adapters seem to be outdated: Guzzle adapters do not support Guzzle 7 yet (which added PSR-18 support but does not supportHttpAsyncClient
promises), Buzz does not supportHttpAsyncClient
and adapter was discontinued... From popular clients, only Symfony supports it natively. This will become better when Guzzle 7HttpAsyncClient
adapter is released, but still not perfect.So, will async HTTP clients (and promises which are requirement for async HTTP client PSR) ever become PSR standard and be directly supported in popular clients? What is current state of becoming standard? From quick (possibly incomplete) search through docs and closed issues, it seems there were some plans to make promise, event loop and async HTTP client PSR. However, discussions about this seem to have ended few years ago and some were "put on hold" until future.
The text was updated successfully, but these errors were encountered: