JEP 321: HTTP Client - Use of Flow.Subscriber and Flow.Publisher

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

JEP 321: HTTP Client - Use of Flow.Subscriber and Flow.Publisher

James Roper
Hi all,

I wanted to start a discussion about the use of Flow.Subscriber and Flow.Publisher in JEP 321 (HTTP Client API).

It seems that users are required to implement their own publishers and subscribers, that is, they can't take a Flow.Publisher or Flow.Subscriber provided by another reactive streams implementation, and pass it on to the HttpClient API. The reason for this is that the HttpClient API doesn't accept Flow.Publisher/Flow.Subscriber, rather it extends them in HttpRequest.BodyPublisher and HttpResponse.BodySubscriber, and then requires the user to return instances of those sub interfaces from their BodyHandlers. Let's say I have a database driver that produces a Flow.Subscriber for consuming a stream to be stored in the database, and I want to plumb a response body into that database subscriber. I can't return this from an HttpResponse.BodyHandler it requires me to return a HttpResponse.BodySubscriber, not a Flow.Subscriber. Of course, users can implement their own HttpResponse.BodySubscriber that delegates onSubscribe/onNext/onComplete/onError to the databases Flow.Subscriber, but needing to do this every time does not provide a great developer experience.

In order for reactive streams implementations to integrate with each other, Flow.Subscriber and Flow.Publisher should only be extended if the implementation is providing its own implementation of that interface and doesn't expect end users to implement it. For cases where an implementation expects users either to pass that sub interface, or return it from a method to that they implement as is the case for BodyHandler, then it has to be a Flow.Subscriber or Flow.Publisher, not a sub interface.

So perhaps HttpResponse.BodySubscriber should be modified to, rather than extending Flow.Subscriber, have a getSubscriber() method that returns a Flow.Subscriber.

