
io.vertx.ext.web.client.impl.HttpContext Maven / Gradle / Ivy
/*
* Copyright 2014 Red Hat, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.ext.web.client.impl;
import io.netty.handler.codec.http.QueryStringEncoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder;
import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.RequestOptions;
import io.vertx.core.http.impl.HttpClientImpl;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.SocketAddress;
import io.vertx.core.streams.Pipe;
import io.vertx.core.streams.ReadStream;
import io.vertx.ext.web.client.HttpRequest;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.spi.CacheStore;
import io.vertx.ext.web.codec.spi.BodyStream;
import io.vertx.ext.web.multipart.MultipartForm;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @author Julien Viet
*/
public class HttpContext {
private final Handler>> handler;
private final HttpClientImpl client;
private final List>> interceptors;
private Context context;
private HttpRequestImpl request;
private Object body;
private String contentType;
private Map attrs;
private int interceptorIdx;
private boolean invoking;
private boolean invokeNext;
private ClientPhase phase;
private RequestOptions requestOptions;
private HttpClientRequest clientRequest;
private HttpClientResponse clientResponse;
private Promise requestPromise;
private HttpResponse response;
private Throwable failure;
private int redirects;
private List redirectedLocations = Collections.emptyList();
private CacheStore privateCacheStore;
HttpContext(HttpClientImpl client, List>> interceptors, Handler>> handler) {
this.handler = handler;
this.client = client;
this.interceptors = interceptors;
}
/**
* @return the underlying client request, only available during {@link ClientPhase#SEND_REQUEST} and after
*/
public HttpClientRequest clientRequest() {
return clientRequest;
}
/**
* @return the underlying client request, only available during {@link ClientPhase#RECEIVE_RESPONSE} and after
*/
public HttpClientResponse clientResponse() {
return clientResponse;
}
/**
* @return the current event type
*/
public ClientPhase phase() {
return phase;
}
/**
* @return the current request object
*/
public HttpRequest request() {
return request;
}
/**
* @return the current request options
*/
public RequestOptions requestOptions() {
return requestOptions;
}
public void setRequestOptions(RequestOptions requestOptions) {
this.requestOptions = requestOptions;
}
/**
* @return the current response object, only available during {@link ClientPhase#DISPATCH_RESPONSE}
*/
public HttpResponse response() {
return response;
}
public HttpContext response(HttpResponse response) {
this.response = response;
return this;
}
/**
* @return the number of followed redirects, this value is initialized to {@code 0} during the prepare phase
*/
public int redirects() {
return redirects;
}
/**
* Set the number of followed redirects.
*
* @param redirects the new value
* @return a reference to this, so the API can be used fluently
*/
public HttpContext redirects(int redirects) {
this.redirects = redirects;
return this;
}
/**
* @return the request content type
*/
public String contentType() {
return contentType;
}
/**
* @return the body to send
*/
public Object body() {
return body;
}
/**
* @return the failure, only for {@link ClientPhase#FAILURE}
*/
public Throwable failure() {
return failure;
}
/**
* @return all traced redirects
*/
public List getRedirectedLocations() {
return redirectedLocations;
}
/**
* @return the private cache store set by {@link io.vertx.ext.web.client.WebClientSession}, or
* null if this is not a session client.
*/
public CacheStore privateCacheStore() {
return privateCacheStore;
}
/**
* Set the private cache store.
*
* @param cacheStore the cache store
* @return a reference to this, so the API can be used fluently
*/
public HttpContext privateCacheStore(CacheStore cacheStore) {
this.privateCacheStore = cacheStore;
return this;
}
/**
* Prepare the HTTP request, this executes the {@link ClientPhase#PREPARE_REQUEST} phase:
*
* - Traverse the interceptor chain
* - Execute the {@link ClientPhase#CREATE_REQUEST} phase
*
*/
public void prepareRequest(HttpRequest request, String contentType, Object body) {
this.request = (HttpRequestImpl) request;
this.contentType = contentType;
this.body = body;
fire(ClientPhase.PREPARE_REQUEST);
}
/**
* Create the HTTP request, this executes the {@link ClientPhase#CREATE_REQUEST} phase:
*
* - Traverse the interceptor chain
* - Create the {@link HttpClientRequest}
*
*/
public void createRequest(RequestOptions requestOptions) {
this.requestOptions = requestOptions;
fire(ClientPhase.CREATE_REQUEST);
}
/**
* Send the HTTP request, this executes the {@link ClientPhase#SEND_REQUEST} phase:
*
* - Traverse the interceptor chain
* - Send the actual request
*
*/
public void sendRequest(HttpClientRequest clientRequest) {
this.clientRequest = clientRequest;
fire(ClientPhase.SEND_REQUEST);
}
/**
* Follow the redirect, this executes the {@link ClientPhase#FOLLOW_REDIRECT} phase:
*
* - Traverse the interceptor chain
* - Send the redirect request
*
*/
private void handleFollowRedirect() {
fire(ClientPhase.CREATE_REQUEST);
}
/**
* Receive the HTTP response, this executes the {@link ClientPhase#RECEIVE_RESPONSE} phase:
*
* - Traverse the interceptor chain
* - Execute the {@link ClientPhase#DISPATCH_RESPONSE} phase
*
*/
public void receiveResponse(HttpClientResponse clientResponse) {
int sc = clientResponse.statusCode();
int maxRedirects = request.followRedirects ? client.getOptions().getMaxRedirects(): 0;
this.clientResponse = clientResponse;
if (redirects < maxRedirects && sc >= 300 && sc < 400) {
redirects++;
Future next = client.redirectHandler().apply(clientResponse);
if (next != null) {
if (redirectedLocations.isEmpty()) {
redirectedLocations = new ArrayList<>();
}
redirectedLocations.add(clientResponse.getHeader(HttpHeaders.LOCATION));
next.onComplete(ar -> {
if (ar.succeeded()) {
RequestOptions options = ar.result();
requestOptions = options;
fire(ClientPhase.FOLLOW_REDIRECT);
} else {
fail(ar.cause());
}
});
return;
}
}
this.clientResponse = clientResponse;
fire(ClientPhase.RECEIVE_RESPONSE);
}
/**
* Dispatch the HTTP response, this executes the {@link ClientPhase#DISPATCH_RESPONSE} phase:
*
* - Create the {@link HttpResponse}
* - Traverse the interceptor chain
* - Deliver the response to the response handler
*
*/
public void dispatchResponse(HttpResponse response) {
this.response = response;
fire(ClientPhase.DISPATCH_RESPONSE);
}
/**
* Fail the current HTTP context, this executes the {@link ClientPhase#FAILURE} phase:
*
* - Traverse the interceptor chain
* - Deliver the failure to the response handler
*
*
* @param cause the failure cause
* @return {@code true} if the failure can be dispatched
*/
public boolean fail(Throwable cause) {
if (phase == ClientPhase.FAILURE) {
// Already processing a failure
return false;
}
failure = cause;
fire(ClientPhase.FAILURE);
return true;
}
/**
* Fire a client execution phase.
*
* When an interception phase is in progress, the current phase is interrupted and
* a new interception phase begins.
*
* @param phase the phase to execute
*/
private void fire(ClientPhase phase) {
Objects.requireNonNull(phase);
this.phase = phase;
this.interceptorIdx = 0;
if (invoking) {
this.invokeNext = true;
} else {
next();
}
}
/**
* Call the next interceptor in the chain.
*/
public void next() {
if (invoking) {
invokeNext = true;
} else {
while (interceptorIdx < interceptors.size()) {
Handler> interceptor = interceptors.get(interceptorIdx);
invoking = true;
interceptorIdx++;
try {
interceptor.handle(this);
} catch (Exception e ) {
// Internal failure => directly dispatch a failure without the interceptor stack
// that could lead to infinite failures
failure = e;
invokeNext = false;
phase = ClientPhase.FAILURE;
break;
} finally {
invoking = false;
}
if (!invokeNext) {
return;
}
invokeNext = false;
}
interceptorIdx = 0;
execute();
}
}
private void execute() {
switch (phase) {
case PREPARE_REQUEST:
handlePrepareRequest();
break;
case CREATE_REQUEST:
handleCreateRequest();
break;
case SEND_REQUEST:
handleSendRequest();
break;
case FOLLOW_REDIRECT:
handleFollowRedirect();
break;
case RECEIVE_RESPONSE:
handleReceiveResponse();
break;
case DISPATCH_RESPONSE:
handleDispatchResponse();
break;
case FAILURE:
handleFailure();
break;
}
}
private void handleFailure() {
handler.handle(Future.failedFuture(failure));
}
private void handleDispatchResponse() {
handler.handle(Future.succeededFuture(response));
}
private void handlePrepareRequest() {
context = client.getVertx().getOrCreateContext();
String requestURI;
if (request.params != null && request.params.size() > 0) {
QueryStringEncoder enc = new QueryStringEncoder(request.uri);
request.params.forEach(param -> enc.addParam(param.getKey(), param.getValue()));
requestURI = enc.toString();
} else {
requestURI = request.uri;
}
int port = request.port();
String host = request.host();
RequestOptions options = new RequestOptions();
if (request.ssl != null && request.ssl != request.options.isSsl()) {
options.setServer(request.serverAddress)
.setMethod(request.method)
.setSsl(request.ssl)
.setHost(host)
.setPort(port)
.setURI(requestURI);
} else {
if (request.protocol != null && !request.protocol.equals("http") && !request.protocol.equals("https")) {
// we have to create an abs url again to parse it in HttpClient
try {
URI uri = new URI(request.protocol, null, host, port, requestURI, null, null);
options.setServer(request.serverAddress)
.setMethod(request.method)
.setAbsoluteURI(uri.toString());
} catch (URISyntaxException ex) {
fail(ex);
return;
}
} else {
options.setServer(request.serverAddress)
.setMethod(request.method)
.setHost(host)
.setPort(port)
.setURI(requestURI);
}
}
redirects = 0;
if (request.virtualHost != null) {
if (options.getServer() == null) {
options.setServer(SocketAddress.inetSocketAddress(options.getPort(), options.getHost()));
}
options.setHost(request.virtualHost);
}
request.mergeHeaders(options);
if (contentType != null) {
String prev = options.getHeaders().get(HttpHeaders.CONTENT_TYPE);
if (prev == null) {
options.addHeader(HttpHeaders.CONTENT_TYPE, contentType);
} else {
contentType = prev;
}
}
options.setTimeout(request.timeout);
createRequest(options);
}
private void handleCreateRequest() {
requestPromise = Promise.promise();
if (body != null || "application/json".equals(contentType)) {
if (body instanceof MultipartForm) {
MultipartFormUpload multipartForm;
try {
boolean multipart = "multipart/form-data".equals(contentType);
HttpPostRequestEncoder.EncoderMode encoderMode = this.request.multipartMixed ? HttpPostRequestEncoder.EncoderMode.RFC1738 : HttpPostRequestEncoder.EncoderMode.HTML5;
multipartForm = new MultipartFormUpload(context, (MultipartForm) this.body, multipart, encoderMode);
this.body = multipartForm;
} catch (Exception e) {
fail(e);
return;
}
for (String headerName : this.request.headers().names()) {
requestOptions.putHeader(headerName, this.request.headers().get(headerName));
}
multipartForm.headers().forEach(header -> {
requestOptions.putHeader(header.getKey(), header.getValue());
});
}
if (body instanceof ReadStream>) {
ReadStream stream = (ReadStream) body;
Pipe pipe = stream.pipe(); // Shouldn't this be called in an earlier phase ?
requestPromise.future().onComplete(ar -> {
if (ar.succeeded()) {
HttpClientRequest req = ar.result();
if (this.request.headers == null || !this.request.headers.contains(HttpHeaders.CONTENT_LENGTH)) {
req.setChunked(true);
}
pipe.endOnFailure(false);
pipe.to(req, ar2 -> {
clientRequest = null;
if (ar2.failed()) {
req.reset(0L, ar2.cause());
}
});
if (body instanceof MultipartFormUpload) {
((MultipartFormUpload) body).run();
}
} else {
// Test this
clientRequest = null;
pipe.close();
}
});
} else {
Buffer buffer;
if (body instanceof Buffer) {
buffer = (Buffer) body;
} else if (body instanceof JsonObject) {
buffer = Buffer.buffer(((JsonObject)body).encode());
} else {
buffer = Buffer.buffer(Json.encode(body));
}
requestOptions.putHeader(HttpHeaders.CONTENT_LENGTH, "" + buffer.length());
requestPromise.future().onSuccess(request -> {
clientRequest = null;
request.end(buffer);
});
}
} else {
requestPromise.future().onSuccess(request -> {
clientRequest = null;
request.end();
});
}
client.request(requestOptions)
.onComplete(ar1 -> {
if (ar1.succeeded()) {
sendRequest(ar1.result());
} else {
fail(ar1.cause());
requestPromise.fail(ar1.cause());
}
});
}
private void handleReceiveResponse() {
HttpClientResponse resp = clientResponse;
Context context = Vertx.currentContext();
Promise> promise = Promise.promise();
promise.future().onComplete(r -> {
// We are running on a context (the HTTP client mandates it)
context.runOnContext(v -> {
if (r.succeeded()) {
dispatchResponse(r.result());
} else {
fail(r.cause());
}
});
});
resp.exceptionHandler(err -> {
if (!promise.future().isComplete()) {
promise.fail(err);
}
});
Pipe pipe = resp.pipe();
request.codec.create(ar1 -> {
if (ar1.succeeded()) {
BodyStream stream = ar1.result();
pipe.to(stream, ar2 -> {
if (ar2.succeeded()) {
stream.result().onComplete(ar3 -> {
if (ar3.succeeded()) {
promise.complete(new HttpResponseImpl<>(
resp.version(),
resp.statusCode(),
resp.statusMessage(),
resp.headers(),
resp.trailers(),
resp.cookies(),
stream.result().result(),
redirectedLocations
));
} else {
promise.fail(ar3.cause());
}
});
} else {
promise.fail(ar2.cause());
}
});
} else {
pipe.close();
fail(ar1.cause());
}
});
}
private void handleSendRequest() {
clientRequest.response(ar -> {
if (ar.succeeded()) {
receiveResponse(ar.result().pause());
} else {
fail(ar.cause());
}
});
requestPromise.complete(clientRequest);
}
public T get(String key) {
return attrs != null ? (T) attrs.get(key) : null;
}
public HttpContext set(String key, Object value) {
if (value == null) {
if (attrs != null) {
attrs.remove(key);
}
} else {
if (attrs == null) {
attrs = new HashMap<>();
}
attrs.put(key, value);
}
return this;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy