Skip to content

Commit

Permalink
Redirect documentation and change the redirect handle to be a Handler…
Browse files Browse the repository at this point in the history
…<HttpClientResponse, Future<HttpClientRequest>>
  • Loading branch information
vietj committed Feb 2, 2017
1 parent 6b9c414 commit ce3814f
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 57 deletions.
66 changes: 66 additions & 0 deletions src/main/asciidoc/java/http.adoc
Expand Up @@ -1374,6 +1374,72 @@ You can retrieve the list of cookies from a response using `link:../../apidocs/i

Alternatively you can just parse the `Set-Cookie` headers yourself in the response.

==== 30x redirection handling

The client can be configured to follow HTTP redirections: when the client receives an
`301`, `302`, `303` or `307` status code, it follows the redirection provided by the `Location` response header
and the response handler is passed the redirected response instead of the original response.

Here’s an example:

[source,java]
----
client.get("some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
}).setFollowRedirects(true).end();
----

The redirection policy is as follow

* on a `301`, `302` or `303` status code, follow the redirection with a `GET` method
* on a `307` status code, follow the redirection with the same HTTP method and the cached body

WARNING: following redirections caches the request body

The maximum redirects is `16` by default and can be changed with `link:../../apidocs/io/vertx/core/http/HttpClientOptions.html#setMaxRedirects-int-[setMaxRedirects]`.

[source,java]
----
HttpClient client = vertx.createHttpClient(
new HttpClientOptions()
.setMaxRedirects(32));
client.get("some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
}).setFollowRedirects(true).end();
----

One size does not fit all and the default redirection policy may not be adapted to your needs.

The default redirection policy can changed with a custom implementation:

[source,java]
----
client.redirectHandler(response -> {
// Only follow 301 code
if (response.statusCode() == 301 && response.getHeader("Location") != null) {
// Compute the redirect URI
String absoluteURI = resolveURI(response.request().absoluteURI(), response.getHeader("Location"));
// Create a new ready to use request that the client will use
return Future.succeededFuture(client.getAbs(absoluteURI));
}
// We don't redirect
return null;
});
----

The policy handles the original `link:../../apidocs/io/vertx/core/http/HttpClientResponse.html[HttpClientResponse]` received and returns either `null`
or a `Future<HttpClientRequest>`.

- when `null` is returned, the original response is processed
- when a future is returned, the request will be sent on its successful completion
- when a future is returned, the exception handler set on the request is called on its failure

The returned request must be unsent so the original request handlers can be sent and the client can send it after.

==== 100-Continue handling

Expand Down
42 changes: 42 additions & 0 deletions src/main/java/examples/HTTPExamples.java
Expand Up @@ -16,6 +16,7 @@

package examples;

import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
Expand Down Expand Up @@ -554,6 +555,47 @@ public void example49(HttpClient client) {
});
}

public void exampleFollowRedirect01(HttpClient client) {

client.get("some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
}).setFollowRedirects(true).end();
}

public void exampleFollowRedirect02(Vertx vertx) {

HttpClient client = vertx.createHttpClient(
new HttpClientOptions()
.setMaxRedirects(32));

client.get("some-uri", response -> {
System.out.println("Received response with status code " + response.statusCode());
}).setFollowRedirects(true).end();
}

private String resolveURI(String base, String uriRef) {
throw new UnsupportedOperationException();
}

public void exampleFollowRedirect03(HttpClient client) {

client.redirectHandler(response -> {

// Only follow 301 code
if (response.statusCode() == 301 && response.getHeader("Location") != null) {

// Compute the redirect URI
String absoluteURI = resolveURI(response.request().absoluteURI(), response.getHeader("Location"));

// Create a new ready to use request that the client will use
return Future.succeededFuture(client.getAbs(absoluteURI));
}

// We don't redirect
return null;
});
}

