php-http / httplug

HTTPlug, the HTTP client abstraction for PHP

Home Page:http://httplug.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

capability discovery

dbu opened this issue · comments

at symfonycon, i discussed with @joelwurtz and he formulated an idea i really think we should implement: a CapabilitiesInterface to be used on all clients. it would allow to detect whether the client can do full async requests or is just emulating them, and allow to detect whether a plugin is somewhere in the chain. the plugin client would just ask each of its plugin, and the plugins would have a constant with their capability name.

in the case of FriendsOfSymfony/FOSHttpCache#257 i could check to see if i need to wrap the client into a plugin client to get http error responses thrown as exceptions and similar things.

does anybody see big issues with that? otherwise i would love to prepare a PR for this.

No problem for me

It sounds a bit hacky to me. Like putting knowledge where it shouldn't be. Like replacing interfaces with one interface and attributes as capabilities?

Isn't it something that belongs to configuration?

Actually I think the plugin client shouldn't even be a dependency of FOSHttpCache. It should be a transparent thing. What if I decide to write my own client wrapper which catches all exceptions. It is just as good as the plugin.

Also: do you plan to rely on an exception handler somewhere in the client? So you don't check in your code if the request is 200 and is valid? Because I think it is your code's responsibility. Handling error cases accordingly is another question.

I also see a major complexity increase in this proposal.

Are there any similar solution out there? Because I haven't seen it in any Middleware-like solutions. Maybe we could do some research.

I don't say it's a no, but I am not completely convinced, at least the upper questions/concerns appeared.

Also: do you plan to rely on an exception handler somewhere in the
client? So you don't check in your code if the request is 200 and is
valid? Because I think it is your code's responsibility. Handling error
cases accordingly is another question.

I also see a major complexity increase in this proposal.

well, this is the reason why i proposed we always throw an exception to
avoid this problem. in my code, i don't want to both catch the exception
and check the response code - that feels cumbersome. if we would know
that there always is an exception, live would be easy. if we would know
there never is an exception, things would work as well. but not knowing
whether we get the exception or not is awkward.

if we decide the capabilities checks bring more hassle than anything
else, we need to document that. the interface currently is very vague as
to when an exception is thrown:
https://github.com/php-http/httplug/blob/master/src/HttpClient.php#L24

basically except for applications where you control the setup and put
the exception plugin, you should thus never rely on having an exception
and check the http status.

the other place where we thought it could be useful is to decide whether
to use the async interface of a client or not. if its an emulating
client, i would prefer to delay, but if its really async, i want to send
immediately. but this maybe should really be decided by the user and
never inside the library.

well, this is the reason why i proposed we always throw an exception to avoid this problem. in my code, i don't want to both catch the exception and check the response code

You don't need to catch an exception. You need to listen for the response code. If the user decides to put the exception plugin in the chain, it's his/her choice.

the interface currently is very vague as to when an exception is thrown

I agree, we need to improve that.

you should thus never rely on having an exception and check the http status.

What you should do is catching HttpExceptions IMHO. Which should be extended by any other HTTP related exceptions. But even better: catch all exceptions which implement our interface.

but this maybe should really be decided by the user and never inside the library.

I don't think the fact that it's emulating should have impact on your code. If it behaves like a sync client and you need sync request, use it that way. If it behaves like an async client .....I think you can guess.

My point is: you don't need to KNOW how it really BEHAVES, you only need to know the public behavior, which is: you can send a request sync/async.

What we can do to improve this:

  • Mention in the docs to check for behavior and always use which you really need
  • Remove emulation from adapters. Implement aync where possible, use EmulatedSync as a wrapper if needed (and vica versa). But this one adds complexity again.

When discussing with @dbu our goal was also to prevent to have a very complex client object, as when we need an http client we don't know if it's an adapter, a client or a plugin client or even a decorated one.

If we need to add some behaviors (logging, decoding, exception, ....) we will certainly add a plugin client around this http client.

However the http client can already be a PluginClient with the logging capability. So we may add another logging layer.

The risk is to have a PluginClient decorating a PluginClient (which can also decorate another PluginClient, etc ....)

Having some sort of capability engine will allow to optimise this case.

we don't know if it's an adapter

That's the point: you should not know that. At least not in the code where you use it. I still think this is a configuration detail. I don't think it's a problem if you require some configurations to be set in your client. Guzzle works the same way: there is a default handler stack, which you can modify, if you want. But it's still configuration.

With decent DI, it shouldn't be a problem at all. If you want this in plain PHP, you can still have a factory which allows for quick starting. It optionally accepts a client (rejects a plugin client?) or creates one using the discovery layer, configures the client when needed, done.

I think we would put knowledge this way where we shouldn't. Also, I wonder if we should explicitly rely on features provided by plugins?

Simple example:

We pass in requests, we expect responses. Exceptions might be thrown. That's the API we work with. If the Exception plugin is added to the plugin client, you can expect domain exceptions which extend our interface which we should catch anyway. Response code should also be checked. Then why do we need to know if an Exception plugin is added if we very likely catch it anyway? If someone adds something nasty to the plugin chain, so be it.

Don't think exception is a good example.

Think of a server which send chunked response (Transfer-Encoding: chunked)

Some adapter / client will know how to decode this (in fact all who use curl), however other one don't know how to decode this (like the SocketClient).

In first we say we should have the minimum logic in Adapter / Client and use decoration / plugin to handle those sort of thing.

So now i have a client which make request against a server which send chunked response.

How do i know / assure that the client / adapter of the user can do this or not ? Test every adapter / client ? documentation in my client ?

It may be not something to add in the main repository but knowing what can do an http client implementation is a crucial point IMO. (can it do ssl ? can it connect to unix socket ? can it handle 2.0 / 1.1 / 1.0 http requests / responses, etc ....)

Let's close this then ?

I would say, yes (at least for now).