Hi,

On Wed, Apr 6, 2016 at 4:08 PM, Michael McMahon
<michael.x.mcma...@oracle.com> wrote:
> Hi,
>
> This is the webrev for the HTTP/2 part of JEP 110.
>
> http://cr.openjdk.java.net/~michaelm/8087124/webrev.1/index.html

API

I have already mentioned in 2 previous messages my feedback about the
API, but a few of my concerns have not been addressed, so here they
are again:

* HttpRequest.create(URI) and related.
I don't think a URI is suitable for OPTIONS or CONNECT, although you
can actually build one.
IMHO needs a method Builder.target(String) to accommodate such cases,
and some facility to create a proper URI (especially in case of query
parameters that often need to be URL encoded).

* HttpClient/HttpRequest/etc. getters.
Getter methods should follow the JavaBean standard to comply with
introspective libraries (e.g. JMX) and tools. E.g.
HttpRequest.method() -> HttpRequest.getMethod() and so forth.
I am fine dropping the "set" for fluent interfaces such as Builder,
but IMHO getters should have the "get".

* HttpRequest.BodyProcessor and HttpResponse.BodyProcessor method
naming consistency.
Some method contain the word "request" (or "response"), some don't,
some contains the word "body", some don't, etc.

* HttpRequest.Builder.headers(String...) way too confusing.
Especially now that there are header() and setHeader(); it's confusing
when trying to set headers with multiple values:
headers("Accept-Encoding", "gzip", "deflate") does not do what is
intuitively expected.
Also, it's not clear the semantic when the same header is present more
than once: headers("Foo", "a", "Foo", "b") has add or replace semantic
?
I would remove this method, does not bring any benefit, it adds
confusion, and the other 2 are already chainable and with clear
semantic.

* HttpRequest.Builder.timeout(TimeUnit, long).
The argument order is the opposite of *all* the other usages in the
JDK, I think it should be made conformant. Using (long, TimeUnit) also
reads (5, SECONDS) much better than (SECONDS, 5).

* HttpClient.Builder.pipelining().
Relic of the past, remove it.

* No per-request cookie support.
Manually setting the Cookie header is prone to errors (quoting, paths, etc.)

* HttpClient.Builder.priority().
Not sure how this can be used (not for HTTP/2 despite the javadocs).
HTTP/2 "priority" is not really a priority, it's a dependency from
another request.
I don't see how this API can be used at all.

* MultiResponse.
Neither HTTP nor HTTP/2 have "multiple responses" (well perhaps only
for 100 and 101 codes).
I would rename this functionality into something like PushHandler, and
separate it from the receive of the main response.
In this way, HTTP/1.1 requests may avoid to add the PushHandler, while
HTTP/2 requests may decide whether they are interested or not in
pushes.
The code that handles the main response remains unchanged.

* Aborting the request
I don't see any method to abort a request or response ?

* HttpResponse.BodyProcessor.onResponseBodyChunk()
The javadocs of this method states that "ByteBuffers can be reused as
soon as this method returns", but I think this is wrong because it
forces applications to copy the data in case of async consumption.
BodyProcessor has a flow control mechanism that tells when the
application is done with the data; before the flow control requests
another buffer, it should be assumed that the buffer is still in use.
For example:

void onResponseBodyChunk(ByteBuffer buffer) {
  writeAsyncToDisk(buffer, new CompletionHandler() {
    success: longConsumer.accept(1);
    failure: ??? what here ?
  });
}

In the failure case, how would an application abort (e.g. send a
RST_STREAM to the server) ?


IMPLEMENTATION

In general, I find this implementation disappointing because it is not
non-blocking.
Concepts from ReactiveStreams (JDK9's Flow) have been borrowed, but
then turned into blocking code.
Every connection uses 2 threads, plus additional threads spawned to
achieve asynchronous behavior.
Any call to the "async" versions of the API is actually spawning a new
thread and block in that thread.
Like for the WebSocket case, I don't see the reason to use all these
resources, when a non-blocking implementation would have been simpler
and used much less resources.
I think this is especially important for code to be included in the
JDK, so I think there is a room for a lot of improvement here.


The details:

* HttpRequest.BodyProcessor
Flow control is not implemented for HTTP/1.1, but it needs to,
otherwise it breaks async HttpRequest.BodyProcessor implementations.

* HttpRequest.BodyProcessor.onComplete()
This method is never invoked by the implementation ?

* Stream.responseBody()
In case onResponseBodyStart() returns null, the body is read
asynchronously, but then responseReceived() is called concurrently
with the async read of the body ?
And then called again from receiveDataAsync() ?

* Window updates
They are inefficiently sent for every DATA frame, and in advance.
See above, I think the "consumption" of a buffer from
onResponseBodyChunk(ByteBuffer) does not happen until the flow
controller is notified (not when the method returns), so only when the
flow controller is notified a window update can be sent.

* HTTP/2 writes
Only one frame at a time is being written, which is inefficient. It
would be possible to dequeue multiple frames, generate all the buffers
they require and then gather write the buffers for multiple frames.
In Stream.getDataFrame() the buffer that is being passed to
onRequestBodyChunk() is "limited" to some max payload length, but of
course the application is free to change that limit up to the buffer
capacity, so I'm not sure what the purpose of setting the limit.

Sending one frame potentially blocks 3 times: to wait for
onRequestBodyChunk(), to wait for the stream flow control, and to wait
for the connection flow control.

* HttpResponse.body().
This method takes a BodyProcessor, which is basically a listener for
response body events.
However, an application calling body() won't return from body() until
the whole content has been received. I find this confusing.
I would rather remove this method (to keep only bodyAsync()), and
provide a utility class that has a blocking behavior when passed to
bodyAsync(), something like:

BlockingBody<T> bb = ...;
response.bodyAsync(bb);
T body = bb.get();

Thanks !

-- 
Simone Bordet
http://bordet.blogspot.com
---
Finally, no matter how good the architecture and design are,
to deliver bug-free software with optimal performance and reliability,
the implementation technique must be flawless.   Victoria Livschitz

Reply via email to