All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.arangodb.shaded.vertx.ext.web.client.impl.HttpContext Maven / Gradle / Ivy

There is a newer version: 7.8.0
Show newest version
/*
 * Copyright 2022 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 com.arangodb.shaded.vertx.ext.web.client.impl;

import com.arangodb.shaded.netty.handler.codec.http.multipart.HttpPostRequestEncoder;
import com.arangodb.shaded.vertx.core.*;
import com.arangodb.shaded.vertx.core.buffer.Buffer;
import com.arangodb.shaded.vertx.core.http.HttpClientRequest;
import com.arangodb.shaded.vertx.core.http.HttpClientResponse;
import com.arangodb.shaded.vertx.core.http.HttpHeaders;
import com.arangodb.shaded.vertx.core.http.RequestOptions;
import com.arangodb.shaded.vertx.core.http.impl.HttpClientInternal;
import com.arangodb.shaded.vertx.core.json.Json;
import com.arangodb.shaded.vertx.core.json.JsonObject;
import com.arangodb.shaded.vertx.core.streams.Pipe;
import com.arangodb.shaded.vertx.core.streams.ReadStream;
import com.arangodb.shaded.vertx.ext.web.client.HttpRequest;
import com.arangodb.shaded.vertx.ext.web.client.HttpResponse;
import com.arangodb.shaded.vertx.ext.web.client.WebClientOptions;
import com.arangodb.shaded.vertx.ext.web.client.spi.CacheStore;
import com.arangodb.shaded.vertx.ext.web.codec.spi.BodyStream;
import com.arangodb.shaded.vertx.ext.web.multipart.MultipartForm;

import java.util.*;

/**
 * @author Julien Viet
 */
public class HttpContext {

  private final Handler>> handler;
  private final HttpClientInternal client;
  private final WebClientOptions options;
  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(HttpClientInternal client, WebClientOptions options, List>> interceptors, Handler>> handler) {
    this.handler = handler;
    this.client = client;
    this.options = options;
    this.interceptors = interceptors;
  }

  /**
   * @return a duplicate of this context
   */
  public HttpContext duplicate() {
    return new HttpContext<>(client, options, interceptors, handler);
  }

  /**
   * @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 com.arangodb.shaded.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.options().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.vertx().getOrCreateContext(); redirects = 0; RequestOptions requestOptions; try { requestOptions = request.buildRequestOptions(); } catch (Exception e) { fail(e); return; } if (contentType != null) { String prev = requestOptions.getHeaders().get(HttpHeaders.CONTENT_TYPE); if (prev == null) { requestOptions.addHeader(HttpHeaders.CONTENT_TYPE, contentType); } else { contentType = prev; } } createRequest(requestOptions); } 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.bodyCodec().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 - 2024 Weber Informatics LLC | Privacy Policy