Hi Simone,

Thank you for your reply.

I have inserted inline my replies to specific assertions and comments,
but please continue to read on past these as there two concrete
proposals further down that address your concerns, as I understand
them.


> On 30 Mar 2018, at 14:34, Simone Bordet <simone.bor...@gmail.com 
> <mailto:simone.bor...@gmail.com>> wrote:
> 
> ...
> It is my understanding, and that of the ReactiveStreams (RS) people I
> spoke with, that both the RS API and the higher level API based on RS
> (such as those offered by Reactor and RxJava2) are supposed to expose
> to users only Publishers.

I can find no material supporting this assertion on reactive-streams.org 
<http://reactive-streams.org/>,
or anywhere else for that matter. This is not something that I have, or
any member of the HTTP Client team has, ever come across. We have had
review comments and feedback, on this list, from folk in the Reactive
Streams community ( that greatly improved interoperability ), and this
has never come up. Without supporting material, I cannot accept this
assertion.

Publisher and Subscriber are low level building blocks that can be used
by different Reactive Streams (RS) implementations. They were added to
Java SE 9 to provide interoperability between various RS
implementations. The HTTP Client API, being proposed for inclusion in
Java SE 11, can only directly reference other Java SE types, so uses
Publisher and Subscriber directly.

The HTTP Client API provides out-of-the-box Publisher and Subscriber
implementations for various common operations. Beyond that, custom
implementations can be used, or more likely Publishers and Subscribers
from RS implementations. Interoperability with these RS implementations
is of utmost importance.

At the end of a Flow, there must always be a Subscriber. Without it
there is no demand. Therefore, all RS APIs must provide Subscribers of
some sort or other.

> This may be counterintuitive at beginning: the typical case of
> InputStream to read content and OutputStream to write content are both
> represented with Publishers in the RS experience.
> The reason behind only offering Publishers is that Subscribers are
> more difficult to implement by regular developers;

I am surprised by this assertion. Our experience writing Publishers and
Subscribers has shown that the most difficult piece of code to write,
in a scalable thread-safe manner, is the Subscription::request.
Subscriptions are created and returned by a Publisher and usually have
an intimate relationship with their Publisher. I consider them part of
the Publishing-side. If a Publisher is not creating its own Subscription,
but instead returning a Subscription from an upstream Publisher, in a
chain, then some work can be avoided, but this is not always the case.
I believe that this could be the scenario that you are referring to, but
it is not always the case.

The HTTP Client deliberately does not expose its Publisher of response
body data. It instead subscribes the given Subscriber, on behalf of the
invoking code. There can, and must, be a single Subscriber.  This is a
simple and safe model, expected to be more readily understood by an
average Java developer ( than exposing a Publisher ). If Publishers are
better for interoperability with some RS implementations, then this
could be addressed by a small addition to the API, as proposed later in
this mail (see below).

> Processors even
> more so, so it is better that this task is left to library
> implementers such as the JDK, Reactor and RxJava2.

Again, similar to Publisher, these are highly context sensitive. For
some common "pass-through" scenarios then they can be relatively
straight forward, but not so when processing is required. I agree, I
think the average Java developer should not be expected to write these,
for common scenarios.

> It is therefore a surprise that the the HTTP client uses instead
> Subscriber for the response content.
> BodyHandler must return a BodySubscriber, which is-a Flow.Subscriber.

The HTTP Client has had this design for a couple of years now, and the
use of Subscriber ( of response body data ) has not surprised anyone,
to my knowledge. Again, folk in the Reactive Streams community are
aware of this API.

> This will force users that need to write non-trivial response content
> handling to implement a Subscriber, or most of the times a Processor
> (to convert the Subscriber back into a Publisher for consumption by
> other more "regular" RS APIs).

In many cases a simple single Subscriber ( sink ) will be a reasonable
choice. The HTTP Client provides a number of combinators and adapters
that demonstrate the composability of Subscribers. For interoperability
with other RS implementations, where the Flow adaptation is being done
through Publishers, I would expect folk to use something like RxJava2's
`io.reactivex.processors.PublishProcessor`.  In fact, Daniel just wrote
something similar in a recent test [1], a handler that makes the
response body available through a Publisher,
`BodyHandler<Publisher<List<ByteBuffer>>>`. It is a not a general
purpose Processor, but a Subscriber that makes its data available
through a Publisher.