public void example50(HttpClient client) {

HttpClientRequest request = client.put("some-uri", response -> {
Expand Down
9 changes: 5 additions & 4 deletions src/main/java/io/vertx/core/http/HttpClient.java
Expand Up @@ -19,6 +19,7 @@
import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.metrics.Measured;
Expand Down Expand Up @@ -1376,21 +1377,21 @@ WebSocketStream websocketStream(String requestURI, MultiMap headers, WebsocketVe
* The redirect handler is passed the {@link HttpClientResponse}, it can return an {@link HttpClientRequest} or {@code null}.
* <ul>
* <li>when null is returned, the original response is processed by the original request response handler</li>
* <li>when a new {@code HttpClientRequest} is returned, the client will send this new request</li>
* <li>when a new {@code Future<HttpClientRequest>} is returned, the client will send this new request</li>
* </ul>
* The handler must return a request unsent so the client can further configure it and send it.
* The handler must return a {@code Future<HttpClientRequest>} unsent so the client can further configure it and send it.
*
* @param handler the new redirect handler
* @return a reference to this, so the API can be used fluently
*/
@Fluent
HttpClient redirectHandler(Function<HttpClientResponse, HttpClientRequest> handler);
HttpClient redirectHandler(Function<HttpClientResponse, Future<HttpClientRequest>> handler);

/**
* @return the current redirect handler.
*/
@GenIgnore
Function<HttpClientResponse, HttpClientRequest> redirectHandler();
Function<HttpClientResponse, Future<HttpClientRequest>> redirectHandler();

/**
* Close the client. Closing will close down any pooled connections.
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/io/vertx/core/http/impl/HttpClientImpl.java
Expand Up @@ -55,7 +55,7 @@
*/
public class HttpClientImpl implements HttpClient, MetricsProvider {

private final Function<HttpClientResponse, HttpClientRequest> DEFAULT_HANDLER = resp -> {
private final Function<HttpClientResponse, Future<HttpClientRequest>> DEFAULT_HANDLER = resp -> {
int statusCode = resp.statusCode();
String location = resp.getHeader(HttpHeaders.LOCATION);
if (location != null && (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307)) {
Expand All @@ -73,7 +73,7 @@ public class HttpClientImpl implements HttpClient, MetricsProvider {
if (uri.getQuery() != null) {
requestURI += "?" + uri.getQuery();
}
return request(m, uri.getPort(), uri.getHost(), requestURI);
return Future.succeededFuture(request(m, uri.getPort(), uri.getHost(), requestURI));
}
return null;
};
Expand All @@ -88,7 +88,7 @@ public class HttpClientImpl implements HttpClient, MetricsProvider {
private final boolean useProxy;
private final SSLHelper sslHelper;
private volatile boolean closed;
private volatile Function<HttpClientResponse, HttpClientRequest> redirectHandler = DEFAULT_HANDLER;
private volatile Function<HttpClientResponse, Future<HttpClientRequest>> redirectHandler = DEFAULT_HANDLER;

public HttpClientImpl(VertxInternal vertx, HttpClientOptions options) {
if (options.isUseAlpn() && !options.isSsl()) {
Expand Down Expand Up @@ -839,7 +839,7 @@ public Metrics getMetrics() {
}

@Override
public HttpClient redirectHandler(Function<HttpClientResponse, HttpClientRequest> handler) {
public HttpClient redirectHandler(Function<HttpClientResponse, Future<HttpClientRequest>> handler) {
if (handler == null) {
handler = DEFAULT_HANDLER;
}
Expand All @@ -848,7 +848,7 @@ public HttpClient redirectHandler(Function<HttpClientResponse, HttpClientRequest
}

@Override
public Function<HttpClientResponse, HttpClientRequest> redirectHandler() {
public Function<HttpClientResponse, Future<HttpClientRequest>> redirectHandler() {
return redirectHandler;
}

Expand Down
106 changes: 58 additions & 48 deletions src/main/java/io/vertx/core/http/impl/HttpClientRequestImpl.java
Expand Up @@ -439,67 +439,77 @@ void handleDrained() {
}
}

private void handleNextRequest(HttpClientResponse resp, HttpClientRequestImpl next) {
next.handler(respHandler);
next.exceptionHandler(exceptionHandler());
next.endHandler(endHandler);
next.pushHandler = pushHandler;
next.followRedirects = followRedirects - 1;
next.written = written;
if (next.hostHeader != null) {
next.hostHeader = hostHeader;
}
if (next.headers != null) {
next.headers().addAll(headers);
}
ByteBuf body;
switch (next.method) {
case GET:
body = null;
break;
case OTHER:
next.rawMethod = rawMethod;
body = null;
break;
default:
if (cachedChunks != null) {
body = cachedChunks;
} else {
body = null;
}
break;
}
cachedChunks = null;
Future<Void> fut = Future.future();
fut.setHandler(ar -> {
if (ar.succeeded()) {
next.write(body, true);
} else {
handleException(ar.cause());
}
});
if (completed) {
fut.complete();
} else {
resp.exceptionHandler(err -> {
if (!fut.isComplete()) {
fut.fail(err);
}
});
resp.endHandler(v -> {
if (!fut.isComplete()) {
fut.complete();
}
});
}
}

protected void doHandleResponse(HttpClientResponseImpl resp) {
if (reset != null) {
stream.resetResponse(reset);
} else {
response = resp;
int statusCode = resp.statusCode();
if (followRedirects > 0 && statusCode >= 300 && statusCode < 400) {
HttpClientRequestImpl next = (HttpClientRequestImpl) client.redirectHandler().apply(resp);
Future<HttpClientRequest> next = client.redirectHandler().apply(resp);
if (next != null) {
next.handler(respHandler);
next.exceptionHandler(exceptionHandler());
next.endHandler(endHandler);
next.pushHandler = pushHandler;
next.followRedirects = followRedirects - 1;
next.written = written;
if (next.hostHeader != null) {
next.hostHeader = hostHeader;
}
if (next.headers != null) {
next.headers().addAll(headers);
}
ByteBuf body;
switch (next.method) {
case GET:
body = null;
break;
case OTHER:
next.rawMethod = rawMethod;
body = null;
break;
default:
if (cachedChunks != null) {
body = cachedChunks;
} else {
body = null;
}
break;
}
cachedChunks = null;
Future<Void> fut = Future.future();
fut.setHandler(ar -> {
next.setHandler(ar -> {
if (ar.succeeded()) {
next.write(body, true);
handleNextRequest(resp, (HttpClientRequestImpl) ar.result());
} else {
handleException(ar.cause());
}
});
if (completed) {
fut.complete();
} else {
resp.exceptionHandler(err -> {
if (!fut.isComplete()) {
fut.fail(err);
}
});
resp.endHandler(v -> {
if (!fut.isComplete()) {
fut.complete();
}
});
}
return;
}
}
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/io/vertx/core/http/package-info.java
Expand Up @@ -1084,6 +1084,50 @@
*
* Alternatively you can just parse the `Set-Cookie` headers yourself in the response.
*
* ==== 30x redirection handling
*
* The client can be configured to follow HTTP redirections: when the client receives an
* `301`, `302`, `303` or `307` status code, it follows the redirection provided by the `Location` response header
* and the response handler is passed the redirected response instead of the original response.
*
* Here’s an example:
*
* [source,$lang]
* ----
* {@link examples.HTTPExamples#exampleFollowRedirect01}
* ----
*
* The redirection policy is as follow
*
* * on a `301`, `302` or `303` status code, follow the redirection with a `GET` method
* * on a `307` status code, follow the redirection with the same HTTP method and the cached body
*
* WARNING: following redirections caches the request body
*
* The maximum redirects is `16` by default and can be changed with {@link io.vertx.core.http.HttpClientOptions#setMaxRedirects(int)}.
*
* [source,$lang]
* ----
* {@link examples.HTTPExamples#exampleFollowRedirect02}
* ----
*
* One size does not fit all and the default redirection policy may not be adapted to your needs.
*
* The default redirection policy can changed with a custom implementation:
*
* [source,$lang]
* ----
* {@link examples.HTTPExamples#exampleFollowRedirect03}
* ----
*
* The policy handles the original {@link io.vertx.core.http.HttpClientResponse} received and returns either `null`
* or a `Future<HttpClientRequest>`.
*
* - when `null` is returned, the original response is processed
* - when a future is returned, the request will be sent on its successful completion
* - when a future is returned, the exception handler set on the request is called on its failure
*
* The returned request must be unsent so the original request handlers can be sent and the client can send it after.
*
* ==== 100-Continue handling
*
Expand Down

0 comments on commit ce3814f

Please sign in to comment.