Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.gravitino.client;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.gravitino.auth.AuthConstants;
import org.apache.gravitino.dto.responses.ErrorResponse;
import org.apache.gravitino.exceptions.RESTException;
import org.apache.gravitino.rest.RESTRequest;
import org.apache.gravitino.rest.RESTResponse;
import org.apache.gravitino.rest.RESTUtils;
import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.impl.EnglishReasonPhraseCatalog;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.net.URIBuilder;
/**
* An HttpClient for usage with the REST catalog.
*
*
This class provides functionality for making HTTP requests to a REST API and processing the
* corresponding responses. It supports common HTTP methods like GET, POST, PUT, DELETE, and HEAD.
* Additionally, it allows handling server error responses using a custom error handler.
*
*
Referred from core/src/main/java/org/apache/iceberg/rest/HTTPClient.java
*/
public class HTTPClient implements RESTClient {
private static final String VERSION_HEADER = "application/vnd.gravitino.v1+json";
private final String uri;
private final CloseableHttpClient httpClient;
private final ObjectMapper mapper;
private final AuthDataProvider authDataProvider;
// Handler to be executed before connecting to the server.
private final Runnable beforeConnectHandler;
// Handler status
enum HandlerStatus {
// The handler has not been executed yet.
Start,
// The handler has been executed successfully.
Finished,
// The handler is currently running.
Running,
}
// The status of the handler.
private volatile HandlerStatus handlerStatus = HandlerStatus.Start;
/**
* Constructs an instance of HTTPClient with the provided information.
*
* @param uri The base URI of the REST API.
* @param baseHeaders A map of base headers to be included in all HTTP requests.
* @param objectMapper The ObjectMapper used for JSON serialization and deserialization.
* @param authDataProvider The provider of authentication data.
* @param beforeConnectHandler The function to be executed before connecting to the server.
*/
private HTTPClient(
String uri,
Map baseHeaders,
ObjectMapper objectMapper,
AuthDataProvider authDataProvider,
Runnable beforeConnectHandler) {
this.uri = uri;
this.mapper = objectMapper;
HttpClientBuilder clientBuilder = HttpClients.custom();
if (baseHeaders != null) {
clientBuilder.setDefaultHeaders(
baseHeaders.entrySet().stream()
.map(e -> new BasicHeader(e.getKey(), e.getValue()))
.collect(Collectors.toList()));
}
this.httpClient = clientBuilder.build();
this.authDataProvider = authDataProvider;
if (beforeConnectHandler == null) {
handlerStatus = HandlerStatus.Finished;
}
this.beforeConnectHandler = beforeConnectHandler;
}
/**
* Extracts the response body as a string from the provided HTTP response.
*
* @param response The HTTP response from which the response body will be extracted.
* @return The response body as a string.
* @throws RESTException If an error occurs during conversion of the response body to a string.
*/
private String extractResponseBodyAsString(CloseableHttpResponse response) {
try {
if (response.getEntity() == null) {
return null;
}
// EntityUtils.toString returns null when HttpEntity.getContent returns null.
return EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (IOException | ParseException e) {
throw new RESTException(e, "Failed to convert HTTP response body to string");
}
}
/**
* Checks if the response indicates a successful response.
*
*
According to the spec, the only currently defined/used "success" responses are 200 and 202.
*
* @param response The response to check for success.
* @return True if the response is successful, false otherwise.
*/
private boolean isSuccessful(CloseableHttpResponse response) {
int code = response.getCode();
return code == HttpStatus.SC_OK
|| code == HttpStatus.SC_ACCEPTED
|| code == HttpStatus.SC_NO_CONTENT;
}
/**
* Builds an error response based on the provided HTTP response.
*
*
This method extracts the reason phrase from the response and uses it as the message for the
* ErrorResponse. If the reason phrase doesn't exist, it retrieves the standard reason phrase from
* the English phrase catalog.
*
* @param response The response from which the ErrorResponse is built.
* @return An ErrorResponse object representing the REST error response.
*/
private ErrorResponse buildRestErrorResponse(CloseableHttpResponse response) {
String responseReason = response.getReasonPhrase();
String message =
responseReason != null && !responseReason.isEmpty()
? responseReason
: EnglishReasonPhraseCatalog.INSTANCE.getReason(response.getCode(), null /* ignored */);
return ErrorResponse.restError(message);
}
/**
* Processes a failed response through the provided error.
*
*
This method takes a response representing a failed response from an HTTP request. It tries
* to parse the response body using the provided parseResponse method.
*
* @param response The failed response from the HTTP request.
* @param responseBody The response body as a string (can be null).
* @param errorHandler The error handler (as a Consumer) used to handle the error response.
* @throws RESTException If the error handler does not throw an exception or an error occurs
* during parsing.
*/
private void throwFailure(
CloseableHttpResponse response, String responseBody, Consumer errorHandler) {
ErrorResponse errorResponse = null;
if (responseBody != null) {
try {
if (errorHandler instanceof ErrorHandler) {
errorResponse =
((ErrorHandler) errorHandler).parseResponse(response.getCode(), responseBody, mapper);
} else {
errorResponse =
ErrorResponse.unknownError(
String.format(
"Unknown error handler %s, response body won't be parsed %s",
errorHandler.getClass().getName(), responseBody));
}
} catch (UncheckedIOException | IllegalArgumentException e) {
// It's possible to receive a non-successful response that isn't a properly defined
// BaseResponse due to various reasons, such as server misconfiguration or
// unanticipated external factors.
// In such cases, we handle the situation by building an error response for the user.
// Examples of such scenarios include network timeouts or load balancers returning default
// 5xx responses.
}
}
if (errorResponse == null) {
errorResponse = buildRestErrorResponse(response);
}
errorHandler.accept(errorResponse);
// Throw an exception in case the provided error handler does not throw.
throw new RESTException("Unhandled error: %s", errorResponse);
}
/**
* Builds a URI for the HTTP request using the given path and query parameters.
*
*
This method constructs a URI by combining the base URI (stored in the "uri" field) with the
* provided path. If query parameters are provided in the "params" map, they are added to the URI,
* ensuring proper encoding of query parameters and that the URI is well-formed.
*
* @param path The URL path to append to the base URI.
* @param params A map of query parameters (key-value pairs) to include in the URI (can be null).
* @return The constructed URI for the HTTP request.
* @throws RESTException If there is an issue building the URI from the base URI and query
* parameters.
*/
private URI buildUri(String path, Map params) {
String baseUri = String.format("%s/%s", uri, path);
try {
URIBuilder builder = new URIBuilder(baseUri);
if (params != null) {
params.forEach(builder::addParameter);
}
return builder.build();
} catch (URISyntaxException e) {
throw new RESTException(
"Failed to create request URI from base %s, params %s", baseUri, params);
}
}
/**
* Executes an HTTP request and processes the corresponding response.
*
*
This method is a helper function to execute HTTP requests.
*
* @param method The HTTP method to use (e.g., GET, POST, PUT, DELETE).
* @param path The URL path to send the request to.
* @param queryParams A map of query parameters (key-value pairs) to include in the request URL
* (can be null).
* @param requestBody The content to place in the request body (can be null).
* @param responseType The class type of the response for deserialization (Must be registered with
* the ObjectMapper).
* @param headers A map of request headers (key-value pairs) to include in the request (can be
* null).
* @param errorHandler The error handler delegated for HTTP responses, which handles server error
* responses.
* @param The class type of the response for deserialization. (Must be registered with the
* ObjectMapper).
* @return The response entity parsed and converted to its type T.
*/
private T execute(
Method method,
String path,
Map queryParams,
Object requestBody,
Class responseType,
Map headers,
Consumer errorHandler) {
return execute(
method, path, queryParams, requestBody, responseType, headers, errorHandler, h -> {});
}
/**
* Executes an HTTP request and processes the corresponding response with support for response
* headers.
*
*
The method constructs the HTTP request using the provided parameters and sends it to the
* server. It then processes the server's response, handling successful responses and server error
* responses accordingly.
*
*
Response headers from the server are extracted and passed to the responseHeaders Consumer
* for further processing by the caller.
*
* @param method The HTTP method to use (e.g., GET, POST, PUT, DELETE).
* @param path The URL path to send the request to.
* @param queryParams A map of query parameters (key-value pairs) to include in the request URL
* (can be null).
* @param requestBody The content to place in the request body (can be null).
* @param responseType The class type of the response for deserialization (Must be registered with
* the ObjectMapper).
* @param headers A map of request headers (key-value pairs) to include in the request (can be
* null).
* @param errorHandler The error handler delegated for HTTP responses, which handles server error
* responses.
* @param responseHeaders The consumer of the response headers for further processing.
* @param The class type of the response for deserialization. (Must be registered with the
* ObjectMapper).
* @return The response entity parsed and converted to its type T.
* @throws RESTException If the provided path is malformed, if there is an issue with the HTTP
* request or response processing, or if the errorHandler does not throw an exception for
* server error responses.
*/
@SuppressWarnings("deprecation")
private T execute(
Method method,
String path,
Map queryParams,
Object requestBody,
Class responseType,
Map headers,
Consumer errorHandler,
Consumer