> It also creates an asymmetry between read and write side, so that for
> example it is not possible, without writing a processor, to echo a
> response content as the next request content.

Sorry, the read-side and write-side are just different ( but use the
appropriate consumer and producer API from RS ). I don't see this as an
asymmetry. One can use the aforementioned `PublishProcessor` if echo is
required.

> Another difficult example is piping the response content to a
> WebSocket RS API (that would take a Publisher), and so on for
> basically all RS libraries out there.

Already answered above.

> I would like to suggest to review this: it is definitely possible to
> have BodyHandler refactored in this way:
> 
> public Publisher<T> apply(int statusCode, HttpHeaders responseHeaders,
> Publisher<ByteBuffer> responseContent);
> 
> Returning a Publisher that performs transformations on the
> responseContent Publisher is the heart of libraries such as Reactor
> and RxJava2, so this will be super simple for users of the API.

If one is using a RS API then maybe this would be easier, but not so
for other cases. I content that writing Publisher is not as easy as a
Subscriber, and we already know that a Subscriber is required. Such an
API would require developers, not using an RS API, to have to write a
Publisher and a Subscriber.

> Utility Publishers that currently discard, convert to string, save to
> file, etc. can be equally trivially implemented as they are now in the
> JDK.

Yes, probably, but doesn't provide anything more than we already have.
I don't believe that this is a simplification or improves
interoperability in any way. The one area that I do see some room for
improvement is the possibility of providing a Publishing handler out of
the box, so one does not need to reach beyond Java SE for such echo
use-cases. Daniel already has this code in the repository as a test [1],
so it would not take much work to provide an API similar to:

BodyHandlers {
..
public static BodyHandler<Publisher<List<ByteBuffer>>> ofPublisher() {...}
..
}

Does this satisfy your concern?

---

> With the occasion, I think that the statusCode parameter and the
> responseHeaders parameter can be replaced by a single HttpResponse
> parameter, which would be better for future maintenance in case other
> information needs to be provided.

We have considered similar ( a data carrier rather than explicit
parameters ) a number of times, but discounted it, since we found no
use-cases for additional information, and none have come to light. Is
a carrier, its instantiation per response, API footprint, warranted? To
date, we have not found it a limiting factor, but I agree that it
would be somewhat easier to evolve the API with such a carrier.

> The simple case that comes to mind is the HTTP version of the
> response, which may be different from that of the request and
> indicates server capabilities: this is currently not provided in the
> BodyHandler.apply() signature.

What use case have you in mind for this?

> So perhaps:
> 
> public Publisher<T> apply(HttpResponse response, Publisher<ByteBuffer>
> responseContent);
> 
> This change would also simplify the maintenance since BodySubscriber
> will be removed.

Your sketch API design throws out `BodySubscriber` that is at the core
of the whole API, it is responsible for conversation of response body
bytes to the higher-level object, and control flow through its
`CompletionStage` ( I don't see how you address control flow in your
sketch ). What you are suggesting is a significant change, not
prototyped, not incubated, not easier to use, and from what I can see
brings no improvements. You are proposing an alternative design based on
your assertions above ( some of which I disagree with ).


I accept that a carrier would be a more friendly for possible evolution,
maybe:

BodyHandlerInfo {
  int statusCode() {... }
  HttpHeaders responseHeaders { ... }
}

Does this satisfy your concern?

---

> My concern is that driving users of the JDK APIs outside of the
> familiar RS APIs offered by Reactor and RxJava2 where only Publishers
> are relevant, by forcing them to implement Subscribers and Processors
> (heck, I even have an IntelliJ warning telling me that I should not do
> that!), will cause confusion and be potential source of bugs - we all
> know non-blocking/async programming is hard.

No one is forcing anyone outside of RS APIs. They already support
Subscribers and Publishing Processors. They already have what is needed.

I have made two concrete proposals in this email that attempt to address
concerned that you have raised. Both represent improvements while
staying within the current design. Do these satisfy your concerns?

-Chris.

[1] 
http://hg.openjdk.java.net/jdk/sandbox/file/c59f684f1eda/test/jdk/java/net/httpclient/ResponsePublisher.java
 
<http://hg.openjdk.java.net/jdk/sandbox/file/c59f684f1eda/test/jdk/java/net/httpclient/ResponsePublisher.java>

Reply via email to