Alternatively, the API could be inverted, for example, HttpResponse.BodyHandler.apply could be modified to take three parameters, the status code, the HttpHeaders, and a Flow.Publisher, and the return value could be CompletionStage<T>. This approach would actually maximise interoperability, since a lot of reactive streams implementations are publisher centric. For example, to my knowledge RxJava only provides limited support for creating and transforming data using a Flow.Subscriber, whereas if you give RxJava a Flow.Publisher, you can then do the full range of operations (eg map) that RxJava supports on that stream (someone with a better understanding of RxJava correct me if I'm wrong).

There's probably many other solutions, these are just two that I've thought of.

There is also a broader discussion that needs to be had in the reactive streams community over whether there should be a bias towards everything returning/accepting publishers, or if implementations should evenly support both. Supporting one or the other arbitrarily will have interoperability implications, and while it's definitely beyond the scope of JEP 321 to decide on that, it's something that we should keep in mind.

Regards,

James

--
James Roper
Senior Octonaut

Lightbend – Build reactive apps!
Twitter: @jroper

Reply | Threaded
Open this post in threaded view
|

Re: JEP 321: HTTP Client - Use of Flow.Subscriber and Flow.Publisher

Chris Hegarty
James,

Thanks for taking the time to look at this, and sending your thoughts.

On 08/12/17 00:30, James Roper wrote:
 > Hi all,
 >
 > I wanted to start a discussion about the use of Flow.Subscriber and
 > Flow.Publisher in JEP 321 (HTTP Client API).
 >
 > It seems that users are required to implement their own publishers and
 > subscribers, that is, they can't take a Flow.Publisher or
 > Flow.Subscriber provided by another reactive streams implementation, and
 > pass it on to the HttpClient API. The reason for this is that the
 > HttpClient API doesn't accept Flow.Publisher/Flow.Subscriber, rather it
 > extends them in HttpRequest.BodyPublisher and
 > HttpResponse.BodySubscriber, and then requires the user to return
 > instances of those sub interfaces from their BodyHandlers. ...

Great point. I think we can address this with straight forward adapters.
For example:

   public interface BodyPublisher extends Flow.Publisher<ByteBuffer> {

      /**
      * Returns a request body publisher whose body is retrieved from the
      * given {@code Flow.Publisher}. The returned request body publisher
      * has an unknown content length.
      *
      * @apiNote This method can be used as an adapter between {@code
      * BodyPublisher} and {@code Flow.Publisher}.
      *
      * @param publisher the publisher responsible for publishing the body
      * @return a BodyPublisher
      */
     static BodyPublisher fromPublisher(Flow.Publisher<ByteBuffer>
publisher) {
         ...
     }

     ...

    public BodySubscriber<T> apply(int statusCode, HttpHeaders
responseHeaders);

      /**
       * Returns a response body handler that returns a {@link
BodySubscriber
       * BodySubscriber}{@code <Void>} obtained from {@link
       * BodySubscriber#fromSubscriber(Subscriber)}.
       *
       * @apiNote This method can be used as an adapter between {@code
       * BodySubscriber} and {@code Flow.Subscriber}.
       *
       * <p> For example:
       * <pre> {@code
       * TextSubscriber subscriber = ...;  // accumulates bytes and
transforms them into a String.
       * Supplier<String> result = subscriber::getTextResult;
       *
       * CompletableFuture<String> cf =  client
       *         .sendAsync(request, BodyHandler.fromSubscriber(subscriber))
       *         .thenApply((response -> result.get()));
       * String text = cf.join();
       * }</pre>
       *
       * @param subscriber the subscriber
       * @return a response body handler
       */
      public static BodyHandler<Void> fromSubscriber(Subscriber<? super
List<ByteBuffer>> subscriber) {
          ...
      }

      // Add an equivalent BodySubscriber ...


This would allow the API to retain its Flow specific types ( that add
additional HTTP specific and API behavior ), while interacting, without
much fuss, with regular Publishers and Subscribers.

-Chris.
Reply | Threaded
Open this post in threaded view
|

Re: JEP 321: HTTP Client - Use of Flow.Subscriber and Flow.Publisher

James Roper
Hi Chris,

This looks like a straight forward way to solve the problem with minimal disruption from the existing API. Can I make a few suggestions though?

We could add a contentLength parameter to fromPublisher, to allow Flow.Publishers where the content length is known to be easily converted to BodyPublisher:

static BodyPublisher fromPublisher(Flow.Publisher<ByteBuffer> publisher, int contentLength) {
    ...
}

This would mean if you were receiving a servlet request body and publishing it to another location, then you could do something like this (this uses a reactive streams implementation on top of the servlet API that I wrote):

HttpServletRequest request = ...
long contentLength = -1;
if (request.getHeader("Content-Length") != null) {
  contentLength = Long.parseLong(request.getHeader("Content-Length"));
}
Publisher<ByteBuffer> publisher = new RequestPublisher(request.startAsync(), 8192);

HttpRequest clientRequest = HttpRequest.newBuilder(target)
  .POST(BodyPublisher.fromPublisher(publisher, contentLength))
  .build()

Perhaps the method could be overloaded for both supplying and not supplying a content length.

Similarly, I think a fromSubscriber API that accepted a CompletionStage<T> would be a little more fluent than having to supply it externally:

public static <T> BodyHandler<T> fromSubscriber(Subscriber<? super List<ByteBuffer>> subscriber, CompletionStage<T> bodyFuture) {
  ...
}

Then you could have something like this:

TextSubscriber subscriber = ...;  // accumulates bytes and transforms them into a CompletionStage<String>.
CompletionStage<String> result = subscriber.getTextResult();

CompletableFuture<String> cf =  client

  .sendAsync(request, BodyHandler.fromSubscriber(subscriber, result));
String text = cf.join();

Likewise, this could be an overload of fromSubscriber if we want the option of not specifying a body future.

One thing I think needs to be carefully specified is, if the method doesn't accept a CompletionStage, when/how the CompletionStage returned from send is redeemed.

Regards,

James

On 9 December 2017 at 04:31, Chris Hegarty <[hidden email]> wrote:
James,

Thanks for taking the time to look at this, and sending your thoughts.

On 08/12/17 00:30, James Roper wrote:
> Hi all,
>
> I wanted to start a discussion about the use of Flow.Subscriber and
> Flow.Publisher in JEP 321 (HTTP Client API).
>
> It seems that users are required to implement their own publishers and
> subscribers, that is, they can't take a Flow.Publisher or
> Flow.Subscriber provided by another reactive streams implementation, and
> pass it on to the HttpClient API. The reason for this is that the
> HttpClient API doesn't accept Flow.Publisher/Flow.Subscriber, rather it
> extends them in HttpRequest.BodyPublisher and
> HttpResponse.BodySubscriber, and then requires the user to return
> instances of those sub interfaces from their BodyHandlers. ...

Great point. I think we can address this with straight forward adapters.
For example:

  public interface BodyPublisher extends Flow.Publisher<ByteBuffer> {

     /**
     * Returns a request body publisher whose body is retrieved from the
     * given {@code Flow.Publisher}. The returned request body publisher
     * has an unknown content length.
     *
     * @apiNote This method can be used as an adapter between {@code
     * BodyPublisher} and {@code Flow.Publisher}.
     *
     * @param publisher the publisher responsible for publishing the body
     * @return a BodyPublisher
     */
    static BodyPublisher fromPublisher(Flow.Publisher<ByteBuffer> publisher) {
        ...
    }

    ...

   public BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders);

     /**
      * Returns a response body handler that returns a {@link BodySubscriber
      * BodySubscriber}{@code <Void>} obtained from {@link
      * BodySubscriber#fromSubscriber(Subscriber)}.
      *
      * @apiNote This method can be used as an adapter between {@code
      * BodySubscriber} and {@code Flow.Subscriber}.
      *
      * <p> For example:
      * <pre> {@code
      * TextSubscriber subscriber = ...;  // accumulates bytes and transforms them into a String.
      * Supplier<String> result = subscriber::getTextResult;
      *
      * CompletableFuture<String> cf =  client
      *         .sendAsync(request, BodyHandler.fromSubscriber(subscriber))
      *         .thenApply((response -> result.get()));
      * String text = cf.join();
      * }</pre>
      *
      * @param subscriber the subscriber
      * @return a response body handler
      */
     public static BodyHandler<Void> fromSubscriber(Subscriber<? super List<ByteBuffer>> subscriber) {
         ...
     }

     // Add an equivalent BodySubscriber ...


This would allow the API to retain its Flow specific types ( that add
additional HTTP specific and API behavior ), while interacting, without
much fuss, with regular Publishers and Subscribers.

-Chris.



--
James Roper
Senior Octonaut

Lightbend – Build reactive apps!
Twitter: @jroper

Reply | Threaded
Open this post in threaded view
|

Re: JEP 321: HTTP Client - Use of Flow.Subscriber and Flow.Publisher

Chris Hegarty
James,

On 11/12/17 00:47, James Roper wrote:
 > Hi Chris,
 >
 > This looks like a straight forward way to solve the problem with minimal
 > disruption from the existing API. Can I make a few suggestions though?

Of course, your input here is much appreciated.

 > We could add a contentLength parameter to fromPublisher, to allow
 > Flow.Publishers where the content length is known to be easily converted
 > to BodyPublisher:
 >
 > static BodyPublisher fromPublisher(Flow.Publisher<ByteBuffer> publisher,
 > int contentLength) {
 >      ...
 > }

Good idea. Added ( as can be seen below ).

 > This would mean if you were receiving a servlet request body and
 > publishing it to another location, then you could do something like this
 > (this uses a reactive streams implementation on top of the servlet API
 > that I wrote):
 >
 > HttpServletRequest request = ...
 > long contentLength = -1;
 > if (request.getHeader("Content-Length") != null) {
 >    contentLength = Long.parseLong(request.getHeader("Content-Length"));
 > }
 > Publisher<ByteBuffer> publisher = new
 > RequestPublisher(request.startAsync(), 8192);
 >
 > HttpRequest clientRequest = HttpRequest.newBuilder(target)
 >    .POST(BodyPublisher.fromPublisher(publisher, contentLength))
 >    .build()

Nice.

 > Perhaps the method could be overloaded for both supplying and not
 > supplying a content length.

I think an overload is justified here. Added.

 > Similarly, I think a fromSubscriber API that accepted a
 > CompletionStage<T> would be a little more fluent than having to supply
 > it externally:

Daniel and I discussed this too, but I opted to leave it out initially
for simplicity. I think if we have two overloads, then the simple case
can still be supported with little ceremony, while allowing a more
powerful variant.

 > public static <T> BodyHandler<T> fromSubscriber(Subscriber<? super
 > List<ByteBuffer>> subscriber, CompletionStage<T> bodyFuture) {
 >    ...
 > }
 >
 > Then you could have something like this:
 >
 > TextSubscriber subscriber = ...;  // accumulates bytes and transforms
 > them into a CompletionStage<String>.
 > CompletionStage<String> result = subscriber.getTextResult();
 >
 > CompletableFuture<String> cf =  client
 >    .sendAsync(request, BodyHandler.fromSubscriber(subscriber, result));
 > String text = cf.join();
 >
 > Likewise, this could be an overload of fromSubscriber if we want the
 > option of not specifying a body future.
 >
 > One thing I think needs to be carefully specified is, if the method
 > doesn't accept a CompletionStage, when/how the CompletionStage returned
 > from send is redeemed.

Hmmm... this could be tricky. I think we can avoid the scenario
completely by accepting a finishing function that can generate/return
the value to use for completion, rather than the CF itself.

Here is an outline of all of the above:

   public interface BodyPublisher extends Flow.Publisher<ByteBuffer> {

     /**
      * Returns a request body publisher whose body is retrieved from the
      * given {@code Flow.Publisher}. The returned request body publisher
      * has an unknown content length.
      *
      * @apiNote This method can be used as an adapter between {@code
      * BodyPublisher} and {@code Flow.Publisher}, where the amount of
      * request body that the publisher will publish is unknown.
      *
      * @param publisher the publisher responsible for publishing the body
      * @return a BodyPublisher
      */
     static BodyPublisher fromPublisher(Flow.Publisher<? extends
ByteBuffer> publisher) { ... }

     /**
      * Returns a request body publisher whose body is retrieved from the
      * given {@code Flow.Publisher}. The returned request body publisher
      * has the given content length.
      *
      * <p> The given {@code contentLength} is a positive number, that
      * represents the exact amount of bytes the {@code publisher} must
      * publish.
      *
      * @apiNote This method can be used as an adapter between {@code
      * BodyPublisher} and {@code Flow.Publisher}, where the amount of
      * request body that the publisher will publish is known.
      *
      * @param publisher the publisher responsible for publishing the body
      * @param contentLength a positive number representing the exact
      *                      amount of bytes the publisher will publish
      * @throws IllegalArgumentException if the content length is
      *                                  non-positive
      * @return a BodyPublisher
      */
     static BodyPublisher fromPublisher(Flow.Publisher<? extends
ByteBuffer> publisher,
                                        long contentLength) { ... }


   public interface BodyHandler<T> {

     /**
      * Returns a response body handler that returns a {@link BodySubscriber
      * BodySubscriber}{@code <Void>} obtained from {@linkplain
      * BodySubscriber#fromSubscriber(Subscriber)}, with the given
      * {@code subscriber}.
      *
      * <p> The response body is not available through this, or the {@code
      * HttpResponse} API, but instead all response body is forwarded to the
      * given {@code subscriber}, which should make it available, if
      * appropriate, through some other mechanism, e.g. an entry in a
      * database, etc.
      *
      * @apiNote This method can be used as an adapter between {@code
      * BodySubscriber} and {@code Flow.Subscriber}.
      *
      * <p> For example:
      * <pre> {@code
      *  TextSubscriber subscriber = new TextSubscriber();
      *  HttpResponse<Void> response = client.sendAsync(request,
      *      BodyHandler.fromSubscriber(subscriber)).join();
      *  System.out.println(response.statusCode());
      * }</pre>
      *
      * @param subscriber the subscriber
      * @return a response body handler
      */
     public static BodyHandler<Void>
     fromSubscriber(Subscriber<? super List<ByteBuffer>> subscriber) { ... }

     /**
      * Returns a response body handler that returns a {@link BodySubscriber
      * BodySubscriber}{@code <S,T>} obtained from {@link
      * BodySubscriber#fromSubscriber(Subscriber, Function)}, with the
      * given {@code subscriber} and {@code finisher} function.
      *
      * <p> The given {@code finisher} function is applied after the given
      * subscriber's {@code onComplete} has been invoked. The {@code
finisher}
      * function is invoked with the given subscriber, and returns a value
      * that is set as the response's body.
      *
      * @apiNote This method can be used as an adapter between {@code
      * BodySubscriber} and {@code Flow.Subscriber}.
      *
      * <p> For example:
      * <pre> {@code
      * TextSubscriber subscriber = ...;  // accumulates bytes and
transforms them into a String.*
      * HttpResponse<String> response = client.sendAsync(request,
      *     BodyHandler.fromSubscriber(subscriber,
TextSubscriber::getTextResult)).join();
      * String text = response.body();
      * }</pre>
      *
      * @param <S> the type of the Subscriber
      * @param <T> the type of the response body
      * @param subscriber the subscriber
      * @param finisher a function to be applied after the subscriber
has completed
      * @return a response body handler
      */
     public static <S extends Subscriber<? super List<ByteBuffer>>,T>
BodyHandler<T>
     fromSubscriber(S subscriber, Function<S,T> finisher) { ... }


   // And a similar pair of BodySubscriber methods, omitted for brevity.


-Chris.
Reply | Threaded
Open this post in threaded view
|

Re: JEP 321: HTTP Client - Use of Flow.Subscriber and Flow.Publisher

Chris Hegarty

I filed the following JIRA issue to track this discussion
and proposal.

   https://bugs.openjdk.java.net/browse/JDK-8193365

I'll start the process of bringing it into 10, unless there
are any final comments. FTR, I'm happy where we ended up on
this.

-Chris.

On 11/12/17 15:48, Chris Hegarty wrote:

> James,
>
> On 11/12/17 00:47, James Roper wrote:
>  > Hi Chris,
>  >
>  > This looks like a straight forward way to solve the problem with minimal
>  > disruption from the existing API. Can I make a few suggestions though?
>
> Of course, your input here is much appreciated.
>
>  > We could add a contentLength parameter to fromPublisher, to allow
>  > Flow.Publishers where the content length is known to be easily converted
>  > to BodyPublisher:
>  >
>  > static BodyPublisher fromPublisher(Flow.Publisher<ByteBuffer> publisher,
>  > int contentLength) {
>  >      ...
>  > }
>
> Good idea. Added ( as can be seen below ).
>
>  > This would mean if you were receiving a servlet request body and
>  > publishing it to another location, then you could do something like this
>  > (this uses a reactive streams implementation on top of the servlet API
>  > that I wrote):
>  >
>  > HttpServletRequest request = ...
>  > long contentLength = -1;
>  > if (request.getHeader("Content-Length") != null) {
>  >    contentLength = Long.parseLong(request.getHeader("Content-Length"));
>  > }
>  > Publisher<ByteBuffer> publisher = new
>  > RequestPublisher(request.startAsync(), 8192);
>  >
>  > HttpRequest clientRequest = HttpRequest.newBuilder(target)
>  >    .POST(BodyPublisher.fromPublisher(publisher, contentLength))
>  >    .build()
>
> Nice.
>
>  > Perhaps the method could be overloaded for both supplying and not
>  > supplying a content length.
>
> I think an overload is justified here. Added.
>
>  > Similarly, I think a fromSubscriber API that accepted a
>  > CompletionStage<T> would be a little more fluent than having to supply
>  > it externally:
>
> Daniel and I discussed this too, but I opted to leave it out initially
> for simplicity. I think if we have two overloads, then the simple case
> can still be supported with little ceremony, while allowing a more
> powerful variant.
>
>  > public static <T> BodyHandler<T> fromSubscriber(Subscriber<? super
>  > List<ByteBuffer>> subscriber, CompletionStage<T> bodyFuture) {
>  >    ...
>  > }
>  >
>  > Then you could have something like this:
>  >
>  > TextSubscriber subscriber = ...;  // accumulates bytes and transforms
>  > them into a CompletionStage<String>.
>  > CompletionStage<String> result = subscriber.getTextResult();
>  >
>  > CompletableFuture<String> cf =  client
>  >    .sendAsync(request, BodyHandler.fromSubscriber(subscriber, result));
>  > String text = cf.join();
>  >
>  > Likewise, this could be an overload of fromSubscriber if we want the
>  > option of not specifying a body future.
>  >
>  > One thing I think needs to be carefully specified is, if the method
>  > doesn't accept a CompletionStage, when/how the CompletionStage returned
>  > from send is redeemed.
>
> Hmmm... this could be tricky. I think we can avoid the scenario
> completely by accepting a finishing function that can generate/return
> the value to use for completion, rather than the CF itself.
>
> Here is an outline of all of the above:
>
>    public interface BodyPublisher extends Flow.Publisher<ByteBuffer> {
>
>      /**
>       * Returns a request body publisher whose body is retrieved from the
>       * given {@code Flow.Publisher}. The returned request body publisher
>       * has an unknown content length.
>       *
>       * @apiNote This method can be used as an adapter between {@code
>       * BodyPublisher} and {@code Flow.Publisher}, where the amount of
>       * request body that the publisher will publish is unknown.
>       *
>       * @param publisher the publisher responsible for publishing the body
>       * @return a BodyPublisher
>       */
>      static BodyPublisher fromPublisher(Flow.Publisher<? extends
> ByteBuffer> publisher) { ... }
>
>      /**
>       * Returns a request body publisher whose body is retrieved from the
>       * given {@code Flow.Publisher}. The returned request body publisher
>       * has the given content length.
>       *
>       * <p> The given {@code contentLength} is a positive number, that
>       * represents the exact amount of bytes the {@code publisher} must
>       * publish.
>       *
>       * @apiNote This method can be used as an adapter between {@code
>       * BodyPublisher} and {@code Flow.Publisher}, where the amount of
>       * request body that the publisher will publish is known.
>       *
>       * @param publisher the publisher responsible for publishing the body
>       * @param contentLength a positive number representing the exact
>       *                      amount of bytes the publisher will publish
>       * @throws IllegalArgumentException if the content length is
>       *                                  non-positive
>       * @return a BodyPublisher
>       */
>      static BodyPublisher fromPublisher(Flow.Publisher<? extends
> ByteBuffer> publisher,
>                                         long contentLength) { ... }
>
>
>    public interface BodyHandler<T> {
>
>      /**
>       * Returns a response body handler that returns a {@link
> BodySubscriber
>       * BodySubscriber}{@code <Void>} obtained from {@linkplain
>       * BodySubscriber#fromSubscriber(Subscriber)}, with the given
>       * {@code subscriber}.
>       *
>       * <p> The response body is not available through this, or the {@code
>       * HttpResponse} API, but instead all response body is forwarded to
> the
>       * given {@code subscriber}, which should make it available, if
>       * appropriate, through some other mechanism, e.g. an entry in a
>       * database, etc.
>       *
>       * @apiNote This method can be used as an adapter between {@code
>       * BodySubscriber} and {@code Flow.Subscriber}.
>       *
>       * <p> For example:
>       * <pre> {@code
>       *  TextSubscriber subscriber = new TextSubscriber();
>       *  HttpResponse<Void> response = client.sendAsync(request,
>       *      BodyHandler.fromSubscriber(subscriber)).join();
>       *  System.out.println(response.statusCode());
>       * }</pre>
>       *
>       * @param subscriber the subscriber
>       * @return a response body handler
>       */
>      public static BodyHandler<Void>
>      fromSubscriber(Subscriber<? super List<ByteBuffer>> subscriber) {
> ... }
>
>      /**
>       * Returns a response body handler that returns a {@link
> BodySubscriber
>       * BodySubscriber}{@code <S,T>} obtained from {@link
>       * BodySubscriber#fromSubscriber(Subscriber, Function)}, with the
>       * given {@code subscriber} and {@code finisher} function.
>       *
>       * <p> The given {@code finisher} function is applied after the given
>       * subscriber's {@code onComplete} has been invoked. The {@code
> finisher}
>       * function is invoked with the given subscriber, and returns a value
>       * that is set as the response's body.
>       *
>       * @apiNote This method can be used as an adapter between {@code
>       * BodySubscriber} and {@code Flow.Subscriber}.
>       *
>       * <p> For example:
>       * <pre> {@code
>       * TextSubscriber subscriber = ...;  // accumulates bytes and
> transforms them into a String.*
>       * HttpResponse<String> response = client.sendAsync(request,
>       *     BodyHandler.fromSubscriber(subscriber,
> TextSubscriber::getTextResult)).join();
>       * String text = response.body();
>       * }</pre>
>       *
>       * @param <S> the type of the Subscriber
>       * @param <T> the type of the response body
>       * @param subscriber the subscriber
>       * @param finisher a function to be applied after the subscriber
> has completed
>       * @return a response body handler
>       */
>      public static <S extends Subscriber<? super List<ByteBuffer>>,T>
> BodyHandler<T>
>      fromSubscriber(S subscriber, Function<S,T> finisher) { ... }
>
>
>    // And a similar pair of BodySubscriber methods, omitted for brevity.
>
>
> -Chris.
Reply | Threaded
Open this post in threaded view
|

Re: JEP 321: HTTP Client - Use of Flow.Subscriber and Flow.Publisher

Chris Hegarty
On 12/12/17 11:13, Chris Hegarty wrote:
>
> I filed the following JIRA issue to track this discussion
> and proposal.
>
>    https://bugs.openjdk.java.net/browse/JDK-8193365
>
> I'll start the process of bringing it into 10, unless there
> are any final comments. FTR, I'm happy where we ended up on
> this.

And link to the CSR that contains the full proposed specification
changes:
   https://bugs.openjdk.java.net/browse/JDK-8193366

-Chris.