8175274: Fix httpclient asynchronous usage
Reviewed-by: dfuchs, michaelm
This commit is contained in:
parent
e3af634a14
commit
4a259fb5dd
@ -27,7 +27,6 @@ package jdk.incubator.http;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Proxy;
|
|
||||||
import java.net.ProxySelector;
|
import java.net.ProxySelector;
|
||||||
import java.net.SocketPermission;
|
import java.net.SocketPermission;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@ -71,7 +70,6 @@ final class Exchange<T> {
|
|||||||
final Executor parentExecutor;
|
final Executor parentExecutor;
|
||||||
final HttpRequest.BodyProcessor requestProcessor;
|
final HttpRequest.BodyProcessor requestProcessor;
|
||||||
boolean upgrading; // to HTTP/2
|
boolean upgrading; // to HTTP/2
|
||||||
volatile Executor responseExecutor;
|
|
||||||
final PushGroup<?,T> pushGroup;
|
final PushGroup<?,T> pushGroup;
|
||||||
|
|
||||||
// buffer for receiving response headers
|
// buffer for receiving response headers
|
||||||
@ -139,7 +137,7 @@ final class Exchange<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler) {
|
public CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler) {
|
||||||
return exchImpl.readBodyAsync(handler, true, responseExecutor);
|
return exchImpl.readBodyAsync(handler, true, parentExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
@ -224,7 +222,8 @@ final class Exchange<T> {
|
|||||||
|
|
||||||
return checkForUpgrade(resp, exchImpl);
|
return checkForUpgrade(resp, exchImpl);
|
||||||
} else {
|
} else {
|
||||||
exchImpl.sendRequest();
|
exchImpl.sendHeadersOnly();
|
||||||
|
exchImpl.sendBody();
|
||||||
Response resp = exchImpl.getResponse();
|
Response resp = exchImpl.getResponse();
|
||||||
HttpResponseImpl.logResponse(resp);
|
HttpResponseImpl.logResponse(resp);
|
||||||
return checkForUpgrade(resp, exchImpl);
|
return checkForUpgrade(resp, exchImpl);
|
||||||
@ -235,8 +234,6 @@ final class Exchange<T> {
|
|||||||
// will be a non null responseAsync if expect continue returns an error
|
// will be a non null responseAsync if expect continue returns an error
|
||||||
|
|
||||||
public CompletableFuture<Response> responseAsync() {
|
public CompletableFuture<Response> responseAsync() {
|
||||||
// take one thread from supplied executor to handle response headers and body
|
|
||||||
responseExecutor = Utils.singleThreadExecutor(parentExecutor);
|
|
||||||
return responseAsyncImpl(null);
|
return responseAsyncImpl(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,20 +264,18 @@ final class Exchange<T> {
|
|||||||
Log.logTrace("Sending Expect: 100-Continue");
|
Log.logTrace("Sending Expect: 100-Continue");
|
||||||
return exchImpl
|
return exchImpl
|
||||||
.sendHeadersAsync()
|
.sendHeadersAsync()
|
||||||
.thenCompose((v) -> exchImpl.getResponseAsync(responseExecutor))
|
.thenCompose(v -> exchImpl.getResponseAsync(parentExecutor))
|
||||||
.thenCompose((Response r1) -> {
|
.thenCompose((Response r1) -> {
|
||||||
HttpResponseImpl.logResponse(r1);
|
HttpResponseImpl.logResponse(r1);
|
||||||
int rcode = r1.statusCode();
|
int rcode = r1.statusCode();
|
||||||
if (rcode == 100) {
|
if (rcode == 100) {
|
||||||
Log.logTrace("Received 100-Continue: sending body");
|
Log.logTrace("Received 100-Continue: sending body");
|
||||||
return exchImpl.sendBodyAsync(parentExecutor)
|
CompletableFuture<Response> cf =
|
||||||
.thenCompose((v) -> exchImpl.getResponseAsync(responseExecutor))
|
exchImpl.sendBodyAsync()
|
||||||
.thenCompose((Response r2) -> {
|
.thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
|
||||||
return checkForUpgradeAsync(r2, exchImpl);
|
cf = wrapForUpgrade(cf);
|
||||||
}).thenApply((Response r) -> {
|
cf = wrapForLog(cf);
|
||||||
HttpResponseImpl.logResponse(r);
|
return cf;
|
||||||
return r;
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
Log.logTrace("Expectation failed: Received {0}",
|
Log.logTrace("Expectation failed: Received {0}",
|
||||||
rcode);
|
rcode);
|
||||||
@ -289,24 +284,36 @@ final class Exchange<T> {
|
|||||||
"Unable to handle 101 while waiting for 100");
|
"Unable to handle 101 while waiting for 100");
|
||||||
return MinimalFuture.failedFuture(failed);
|
return MinimalFuture.failedFuture(failed);
|
||||||
}
|
}
|
||||||
return exchImpl.readBodyAsync(this::ignoreBody, false, responseExecutor)
|
return exchImpl.readBodyAsync(this::ignoreBody, false, parentExecutor)
|
||||||
.thenApply((v) -> {
|
.thenApply(v -> r1);
|
||||||
return r1;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return exchImpl
|
CompletableFuture<Response> cf = exchImpl
|
||||||
.sendRequestAsync(parentExecutor)
|
.sendHeadersAsync()
|
||||||
.thenCompose((v) -> exchImpl.getResponseAsync(responseExecutor))
|
.thenCompose(ExchangeImpl::sendBodyAsync)
|
||||||
.thenCompose((Response r1) -> {
|
.thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
|
||||||
return checkForUpgradeAsync(r1, exchImpl);
|
cf = wrapForUpgrade(cf);
|
||||||
})
|
cf = wrapForLog(cf);
|
||||||
.thenApply((Response response) -> {
|
return cf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<Response> wrapForUpgrade(CompletableFuture<Response> cf) {
|
||||||
|
if (upgrading) {
|
||||||
|
return cf.thenCompose(r -> checkForUpgradeAsync(r, exchImpl));
|
||||||
|
}
|
||||||
|
return cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<Response> wrapForLog(CompletableFuture<Response> cf) {
|
||||||
|
if (Log.requests()) {
|
||||||
|
return cf.thenApply(response -> {
|
||||||
HttpResponseImpl.logResponse(response);
|
HttpResponseImpl.logResponse(response);
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return cf;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse.BodyProcessor<T> ignoreBody(int status, HttpHeaders hdrs) {
|
HttpResponse.BodyProcessor<T> ignoreBody(int status, HttpHeaders hdrs) {
|
||||||
|
@ -102,16 +102,12 @@ abstract class ExchangeImpl<T> {
|
|||||||
|
|
||||||
// Blocking impl but in async style
|
// Blocking impl but in async style
|
||||||
|
|
||||||
CompletableFuture<Void> sendHeadersAsync() {
|
CompletableFuture<ExchangeImpl<T>> sendHeadersAsync() {
|
||||||
CompletableFuture<Void> cf = new MinimalFuture<>();
|
|
||||||
try {
|
|
||||||
sendHeadersOnly();
|
|
||||||
cf.complete(null);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
cf.completeExceptionally(t);
|
|
||||||
}
|
|
||||||
// this is blocking. cf will already be completed.
|
// this is blocking. cf will already be completed.
|
||||||
return cf;
|
return MinimalFuture.supply(() -> {
|
||||||
|
sendHeadersOnly();
|
||||||
|
return this;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -156,39 +152,13 @@ abstract class ExchangeImpl<T> {
|
|||||||
|
|
||||||
// Async version of sendBody(). This only used when body sent separately
|
// Async version of sendBody(). This only used when body sent separately
|
||||||
// to headers (100 continue)
|
// to headers (100 continue)
|
||||||
CompletableFuture<Void> sendBodyAsync(Executor executor) {
|
CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
|
||||||
CompletableFuture<Void> cf = new MinimalFuture<>();
|
return MinimalFuture.supply(() -> {
|
||||||
executor.execute(() -> {
|
|
||||||
try {
|
|
||||||
sendBody();
|
sendBody();
|
||||||
cf.complete(null);
|
return this;
|
||||||
} catch (Throwable t) {
|
|
||||||
cf.completeExceptionally(t);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return cf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the entire request (headers and body) blocking.
|
|
||||||
*/
|
|
||||||
void sendRequest() throws IOException, InterruptedException {
|
|
||||||
sendHeadersOnly();
|
|
||||||
sendBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
CompletableFuture<Void> sendRequestAsync(Executor executor) {
|
|
||||||
CompletableFuture<Void> cf = new MinimalFuture<>();
|
|
||||||
executor.execute(() -> {
|
|
||||||
try {
|
|
||||||
sendRequest();
|
|
||||||
cf.complete(null);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
cf.completeExceptionally(t);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return cf;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Cancels a request. Not currently exposed through API.
|
* Cancels a request. Not currently exposed through API.
|
||||||
*/
|
*/
|
||||||
|
@ -194,17 +194,11 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CompletableFuture<Response> getResponseAsyncImpl(Executor executor) {
|
CompletableFuture<Response> getResponseAsyncImpl(Executor executor) {
|
||||||
CompletableFuture<Response> cf = new MinimalFuture<>();
|
return MinimalFuture.supply( () -> {
|
||||||
executor.execute(() -> {
|
|
||||||
try {
|
|
||||||
response = new Http1Response<>(connection, Http1Exchange.this);
|
response = new Http1Response<>(connection, Http1Exchange.this);
|
||||||
response.readHeaders();
|
response.readHeaders();
|
||||||
cf.complete(response.response());
|
return response.response();
|
||||||
} catch (Throwable e) {
|
}, executor);
|
||||||
cf.completeExceptionally(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return cf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -241,14 +241,7 @@ class Http2Connection {
|
|||||||
Http2ClientImpl client2,
|
Http2ClientImpl client2,
|
||||||
Exchange<?> exchange,
|
Exchange<?> exchange,
|
||||||
ByteBuffer initial) {
|
ByteBuffer initial) {
|
||||||
CompletableFuture<Http2Connection> cf = new MinimalFuture<>();
|
return MinimalFuture.supply(() -> new Http2Connection(connection, client2, exchange, initial));
|
||||||
try {
|
|
||||||
Http2Connection c = new Http2Connection(connection, client2, exchange, initial);
|
|
||||||
cf.complete(c);
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
cf.completeExceptionally(e);
|
|
||||||
}
|
|
||||||
return cf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -235,7 +235,7 @@ class HttpClientImpl extends HttpClient {
|
|||||||
sendAsync(HttpRequest req, HttpResponse.BodyHandler<T> responseHandler)
|
sendAsync(HttpRequest req, HttpResponse.BodyHandler<T> responseHandler)
|
||||||
{
|
{
|
||||||
MultiExchange<Void,T> mex = new MultiExchange<>(req, this, responseHandler);
|
MultiExchange<Void,T> mex = new MultiExchange<>(req, this, responseHandler);
|
||||||
return mex.responseAsync(null)
|
return mex.responseAsync()
|
||||||
.thenApply((HttpResponseImpl<T> b) -> (HttpResponse<T>) b);
|
.thenApply((HttpResponseImpl<T> b) -> (HttpResponse<T>) b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ import java.util.concurrent.CompletionException;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
import jdk.incubator.http.internal.common.Log;
|
import jdk.incubator.http.internal.common.Log;
|
||||||
import jdk.incubator.http.internal.common.MinimalFuture;
|
import jdk.incubator.http.internal.common.MinimalFuture;
|
||||||
import jdk.incubator.http.internal.common.Pair;
|
import jdk.incubator.http.internal.common.Pair;
|
||||||
@ -150,8 +152,7 @@ class MultiExchange<U,T> {
|
|||||||
Exchange<T> currExchange = getExchange();
|
Exchange<T> currExchange = getExchange();
|
||||||
requestFilters(r);
|
requestFilters(r);
|
||||||
Response response = currExchange.response();
|
Response response = currExchange.response();
|
||||||
Pair<Response, HttpRequestImpl> filterResult = responseFilters(response);
|
HttpRequestImpl newreq = responseFilters(response);
|
||||||
HttpRequestImpl newreq = filterResult.second;
|
|
||||||
if (newreq == null) {
|
if (newreq == null) {
|
||||||
if (attempts > 1) {
|
if (attempts > 1) {
|
||||||
Log.logError("Succeeded on attempt: " + attempts);
|
Log.logError("Succeeded on attempt: " + attempts);
|
||||||
@ -213,23 +214,7 @@ class MultiExchange<U,T> {
|
|||||||
Log.logTrace("All filters applied");
|
Log.logTrace("All filters applied");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filters are assumed to be non-blocking so the async
|
private HttpRequestImpl responseFilters(Response response) throws IOException
|
||||||
// versions of these methods just call the blocking ones
|
|
||||||
|
|
||||||
private CompletableFuture<Void> requestFiltersAsync(HttpRequestImpl r) {
|
|
||||||
CompletableFuture<Void> cf = new MinimalFuture<>();
|
|
||||||
try {
|
|
||||||
requestFilters(r);
|
|
||||||
cf.complete(null);
|
|
||||||
} catch(Throwable e) {
|
|
||||||
cf.completeExceptionally(e);
|
|
||||||
}
|
|
||||||
return cf;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Pair<Response,HttpRequestImpl>
|
|
||||||
responseFilters(Response response) throws IOException
|
|
||||||
{
|
{
|
||||||
Log.logTrace("Applying response filters");
|
Log.logTrace("Applying response filters");
|
||||||
for (HeaderFilter filter : filters) {
|
for (HeaderFilter filter : filters) {
|
||||||
@ -237,24 +222,11 @@ class MultiExchange<U,T> {
|
|||||||
HttpRequestImpl newreq = filter.response(response);
|
HttpRequestImpl newreq = filter.response(response);
|
||||||
if (newreq != null) {
|
if (newreq != null) {
|
||||||
Log.logTrace("New request: stopping filters");
|
Log.logTrace("New request: stopping filters");
|
||||||
return pair(null, newreq);
|
return newreq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.logTrace("All filters applied");
|
Log.logTrace("All filters applied");
|
||||||
return pair(response, null);
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<Pair<Response,HttpRequestImpl>>
|
|
||||||
responseFiltersAsync(Response response)
|
|
||||||
{
|
|
||||||
CompletableFuture<Pair<Response,HttpRequestImpl>> cf = new MinimalFuture<>();
|
|
||||||
try {
|
|
||||||
Pair<Response,HttpRequestImpl> n = responseFilters(response); // assumed to be fast
|
|
||||||
cf.complete(n);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
cf.completeExceptionally(e);
|
|
||||||
}
|
|
||||||
return cf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
@ -267,24 +239,27 @@ class MultiExchange<U,T> {
|
|||||||
getExchange().cancel(cause);
|
getExchange().cancel(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<HttpResponseImpl<T>> responseAsync(Void v) {
|
public CompletableFuture<HttpResponseImpl<T>> responseAsync() {
|
||||||
return responseAsync1(null)
|
CompletableFuture<Void> start = new MinimalFuture<>();
|
||||||
|
CompletableFuture<HttpResponseImpl<T>> cf = responseAsync0(start);
|
||||||
|
start.completeAsync( () -> null, executor); // trigger execution
|
||||||
|
return cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<HttpResponseImpl<T>> responseAsync0(CompletableFuture<Void> start) {
|
||||||
|
return start.thenCompose( v -> responseAsyncImpl())
|
||||||
.thenCompose((Response r) -> {
|
.thenCompose((Response r) -> {
|
||||||
Exchange<T> exch = getExchange();
|
Exchange<T> exch = getExchange();
|
||||||
return exch.readBodyAsync(responseHandler)
|
return exch.readBodyAsync(responseHandler)
|
||||||
.thenApply((T body) -> {
|
.thenApply((T body) -> new HttpResponseImpl<>(userRequest, r, body, exch));
|
||||||
Pair<Response,T> result = new Pair<>(r, body);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.thenApply((Pair<Response,T> result) -> {
|
|
||||||
return new HttpResponseImpl<>(userRequest, result.first, result.second, getExchange());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletableFuture<U> multiResponseAsync() {
|
CompletableFuture<U> multiResponseAsync() {
|
||||||
CompletableFuture<HttpResponse<T>> mainResponse = responseAsync(null)
|
CompletableFuture<Void> start = new MinimalFuture<>();
|
||||||
.thenApply((HttpResponseImpl<T> b) -> {
|
CompletableFuture<HttpResponseImpl<T>> cf = responseAsync0(start);
|
||||||
|
CompletableFuture<HttpResponse<T>> mainResponse =
|
||||||
|
cf.thenApply((HttpResponseImpl<T> b) -> {
|
||||||
multiResponseHandler.onResponse(b);
|
multiResponseHandler.onResponse(b);
|
||||||
return (HttpResponse<T>)b;
|
return (HttpResponse<T>)b;
|
||||||
});
|
});
|
||||||
@ -295,10 +270,12 @@ class MultiExchange<U,T> {
|
|||||||
// All push promises received by now.
|
// All push promises received by now.
|
||||||
pushGroup.noMorePushes(true);
|
pushGroup.noMorePushes(true);
|
||||||
});
|
});
|
||||||
return multiResponseHandler.completion(pushGroup.groupResult(), pushGroup.pushesCF());
|
CompletableFuture<U> res = multiResponseHandler.completion(pushGroup.groupResult(), pushGroup.pushesCF());
|
||||||
|
start.completeAsync( () -> null, executor); // trigger execution
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Response> responseAsync1(Void v) {
|
private CompletableFuture<Response> responseAsyncImpl() {
|
||||||
CompletableFuture<Response> cf;
|
CompletableFuture<Response> cf;
|
||||||
if (++attempts > max_attempts) {
|
if (++attempts > max_attempts) {
|
||||||
cf = MinimalFuture.failedFuture(new IOException("Too many retries"));
|
cf = MinimalFuture.failedFuture(new IOException("Too many retries"));
|
||||||
@ -307,48 +284,51 @@ class MultiExchange<U,T> {
|
|||||||
timedEvent = new TimedEvent(currentreq.duration());
|
timedEvent = new TimedEvent(currentreq.duration());
|
||||||
client.registerTimer(timedEvent);
|
client.registerTimer(timedEvent);
|
||||||
}
|
}
|
||||||
Exchange<T> exch = getExchange();
|
try {
|
||||||
// 1. Apply request filters
|
// 1. Apply request filters
|
||||||
cf = requestFiltersAsync(currentreq)
|
requestFilters(currentreq);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return MinimalFuture.failedFuture(e);
|
||||||
|
}
|
||||||
|
Exchange<T> exch = getExchange();
|
||||||
// 2. get response
|
// 2. get response
|
||||||
.thenCompose((v1) -> {
|
cf = exch.responseAsync()
|
||||||
return exch.responseAsync();
|
.thenCompose((Response response) -> {
|
||||||
})
|
HttpRequestImpl newrequest = null;
|
||||||
|
try {
|
||||||
// 3. Apply response filters
|
// 3. Apply response filters
|
||||||
.thenCompose(this::responseFiltersAsync)
|
newrequest = responseFilters(response);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return MinimalFuture.failedFuture(e);
|
||||||
|
}
|
||||||
// 4. Check filter result and repeat or continue
|
// 4. Check filter result and repeat or continue
|
||||||
.thenCompose((Pair<Response,HttpRequestImpl> pair) -> {
|
if (newrequest == null) {
|
||||||
Response resp = pair.first;
|
|
||||||
if (resp != null) {
|
|
||||||
if (attempts > 1) {
|
if (attempts > 1) {
|
||||||
Log.logError("Succeeded on attempt: " + attempts);
|
Log.logError("Succeeded on attempt: " + attempts);
|
||||||
}
|
}
|
||||||
return MinimalFuture.completedFuture(resp);
|
return MinimalFuture.completedFuture(response);
|
||||||
} else {
|
} else {
|
||||||
currentreq = pair.second;
|
currentreq = newrequest;
|
||||||
Exchange<T> previous = exch;
|
|
||||||
setExchange(new Exchange<>(currentreq, this, acc));
|
setExchange(new Exchange<>(currentreq, this, acc));
|
||||||
//reads body off previous, and then waits for next response
|
//reads body off previous, and then waits for next response
|
||||||
return responseAsync1(null);
|
return responseAsyncImpl();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 5. Convert result to Pair
|
// 5. Handle errors and cancel any timer set
|
||||||
.handle((BiFunction<Response, Throwable, Pair<Response, Throwable>>) Pair::new)
|
.handle((response, ex) -> {
|
||||||
// 6. Handle errors and cancel any timer set
|
|
||||||
.thenCompose((Pair<Response,Throwable> obj) -> {
|
|
||||||
Response response = obj.first;
|
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
return MinimalFuture.completedFuture(response);
|
return MinimalFuture.completedFuture(response);
|
||||||
}
|
}
|
||||||
// all exceptions thrown are handled here
|
// all exceptions thrown are handled here
|
||||||
CompletableFuture<Response> error = getExceptionalCF(obj.second);
|
CompletableFuture<Response> error = getExceptionalCF(ex);
|
||||||
if (error == null) {
|
if (error == null) {
|
||||||
cancelTimer();
|
cancelTimer();
|
||||||
return responseAsync1(null);
|
return responseAsyncImpl();
|
||||||
} else {
|
} else {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.thenCompose(UnaryOperator.identity());
|
||||||
}
|
}
|
||||||
return cf;
|
return cf;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ class PlainTunnelingConnection extends HttpConnection {
|
|||||||
.thenCompose((Void v) -> {
|
.thenCompose((Void v) -> {
|
||||||
HttpRequestImpl req = new HttpRequestImpl("CONNECT", client, address);
|
HttpRequestImpl req = new HttpRequestImpl("CONNECT", client, address);
|
||||||
MultiExchange<Void,Void> mconnectExchange = new MultiExchange<>(req, client, this::ignore);
|
MultiExchange<Void,Void> mconnectExchange = new MultiExchange<>(req, client, this::ignore);
|
||||||
return mconnectExchange.responseAsync(null)
|
return mconnectExchange.responseAsync()
|
||||||
.thenCompose((HttpResponseImpl<Void> resp) -> {
|
.thenCompose((HttpResponseImpl<Void> resp) -> {
|
||||||
CompletableFuture<Void> cf = new MinimalFuture<>();
|
CompletableFuture<Void> cf = new MinimalFuture<>();
|
||||||
if (resp.statusCode() != 200) {
|
if (resp.statusCode() != 200) {
|
||||||
|
@ -50,18 +50,11 @@ class SSLConnection extends HttpConnection {
|
|||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> connectAsync() {
|
public CompletableFuture<Void> connectAsync() {
|
||||||
return delegate.connectAsync()
|
return delegate.connectAsync()
|
||||||
.thenCompose((Void v) -> {
|
.thenCompose((Void v) ->
|
||||||
CompletableFuture<Void> cf = new MinimalFuture<>();
|
MinimalFuture.supply( () -> {
|
||||||
try {
|
this.sslDelegate = new SSLDelegate(delegate.channel(), client, alpn);
|
||||||
this.sslDelegate = new SSLDelegate(delegate.channel(),
|
return null;
|
||||||
client,
|
}));
|
||||||
alpn);
|
|
||||||
cf.complete(null);
|
|
||||||
} catch (IOException e) {
|
|
||||||
cf.completeExceptionally(e);
|
|
||||||
}
|
|
||||||
return cf;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -39,6 +39,8 @@ import java.util.concurrent.Flow;
|
|||||||
import java.util.concurrent.Flow.Subscription;
|
import java.util.concurrent.Flow.Subscription;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import jdk.incubator.http.internal.common.*;
|
import jdk.incubator.http.internal.common.*;
|
||||||
import jdk.incubator.http.internal.frame.*;
|
import jdk.incubator.http.internal.frame.*;
|
||||||
import jdk.incubator.http.internal.hpack.DecodingCallback;
|
import jdk.incubator.http.internal.hpack.DecodingCallback;
|
||||||
@ -96,7 +98,7 @@ import jdk.incubator.http.internal.hpack.DecodingCallback;
|
|||||||
*/
|
*/
|
||||||
class Stream<T> extends ExchangeImpl<T> {
|
class Stream<T> extends ExchangeImpl<T> {
|
||||||
|
|
||||||
final Queue<Http2Frame> inputQ;
|
final AsyncDataReadQueue inputQ = new AsyncDataReadQueue();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This stream's identifier. Assigned lazily by the HTTP2Connection before
|
* This stream's identifier. Assigned lazily by the HTTP2Connection before
|
||||||
@ -169,7 +171,7 @@ class Stream<T> extends ExchangeImpl<T> {
|
|||||||
{
|
{
|
||||||
CompletableFuture<T> cf = readBodyAsync(handler,
|
CompletableFuture<T> cf = readBodyAsync(handler,
|
||||||
returnToCache,
|
returnToCache,
|
||||||
this::executeInline);
|
null);
|
||||||
try {
|
try {
|
||||||
return cf.join();
|
return cf.join();
|
||||||
} catch (CompletionException e) {
|
} catch (CompletionException e) {
|
||||||
@ -177,10 +179,6 @@ class Stream<T> extends ExchangeImpl<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void executeInline(Runnable r) {
|
|
||||||
r.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@ -189,60 +187,69 @@ class Stream<T> extends ExchangeImpl<T> {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// pushes entire response body into response processor
|
private boolean receiveDataFrame(Http2Frame frame) throws IOException, InterruptedException {
|
||||||
// blocking when required by local or remote flow control
|
|
||||||
CompletableFuture<T> receiveData(Executor executor) {
|
|
||||||
CompletableFuture<T> cf = responseProcessor
|
|
||||||
.getBody()
|
|
||||||
.toCompletableFuture();
|
|
||||||
|
|
||||||
executor.execute(() -> {
|
|
||||||
Http2Frame frame;
|
|
||||||
DataFrame df = null;
|
|
||||||
try {
|
|
||||||
if (!endStreamReceived()) {
|
|
||||||
do {
|
|
||||||
frame = inputQ.take();
|
|
||||||
if (frame instanceof ResetFrame) {
|
if (frame instanceof ResetFrame) {
|
||||||
handleReset((ResetFrame) frame);
|
handleReset((ResetFrame) frame);
|
||||||
continue;
|
return true;
|
||||||
} else if (!(frame instanceof DataFrame)) {
|
} else if (!(frame instanceof DataFrame)) {
|
||||||
assert false;
|
assert false;
|
||||||
continue;
|
return true;
|
||||||
}
|
}
|
||||||
df = (DataFrame) frame;
|
DataFrame df = (DataFrame) frame;
|
||||||
// RFC 7540 6.1:
|
// RFC 7540 6.1:
|
||||||
// The entire DATA frame payload is included in flow control,
|
// The entire DATA frame payload is included in flow control,
|
||||||
// including the Pad Length and Padding fields if present
|
// including the Pad Length and Padding fields if present
|
||||||
int len = df.payloadLength();
|
int len = df.payloadLength();
|
||||||
ByteBufferReference[] buffers = df.getData();
|
ByteBufferReference[] buffers = df.getData();
|
||||||
for (ByteBufferReference b : buffers) {
|
for (ByteBufferReference b : buffers) {
|
||||||
publisher.acceptData(Optional.of(b.get()));
|
ByteBuffer buf = b.get();
|
||||||
|
if (buf.hasRemaining()) {
|
||||||
|
publisher.acceptData(Optional.of(buf));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
connection.windowUpdater.update(len);
|
connection.windowUpdater.update(len);
|
||||||
if (df.getFlag(DataFrame.END_STREAM)) {
|
if (df.getFlag(DataFrame.END_STREAM)) {
|
||||||
break;
|
setEndStreamReceived();
|
||||||
|
publisher.acceptData(Optional.empty());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
// Don't send window update on a stream which is
|
// Don't send window update on a stream which is
|
||||||
// closed or half closed.
|
// closed or half closed.
|
||||||
windowUpdater.update(len);
|
windowUpdater.update(len);
|
||||||
} while (true);
|
return true;
|
||||||
setEndStreamReceived();
|
|
||||||
}
|
}
|
||||||
publisher.acceptData(Optional.empty());
|
|
||||||
} catch (Throwable e) {
|
// pushes entire response body into response processor
|
||||||
|
// blocking when required by local or remote flow control
|
||||||
|
CompletableFuture<T> receiveData(Executor executor) {
|
||||||
|
CompletableFuture<T> cf = responseProcessor
|
||||||
|
.getBody()
|
||||||
|
.toCompletableFuture();
|
||||||
|
Consumer<Throwable> onError = e -> {
|
||||||
Log.logTrace("receiveData: {0}", e.toString());
|
Log.logTrace("receiveData: {0}", e.toString());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
cf.completeExceptionally(e);
|
cf.completeExceptionally(e);
|
||||||
publisher.acceptError(e);
|
publisher.acceptError(e);
|
||||||
|
};
|
||||||
|
if (executor == null) {
|
||||||
|
inputQ.blockingReceive(this::receiveDataFrame, onError);
|
||||||
|
} else {
|
||||||
|
inputQ.asyncReceive(executor, this::receiveDataFrame, onError);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
return cf;
|
return cf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void sendBody() throws IOException, InterruptedException {
|
void sendBody() throws IOException {
|
||||||
sendBodyImpl();
|
try {
|
||||||
|
sendBodyImpl().join();
|
||||||
|
} catch (CompletionException e) {
|
||||||
|
throw Utils.getIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
|
||||||
|
return sendBodyImpl().thenApply( v -> this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@ -268,7 +275,6 @@ class Stream<T> extends ExchangeImpl<T> {
|
|||||||
};
|
};
|
||||||
this.requestPseudoHeaders = new HttpHeadersImpl();
|
this.requestPseudoHeaders = new HttpHeadersImpl();
|
||||||
// NEW
|
// NEW
|
||||||
this.inputQ = new Queue<>();
|
|
||||||
this.publisher = new BlockingPushPublisher<>();
|
this.publisher = new BlockingPushPublisher<>();
|
||||||
this.windowUpdater = new StreamWindowUpdateSender(connection);
|
this.windowUpdater = new StreamWindowUpdateSender(connection);
|
||||||
}
|
}
|
||||||
@ -673,6 +679,10 @@ class Stream<T> extends ExchangeImpl<T> {
|
|||||||
response_cfs.add(cf);
|
response_cfs.add(cf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (executor != null && !cf.isDone()) {
|
||||||
|
// protect from executing later chain of CompletableFuture operations from SelectorManager thread
|
||||||
|
cf = cf.thenApplyAsync(r -> r, executor);
|
||||||
|
}
|
||||||
Log.logTrace("Response future (stream={0}) is: {1}", streamid, cf);
|
Log.logTrace("Response future (stream={0}) is: {1}", streamid, cf);
|
||||||
PushGroup<?,?> pg = exchange.getPushGroup();
|
PushGroup<?,?> pg = exchange.getPushGroup();
|
||||||
if (pg != null) {
|
if (pg != null) {
|
||||||
@ -743,20 +753,12 @@ class Stream<T> extends ExchangeImpl<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitForCompletion() throws IOException {
|
CompletableFuture<Void> sendBodyImpl() {
|
||||||
try {
|
|
||||||
requestBodyCF.join();
|
|
||||||
} catch (CompletionException e) {
|
|
||||||
throw Utils.getIOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendBodyImpl() throws IOException, InterruptedException {
|
|
||||||
RequestSubscriber subscriber = new RequestSubscriber(requestContentLen);
|
RequestSubscriber subscriber = new RequestSubscriber(requestContentLen);
|
||||||
subscriber.setClient(client);
|
subscriber.setClient(client);
|
||||||
requestProcessor.subscribe(subscriber);
|
requestProcessor.subscribe(subscriber);
|
||||||
waitForCompletion();
|
requestBodyCF.whenComplete((v,t) -> requestSent());
|
||||||
requestSent();
|
return requestBodyCF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -846,30 +848,24 @@ class Stream<T> extends ExchangeImpl<T> {
|
|||||||
// error record it in the PushGroup. The error method is called
|
// error record it in the PushGroup. The error method is called
|
||||||
// with a null value when no error occurred (is a no-op)
|
// with a null value when no error occurred (is a no-op)
|
||||||
@Override
|
@Override
|
||||||
CompletableFuture<Void> sendBodyAsync(Executor executor) {
|
CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
|
||||||
return super.sendBodyAsync(executor)
|
return super.sendBodyAsync()
|
||||||
.whenComplete((Void v, Throwable t) -> {
|
.whenComplete((ExchangeImpl<T> v, Throwable t) -> pushGroup.pushError(t));
|
||||||
pushGroup.pushError(t);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
CompletableFuture<Void> sendHeadersAsync() {
|
CompletableFuture<ExchangeImpl<T>> sendHeadersAsync() {
|
||||||
return super.sendHeadersAsync()
|
return super.sendHeadersAsync()
|
||||||
.whenComplete((Void v, Throwable t) -> {
|
.whenComplete((ExchangeImpl<T> ex, Throwable t) -> pushGroup.pushError(t));
|
||||||
pushGroup.pushError(t);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
CompletableFuture<Void> sendRequestAsync(Executor executor) {
|
|
||||||
return super.sendRequestAsync(executor)
|
|
||||||
.whenComplete((v, t) -> pushGroup.pushError(t));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
CompletableFuture<Response> getResponseAsync(Executor executor) {
|
CompletableFuture<Response> getResponseAsync(Executor executor) {
|
||||||
return pushCF.whenComplete((v, t) -> pushGroup.pushError(t));
|
CompletableFuture<Response> cf = pushCF.whenComplete((v, t) -> pushGroup.pushError(t));
|
||||||
|
if(executor!=null && !cf.isDone()) {
|
||||||
|
cf = cf.thenApplyAsync( r -> r, executor);
|
||||||
|
}
|
||||||
|
return cf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -887,7 +883,8 @@ class Stream<T> extends ExchangeImpl<T> {
|
|||||||
HttpResponseImpl.logResponse(r);
|
HttpResponseImpl.logResponse(r);
|
||||||
pushCF.complete(r); // not strictly required for push API
|
pushCF.complete(r); // not strictly required for push API
|
||||||
// start reading the body using the obtained BodyProcessor
|
// start reading the body using the obtained BodyProcessor
|
||||||
readBodyAsync(getPushHandler(), false, getExchange().executor())
|
CompletableFuture<Void> start = new MinimalFuture<>();
|
||||||
|
start.thenCompose( v -> readBodyAsync(getPushHandler(), false, getExchange().executor()))
|
||||||
.whenComplete((T body, Throwable t) -> {
|
.whenComplete((T body, Throwable t) -> {
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
responseCF.completeExceptionally(t);
|
responseCF.completeExceptionally(t);
|
||||||
@ -896,6 +893,7 @@ class Stream<T> extends ExchangeImpl<T> {
|
|||||||
responseCF.complete(response);
|
responseCF.complete(response);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
start.completeAsync(() -> null, getExchange().executor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
package jdk.incubator.http.internal.common;
|
||||||
|
|
||||||
|
import jdk.incubator.http.internal.frame.DataFrame;
|
||||||
|
import jdk.incubator.http.internal.frame.Http2Frame;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http2Frame Producer-Consumer queue which either allows to consume all frames in blocking way
|
||||||
|
* or allows to consume it asynchronously. In the latter case put operation from the producer thread
|
||||||
|
* executes consume operation in the given executor.
|
||||||
|
*/
|
||||||
|
public class AsyncDataReadQueue implements Closeable {
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface DataConsumer {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param t - frame
|
||||||
|
* @return true if consuming should be continued. false when END_STREAM was received.
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
boolean accept(Http2Frame t) throws Throwable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int BLOCKING = 0;
|
||||||
|
private static final int FLUSHING = 1;
|
||||||
|
private static final int REFLUSHING = 2;
|
||||||
|
private static final int ASYNC = 3;
|
||||||
|
private static final int CLOSED = 4;
|
||||||
|
|
||||||
|
|
||||||
|
private final AtomicInteger state = new AtomicInteger(BLOCKING);
|
||||||
|
private final BlockingQueue<Http2Frame> queue = new LinkedBlockingQueue<>();
|
||||||
|
private Executor executor;
|
||||||
|
private DataConsumer onData;
|
||||||
|
private Consumer<Throwable> onError;
|
||||||
|
|
||||||
|
public AsyncDataReadQueue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean tryPut(Http2Frame f) {
|
||||||
|
if(state.get() == CLOSED) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
queue.offer(f);
|
||||||
|
flushAsync(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(Http2Frame f) throws IOException {
|
||||||
|
if(!tryPut(f))
|
||||||
|
throw new IOException("stream closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void blockingReceive(DataConsumer onData, Consumer<Throwable> onError) {
|
||||||
|
if (state.get() == CLOSED) {
|
||||||
|
onError.accept(new IOException("stream closed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert state.get() == BLOCKING;
|
||||||
|
try {
|
||||||
|
while (onData.accept(queue.take()));
|
||||||
|
assert state.get() == CLOSED;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
onError.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void asyncReceive(Executor executor, DataConsumer onData,
|
||||||
|
Consumer<Throwable> onError) {
|
||||||
|
if (state.get() == CLOSED) {
|
||||||
|
onError.accept(new IOException("stream closed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert state.get() == BLOCKING;
|
||||||
|
|
||||||
|
// Validates that fields not already set.
|
||||||
|
if (!checkCanSet("executor", this.executor, onError)
|
||||||
|
|| !checkCanSet("onData", this.onData, onError)
|
||||||
|
|| !checkCanSet("onError", this.onError, onError)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.executor = executor;
|
||||||
|
this.onData = onData;
|
||||||
|
this.onError = onError;
|
||||||
|
|
||||||
|
// This will report an error if asyncReceive is called twice,
|
||||||
|
// because we won't be in BLOCKING state if that happens
|
||||||
|
if (!this.state.compareAndSet(BLOCKING, ASYNC)) {
|
||||||
|
onError.accept(new IOException(
|
||||||
|
new IllegalStateException("State: "+this.state.get())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
flushAsync(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> boolean checkCanSet(String name, T oldval, Consumer<Throwable> onError) {
|
||||||
|
if (oldval != null) {
|
||||||
|
onError.accept(new IOException(
|
||||||
|
new IllegalArgumentException(name)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
int prevState = state.getAndSet(CLOSED);
|
||||||
|
if(prevState == BLOCKING) {
|
||||||
|
// wake up blocked take()
|
||||||
|
queue.offer(new DataFrame(0, DataFrame.END_STREAM, new ByteBufferReference[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushAsync(boolean alreadyInExecutor) {
|
||||||
|
while(true) {
|
||||||
|
switch (state.get()) {
|
||||||
|
case BLOCKING:
|
||||||
|
case CLOSED:
|
||||||
|
case REFLUSHING:
|
||||||
|
return;
|
||||||
|
case ASYNC:
|
||||||
|
if(state.compareAndSet(ASYNC, FLUSHING)) {
|
||||||
|
if(alreadyInExecutor) {
|
||||||
|
flushLoop();
|
||||||
|
} else {
|
||||||
|
executor.execute(this::flushLoop);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FLUSHING:
|
||||||
|
if(state.compareAndSet(FLUSHING, REFLUSHING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushLoop() {
|
||||||
|
try {
|
||||||
|
while(true) {
|
||||||
|
Http2Frame frame = queue.poll();
|
||||||
|
while (frame != null) {
|
||||||
|
if(!onData.accept(frame)) {
|
||||||
|
assert state.get() == CLOSED;
|
||||||
|
return; // closed
|
||||||
|
}
|
||||||
|
frame = queue.poll();
|
||||||
|
}
|
||||||
|
switch (state.get()) {
|
||||||
|
case BLOCKING:
|
||||||
|
assert false;
|
||||||
|
break;
|
||||||
|
case ASYNC:
|
||||||
|
throw new RuntimeException("Shouldn't happen");
|
||||||
|
case FLUSHING:
|
||||||
|
if(state.compareAndSet(FLUSHING, ASYNC)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case REFLUSHING:
|
||||||
|
// We need to check if new elements were put after last
|
||||||
|
// poll() and do graceful exit
|
||||||
|
state.compareAndSet(REFLUSHING, FLUSHING);
|
||||||
|
break;
|
||||||
|
case CLOSED:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
onError.accept(e);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@
|
|||||||
package jdk.incubator.http.internal.common;
|
package jdk.incubator.http.internal.common;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.CompletionStage;
|
import java.util.concurrent.CompletionStage;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
@ -40,6 +41,11 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
*/
|
*/
|
||||||
public final class MinimalFuture<T> extends CompletableFuture<T> {
|
public final class MinimalFuture<T> extends CompletableFuture<T> {
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ExceptionalSupplier<U> {
|
||||||
|
U get() throws Throwable;
|
||||||
|
}
|
||||||
|
|
||||||
final static AtomicLong TOKENS = new AtomicLong();
|
final static AtomicLong TOKENS = new AtomicLong();
|
||||||
final long id;
|
final long id;
|
||||||
|
|
||||||
@ -56,6 +62,29 @@ public final class MinimalFuture<T> extends CompletableFuture<T> {
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <U> CompletableFuture<U> supply(ExceptionalSupplier<U> supplier) {
|
||||||
|
CompletableFuture<U> cf = new MinimalFuture<>();
|
||||||
|
try {
|
||||||
|
U value = supplier.get();
|
||||||
|
cf.complete(value);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
cf.completeExceptionally(t);
|
||||||
|
}
|
||||||
|
return cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <U> CompletableFuture<U> supply(ExceptionalSupplier<U> supplier, Executor executor) {
|
||||||
|
CompletableFuture<U> cf = new MinimalFuture<>();
|
||||||
|
cf.completeAsync( () -> {
|
||||||
|
try {
|
||||||
|
return supplier.get();
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
throw new CompletionException(ex);
|
||||||
|
}
|
||||||
|
}, executor);
|
||||||
|
return cf;
|
||||||
|
}
|
||||||
|
|
||||||
public MinimalFuture() {
|
public MinimalFuture() {
|
||||||
super();
|
super();
|
||||||
this.id = TOKENS.incrementAndGet();
|
this.id = TOKENS.incrementAndGet();
|
||||||
|
241
jdk/test/java/net/httpclient/http2/FixedThreadPoolTest.java
Normal file
241
jdk/test/java/net/httpclient/http2/FixedThreadPoolTest.java
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8087112
|
||||||
|
* @library /lib/testlibrary server
|
||||||
|
* @build jdk.testlibrary.SimpleSSLContext
|
||||||
|
* @modules jdk.incubator.httpclient/jdk.incubator.http.internal.common
|
||||||
|
* jdk.incubator.httpclient/jdk.incubator.http.internal.frame
|
||||||
|
* jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
|
||||||
|
* @run testng/othervm -Djdk.httpclient.HttpClient.log=ssl,requests,responses,errors FixedThreadPoolTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.net.*;
|
||||||
|
import jdk.incubator.http.*;
|
||||||
|
import static jdk.incubator.http.HttpClient.Version.HTTP_2;
|
||||||
|
import javax.net.ssl.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import jdk.testlibrary.SimpleSSLContext;
|
||||||
|
import static jdk.incubator.http.HttpRequest.BodyProcessor.fromFile;
|
||||||
|
import static jdk.incubator.http.HttpRequest.BodyProcessor.fromString;
|
||||||
|
import static jdk.incubator.http.HttpResponse.BodyHandler.asFile;
|
||||||
|
import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
|
||||||
|
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public class FixedThreadPoolTest {
|
||||||
|
static int httpPort, httpsPort;
|
||||||
|
static Http2TestServer httpServer, httpsServer;
|
||||||
|
static HttpClient client = null;
|
||||||
|
static ExecutorService exec;
|
||||||
|
static SSLContext sslContext;
|
||||||
|
|
||||||
|
static String httpURIString, httpsURIString;
|
||||||
|
|
||||||
|
static void initialize() throws Exception {
|
||||||
|
try {
|
||||||
|
SimpleSSLContext sslct = new SimpleSSLContext();
|
||||||
|
sslContext = sslct.get();
|
||||||
|
client = getClient();
|
||||||
|
httpServer = new Http2TestServer(false, 0, exec, sslContext);
|
||||||
|
httpServer.addHandler(new EchoHandler(), "/");
|
||||||
|
httpPort = httpServer.getAddress().getPort();
|
||||||
|
|
||||||
|
httpsServer = new Http2TestServer(true, 0, exec, sslContext);
|
||||||
|
httpsServer.addHandler(new EchoHandler(), "/");
|
||||||
|
|
||||||
|
httpsPort = httpsServer.getAddress().getPort();
|
||||||
|
httpURIString = "http://127.0.0.1:" + httpPort + "/foo/";
|
||||||
|
httpsURIString = "https://127.0.0.1:" + httpsPort + "/bar/";
|
||||||
|
|
||||||
|
httpServer.start();
|
||||||
|
httpsServer.start();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
System.err.println("Throwing now");
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeOut=3000000)
|
||||||
|
public static void test() throws Exception {
|
||||||
|
try {
|
||||||
|
initialize();
|
||||||
|
simpleTest(false);
|
||||||
|
simpleTest(true);
|
||||||
|
streamTest(false);
|
||||||
|
streamTest(true);
|
||||||
|
paramsTest();
|
||||||
|
Thread.sleep(1000 * 4);
|
||||||
|
} catch (Exception | Error tt) {
|
||||||
|
tt.printStackTrace();
|
||||||
|
throw tt;
|
||||||
|
} finally {
|
||||||
|
httpServer.stop();
|
||||||
|
httpsServer.stop();
|
||||||
|
exec.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static HttpClient getClient() {
|
||||||
|
if (client == null) {
|
||||||
|
exec = Executors.newCachedThreadPool();
|
||||||
|
client = HttpClient.newBuilder()
|
||||||
|
.executor(Executors.newFixedThreadPool(2))
|
||||||
|
.sslContext(sslContext)
|
||||||
|
.version(HTTP_2)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
static URI getURI(boolean secure) {
|
||||||
|
if (secure)
|
||||||
|
return URI.create(httpsURIString);
|
||||||
|
else
|
||||||
|
return URI.create(httpURIString);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkStatus(int expected, int found) throws Exception {
|
||||||
|
if (expected != found) {
|
||||||
|
System.err.printf ("Test failed: wrong status code %d/%d\n",
|
||||||
|
expected, found);
|
||||||
|
throw new RuntimeException("Test failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkStrings(String expected, String found) throws Exception {
|
||||||
|
if (!expected.equals(found)) {
|
||||||
|
System.err.printf ("Test failed: wrong string %s/%s\n",
|
||||||
|
expected, found);
|
||||||
|
throw new RuntimeException("Test failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Void compareFiles(Path path1, Path path2) {
|
||||||
|
return TestUtil.compareFiles(path1, path2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Path tempFile() {
|
||||||
|
return TestUtil.tempFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final String SIMPLE_STRING = "Hello world Goodbye world";
|
||||||
|
|
||||||
|
static final int LOOPS = 32;
|
||||||
|
static final int FILESIZE = 64 * 1024 + 200;
|
||||||
|
|
||||||
|
static void streamTest(boolean secure) throws Exception {
|
||||||
|
URI uri = getURI(secure);
|
||||||
|
System.err.printf("streamTest %b to %s\n" , secure, uri);
|
||||||
|
|
||||||
|
HttpClient client = getClient();
|
||||||
|
Path src = TestUtil.getAFile(FILESIZE * 4);
|
||||||
|
HttpRequest req = HttpRequest.newBuilder(uri)
|
||||||
|
.POST(fromFile(src))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Path dest = Paths.get("streamtest.txt");
|
||||||
|
dest.toFile().delete();
|
||||||
|
CompletableFuture<Path> response = client.sendAsync(req, asFile(dest))
|
||||||
|
.thenApply(resp -> {
|
||||||
|
if (resp.statusCode() != 200)
|
||||||
|
throw new RuntimeException();
|
||||||
|
return resp.body();
|
||||||
|
});
|
||||||
|
response.join();
|
||||||
|
compareFiles(src, dest);
|
||||||
|
System.err.println("DONE");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void paramsTest() throws Exception {
|
||||||
|
System.err.println("paramsTest");
|
||||||
|
Http2TestServer server = new Http2TestServer(true, 0, exec, sslContext);
|
||||||
|
server.addHandler((t -> {
|
||||||
|
SSLSession s = t.getSSLSession();
|
||||||
|
String prot = s.getProtocol();
|
||||||
|
if (prot.equals("TLSv1.2")) {
|
||||||
|
t.sendResponseHeaders(200, -1);
|
||||||
|
} else {
|
||||||
|
System.err.printf("Protocols =%s\n", prot);
|
||||||
|
t.sendResponseHeaders(500, -1);
|
||||||
|
}
|
||||||
|
}), "/");
|
||||||
|
server.start();
|
||||||
|
int port = server.getAddress().getPort();
|
||||||
|
URI u = new URI("https://127.0.0.1:"+port+"/foo");
|
||||||
|
HttpClient client = getClient();
|
||||||
|
HttpRequest req = HttpRequest.newBuilder(u).build();
|
||||||
|
HttpResponse<String> resp = client.sendAsync(req, asString()).get();
|
||||||
|
int stat = resp.statusCode();
|
||||||
|
if (stat != 200) {
|
||||||
|
throw new RuntimeException("paramsTest failed "
|
||||||
|
+ Integer.toString(stat));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void simpleTest(boolean secure) throws Exception {
|
||||||
|
URI uri = getURI(secure);
|
||||||
|
System.err.println("Request to " + uri);
|
||||||
|
|
||||||
|
// Do a simple warmup request
|
||||||
|
|
||||||
|
HttpClient client = getClient();
|
||||||
|
HttpRequest req = HttpRequest.newBuilder(uri)
|
||||||
|
.POST(fromString(SIMPLE_STRING))
|
||||||
|
.build();
|
||||||
|
HttpResponse<String> response = client.sendAsync(req, asString()).get();
|
||||||
|
HttpHeaders h = response.headers();
|
||||||
|
|
||||||
|
checkStatus(200, response.statusCode());
|
||||||
|
|
||||||
|
String responseBody = response.body();
|
||||||
|
checkStrings(SIMPLE_STRING, responseBody);
|
||||||
|
|
||||||
|
checkStrings(h.firstValue("x-hello").get(), "world");
|
||||||
|
checkStrings(h.firstValue("x-bye").get(), "universe");
|
||||||
|
|
||||||
|
// Do loops asynchronously
|
||||||
|
|
||||||
|
CompletableFuture[] responses = new CompletableFuture[LOOPS];
|
||||||
|
final Path source = TestUtil.getAFile(FILESIZE);
|
||||||
|
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||||
|
.POST(fromFile(source))
|
||||||
|
.build();
|
||||||
|
for (int i = 0; i < LOOPS; i++) {
|
||||||
|
responses[i] = client.sendAsync(request, asFile(tempFile()))
|
||||||
|
//.thenApply(resp -> compareFiles(resp.body(), source));
|
||||||
|
.thenApply(resp -> {
|
||||||
|
System.out.printf("Resp status %d body size %d\n",
|
||||||
|
resp.statusCode(), resp.body().toFile().length());
|
||||||
|
return compareFiles(resp.body(), source);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
CompletableFuture.allOf(responses).join();
|
||||||
|
System.err.println("DONE");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user