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.vertx.core.*;
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.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.codec.spi.BodyStream;
import io.vertx.ext.web.multipart.MultipartForm;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author Julien Viet
*/
public class HttpContext {
private final Context context;
private final Handler>> handler;
private final HttpClientImpl client;
private final List>> interceptors;
private HttpRequestImpl request;
private Object body;
private String contentType;
private Map attrs;
private Iterator>> it;
private ClientPhase phase;
private HttpClientRequest clientRequest;
private HttpClientResponse clientResponse;
private HttpResponse response;
private Throwable failure;
private int redirects;
HttpContext(Context context, HttpClientImpl client, List>> interceptors, Handler>> handler) {
this.context = context;
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 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;
}
/**
* Prepare the HTTP request, this executes the {@link ClientPhase#PREPARE_REQUEST} phase:
*
* - Traverse the interceptor chain
* - Execute the {@link ClientPhase#SEND_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);
}
/**
* Send the HTTP request, this executes the {@link ClientPhase#SEND_REQUEST} phase:
*
* - Create the {@link HttpClientRequest}
* - Traverse the interceptor chain
* - Send the actual request
*
*/
public void sendRequest(HttpClientRequest clientRequest) {
this.clientRequest = clientRequest;
fire(ClientPhase.SEND_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;
if (redirects < maxRedirects && sc >= 300 && sc < 400) {
redirects++;
Future next = client.redirectHandler().apply(clientResponse);
if (next != null) {
next.setHandler(ar -> {
if (ar.succeeded()) {
HttpClientRequest nextRequest = ar.result();
if (request.headers != null) {
nextRequest.headers().addAll(request.headers);
}
sendRequest(nextRequest);
} 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;
}
/**
* Call the next interceptor in the chain.
*/
public void next() {
if (it.hasNext()) {
Handler> next = it.next();
next.handle(this);
} else {
it = null;
execute();
}
}
private void fire(ClientPhase phase) {
this.phase = phase;
this.it = interceptors.iterator();
next();
}
private void execute() {
switch (phase) {
case PREPARE_REQUEST:
handlePrepareRequest();
break;
case SEND_REQUEST:
handleSendRequest();
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() {
HttpClientRequest req;
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;
if (request.ssl != null && request.ssl != request.options.isSsl()) {
req = client.request(request.method, request.serverAddress, new RequestOptions().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);
req = client.requestAbs(request.method, request.serverAddress, uri.toString());
} catch (URISyntaxException ex) {
fail(ex);
return;
}
} else {
req = client.request(request.method, request.serverAddress, port, host, requestURI);
}
}
if (request.virtualHost != null) {
String virtalHost = request.virtualHost;
if (port != 80) {
virtalHost += ":" + port;
}
req.setHost(virtalHost);
}
redirects = 0;
if (request.headers != null) {
req.headers().addAll(request.headers);
}
sendRequest(req);
}
private void handleReceiveResponse() {
HttpClientResponse resp = clientResponse;
Context context = Vertx.currentContext();
Future> fut = Future.future();
fut.setHandler(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 (!fut.isComplete()) {
fut.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().setHandler(ar3 -> {
if (ar3.succeeded()) {
fut.complete(new HttpResponseImpl(
resp.version(),
resp.statusCode(),
resp.statusMessage(),
resp.headers(),
resp.trailers(),
resp.cookies(),
stream.result().result()));
} else {
fut.fail(ar3.cause());
}
});
} else {
fut.fail(ar2.cause());
}
});
} else {
pipe.close();
fail(ar1.cause());
}
});
}
private void handleSendRequest() {
Future responseFuture = Future.future().setHandler(ar -> {
if (ar.succeeded()) {
HttpClientResponse resp = ar.result();
resp.pause();
receiveResponse(resp);
} else {
fail(ar.cause());
}
});
HttpClientRequest req = clientRequest;
req.handler(responseFuture::tryComplete);
if (request.timeout > 0) {
req.setTimeout(request.timeout);
}
if (body != null) {
if (contentType != null) {
String prev = req.headers().get(HttpHeaders.CONTENT_TYPE);
if (prev == null) {
req.putHeader(HttpHeaders.CONTENT_TYPE, contentType);
} else {
contentType = prev;
}
}
if (body instanceof MultiMap) {
MultipartForm parts = MultipartForm.create();
MultiMap attributes = (MultiMap) body;
for (Map.Entry attribute : attributes) {
parts.attribute(attribute.getKey(), attribute.getValue());
}
body = parts;
}
if (body instanceof MultipartForm) {
MultipartFormUpload multipartForm;
try {
boolean multipart = "multipart/form-data".equals(contentType);
multipartForm = new MultipartFormUpload(context, (MultipartForm) this.body, multipart);
this.body = multipartForm;
} catch (Exception e) {
responseFuture.tryFail(e);
return;
}
for (String headerName : request.headers().names()) {
req.putHeader(headerName, request.headers().get(headerName));
}
multipartForm.headers().forEach(header -> {
req.putHeader(header.getKey(), header.getValue());
});
multipartForm.run();
}
if (body instanceof ReadStream>) {
ReadStream stream = (ReadStream) body;
if (request.headers == null || !request.headers.contains(HttpHeaders.CONTENT_LENGTH)) {
req.setChunked(true);
}
stream.pipeTo(req, ar -> {
if (ar.failed()) {
responseFuture.tryFail(ar.cause());
req.reset();
}
});
} 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));
}
req.exceptionHandler(responseFuture::tryFail);
req.end(buffer);
}
} else {
req.exceptionHandler(responseFuture::tryFail);
req.end();
}
}
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