Please wait. This can take some minutes ...
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.
com.ifengxue.http.proxy.RequestInvoker Maven / Gradle / Ivy
/*
* Copyright 2019 https://www.ifengxue.com
*
* Licensed 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 com.ifengxue.http.proxy;
import com.ifengxue.http.HttpClientException;
import com.ifengxue.http.annotation.BodyType;
import com.ifengxue.http.annotation.Delete;
import com.ifengxue.http.annotation.Get;
import com.ifengxue.http.annotation.Head;
import com.ifengxue.http.annotation.HttpMethod;
import com.ifengxue.http.annotation.Patch;
import com.ifengxue.http.annotation.Post;
import com.ifengxue.http.annotation.Put;
import com.ifengxue.http.annotation.ResponseType;
import com.ifengxue.http.annotation.Rest;
import com.ifengxue.http.collection.MultiMap;
import com.ifengxue.http.collection.MultiValueMap;
import com.ifengxue.http.contract.Callback;
import com.ifengxue.http.contract.HttpOperations;
import com.ifengxue.http.contract.HttpResponse;
import com.ifengxue.http.executor.HttpExecutor;
import com.ifengxue.http.executor.HttpExecutorFactory;
import com.ifengxue.http.executor.Request;
import com.ifengxue.http.executor.Request.Builder;
import com.ifengxue.http.parser.HttpParser;
import com.ifengxue.http.parser.HttpParserFactory;
import com.ifengxue.http.parser.StreamHttpParser;
import com.ifengxue.http.util.TypeUtil;
import com.ifengxue.http.util.Version;
import io.mikael.urlbuilder.UrlBuilder;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.CloseableHttpClient;
/**
* 请求调用代理
*/
public class RequestInvoker implements InvocationHandler, HttpClientConfig, HttpOperations {
private static final Timeout NOT_SET_TIMEOUT = new Timeout();
private final Class proxyInterface;
private final HttpExecutorFactory executorFactory = new HttpExecutorFactory();
private final HttpParserFactory parserFactory = new HttpParserFactory();
private final LinkedList interceptors = new LinkedList<>();
private final Map headers = new HashMap<>();
private final MultiMap parameters = new MultiValueMap<>();
private final Map httpOperationsDelegateMethod = new ConcurrentHashMap<>();
private final Rest rest;
private volatile URL prefixUrl;
private Charset charset = UTF_8;
private HttpHost proxy;
private Timeout timeout = NOT_SET_TIMEOUT;
private volatile UsernamePasswordCredentials basicAuthCredentials;
private AtomicBoolean basicAuthInterceptorHasSet = new AtomicBoolean(false);
/**
* 自定义的http client
*/
private CloseableHttpClient customHttpClient;
private ExecutorService threadPool;
private RequestInvoker(Class proxyInterface) {
this.proxyInterface = proxyInterface;
rest = proxyInterface.getAnnotation(Rest.class);
setUserAgent("Http-client-proxy/" + Version.getVersion() + " Java/" + SystemUtils.JAVA_VERSION);
}
/**
* 使用Thread.currentThread().getContextClassLoader()
作为类加载器创建请求代理
*
* @param proxyInterface 被代理的接口
*/
public static T create(Class proxyInterface) {
return create(proxyInterface, Thread.currentThread().getContextClassLoader());
}
/**
* 创建请求代理
*
* @param proxyInterface 被代理的接口
* @param classLoader 类加载器
*/
@SuppressWarnings("unchecked")
public static T create(Class proxyInterface, ClassLoader classLoader) {
Objects.requireNonNull(proxyInterface, "proxyInterface cannot be null");
if (!proxyInterface.isInterface()) {
throw new IllegalArgumentException(proxyInterface.getName() + " not an interface");
}
classLoader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
// 当Thread.currentThread.getContextClassLoader()为null时重新赋值
classLoader = classLoader == null ? RequestInvoker.class.getClassLoader() : classLoader;
return (T) Proxy.newProxyInstance(classLoader,
new Class[]{proxyInterface, HttpClientConfig.class, HttpOperations.class},
new RequestInvoker(proxyInterface));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("toString") && args == null) {
return proxyInterface.getSimpleName() + " is proxy by " + getClass().getSimpleName() + "!";
}
if (method.getName().equals("hashCode") && args == null) {
return Objects.hashCode(this);
}
if (method.getName().equals("equals") && args != null && args.length == 1) {
return Objects.equals(this, args[0]);
}
if (method.getDeclaringClass() == HttpClientConfig.class) {
return method.invoke(this, args);
}
if (method.isDefault()) {
Constructor constructor = Lookup.class.getDeclaredConstructor(Class.class);
constructor.setAccessible(true);
return constructor.newInstance(method.getDeclaringClass())
.in(method.getDeclaringClass())
.unreflectSpecial(method, method.getDeclaringClass())
.bindTo(proxy)
.invokeWithArguments(args);
}
doInit();
// invoke after init config
if (method.getDeclaringClass() == HttpOperations.class) {
return findHttpOperationsDelegateMethod(method).invoke(this, createNewArgs(method, args));
}
RequestMethodHolder requestMethodHolder = createHolder(method);
MultiMap parameterMap = Resolver.resolveParameters(method, args, requestMethodHolder.getBodyType());
// 解析request
Request request = Request.Builder.newBuilder(requestMethodHolder.getMethod())
.setPrefixUrl(prefixUrl)
.setSuffixUrl(requestMethodHolder.getSuffixUrl())
.addHeaders(headers)
.addHeaders(Resolver.resolveHeaders(method))
.addHeaders(Resolver.resolveParamHeaders(method, args))
.addParameters(parameters)
.addParameters(parameterMap)
.setBody(Resolver.resolveBody(method, args))
.addQueryParameterNames(Resolver.resolveQueryParameterNames(method, args))
.setCallbackHandler(Resolver.findCallbackHandler(method, args))
.build();
if (request.getCallbackHandler() == null) {
Supplier supplier = new HttpInvokerSupplier(method, request, requestMethodHolder);
if (TypeUtil.isAsyncType(method.getReturnType())) {
return CompletableFuture.supplyAsync(supplier, getThreadPool());
}
return supplier.get();
}
// do callback
@SuppressWarnings("unchecked")
Callback callback = (Callback) request.getCallbackHandler().getCallback();
Supplier supplier = new HttpInvokerSupplier(method, request, requestMethodHolder.getBodyType(),
requestMethodHolder.getResponseType(), request.getCallbackHandler().getResponseType());
CompletableFuture.supplyAsync(supplier, getThreadPool())
.whenCompleteAsync(callback::call, getThreadPool());
return null;
}
private Object[] createNewArgs(Method method, Object[] args) {
Object[] newArgs = new Object[method.getParameterTypes().length + 1];
if (newArgs.length > 1) {
System.arraycopy(args, 0, newArgs, 1, args.length);
}
newArgs[0] = method;
return newArgs;
}
private Method findHttpOperationsDelegateMethod(Method method) {
return httpOperationsDelegateMethod.computeIfAbsent(method, m -> {
Class[] parameterTypes = method.getParameterTypes();
Class[] newParameterTypes = new Class[parameterTypes.length + 1];
if (parameterTypes.length > 0) {
System.arraycopy(parameterTypes, 0, newParameterTypes, 1, parameterTypes.length);
}
newParameterTypes[0] = Method.class;
try {
Method delegateMethod = RequestInvoker.class.getDeclaredMethod(method.getName(), newParameterTypes);
delegateMethod.setAccessible(true);
return delegateMethod;
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Cannot find matched delegate method " + method.getName());
}
});
}
/**
* 创建http executor,并设置基本属性
*/
private HttpExecutor createHttpExecutor(BodyType bodyType) {
HttpExecutor executor;
if (customHttpClient == null) {
executor = executorFactory.getHttpExecutor(bodyType);
} else {
executor = executorFactory.getHttpExecutor(bodyType, customHttpClient);
}
executor.setCharset(charset);
executor.setProxy(this.proxy);
executor.setTimeout(timeout.socketTimeout, timeout.connectTimeout);
return executor;
}
private void closeHttpResponse(HttpResponse httpResponse) {
if (httpResponse == null) {
return;
}
try {
httpResponse.close();
} catch (Exception ex) {
// ignore
}
}
private RequestMethodHolder createHolder(Method method) {
if (method.isAnnotationPresent(Get.class)) {
Get get = method.getAnnotation(Get.class);
return new RequestMethodHolder(HttpMethod.GET.name(), get.value(), get.bodyType(), get.responseType());
} else if (method.isAnnotationPresent(Post.class)) {
Post post = method.getAnnotation(Post.class);
return new RequestMethodHolder(HttpMethod.POST.name(), post.value(), post.bodyType(), post.responseType());
} else if (method.isAnnotationPresent(Put.class)) {
Put put = method.getAnnotation(Put.class);
return new RequestMethodHolder(HttpMethod.PUT.name(), put.value(), put.bodyType(), put.responseType());
} else if (method.isAnnotationPresent(Patch.class)) {
Patch patch = method.getAnnotation(Patch.class);
return new RequestMethodHolder(HttpMethod.PATCH.name(), patch.value(), patch.bodyType(), patch.responseType());
} else if (method.isAnnotationPresent(Delete.class)) {
Delete delete = method.getAnnotation(Delete.class);
return new RequestMethodHolder(HttpMethod.DELETE.name(), delete.value(), delete.bodyType(),
delete.responseType());
} else if (method.isAnnotationPresent(Head.class)) {
Head head = method.getAnnotation(Head.class);
return new RequestMethodHolder(HttpMethod.DELETE.name(), head.value(), head.bodyType(),
ResponseType.HEADER);
} else {
throw new IllegalStateException("method does not specify a matching annotation " + Arrays
.toString(new String[]{
Get.class.getName(), Post.class.getName(), Put.class.getName(), Patch.class.getName(),
Delete.class.getName(), Head.class.getName(),
}));
}
}
private void initPrefixUrl(String host) {
if (StringUtils.isBlank(host) && (rest == null || StringUtils.isBlank(rest.value()))) {
return;
}
if (StringUtils.isBlank(host)) {
prefixUrl = decodeURL(UrlBuilder.fromString(rest.value()).toUrl());
return;
}
if (rest == null || rest.value().isEmpty()) {
prefixUrl = decodeURL(UrlBuilder.fromString(host).toUrl());
return;
}
UrlBuilder rawHost = UrlBuilder.fromString(rest.value());
URL replaceHost = decodeURL(UrlBuilder.fromString(host).toUrl());
prefixUrl = decodeURL(rawHost.withScheme(replaceHost.getProtocol())
.withHost(replaceHost.getHost())
.withPort(replaceHost.getPort() == -1 ? null : replaceHost.getPort())
.withUserInfo(replaceHost.getUserInfo())
.withPath(replaceHost.getPath())
.withQuery(replaceHost.getQuery())
.toUrl());
}
private URL decodeURL(URL url) {
try {
return new URL(URLDecoder.decode(url.toString(), StandardCharsets.UTF_8.name()));
} catch (MalformedURLException e) {
throw new IllegalArgumentException("invalid url:" + url.toString(), e);
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("invalid charset", e);
}
}
@Override
public synchronized void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
@Override
public synchronized void removeInterceptors(Interceptor interceptor) throws NoSuchElementException {
if (!interceptors.removeIf(other -> other == interceptor)) {
throw new NoSuchElementException("No matching interceptors, interceptor instance is " + interceptor);
}
}
@Override
public synchronized void removeInterceptors(Class interceptorClass)
throws NoSuchElementException {
if (!interceptors.removeIf(interceptor -> interceptor.getClass() == interceptorClass)) {
throw new NoSuchElementException("No matching interceptors, interceptor type is " + interceptorClass.getName());
}
}
@Override
public void setCharset(Charset charset) {
this.charset = charset;
}
@Override
public synchronized void setUserAgent(String userAgent) {
addHeader(HttpHeaders.USER_AGENT, userAgent);
}
@Override
public synchronized void addHeader(String name, String value) {
headers.put(name, value);
}
@Override
public synchronized void addParameter(String name, Object value) {
parameters.put(name, value);
}
@Override
public void setHost(String host) {
if (StringUtils.isBlank(host)) {
throw new IllegalArgumentException("host can't be empty value");
}
synchronized (this) {
this.prefixUrl = null;
initPrefixUrl(host);
}
}
@Override
public synchronized void setProxy(String host, int port, String scheme) {
proxy = new HttpHost(host, port, scheme);
}
@Override
public synchronized void setTimeout(int socketTimeout, int connectTimeout) {
timeout = new Timeout(socketTimeout, connectTimeout);
}
@Override
public synchronized void setHttpClient(CloseableHttpClient httpClient) {
this.customHttpClient = Objects.requireNonNull(httpClient);
}
private ExecutorService getThreadPool() {
return Optional.ofNullable(threadPool).orElseGet(HttpExecutorFactory::getDefaultThreadPool);
}
@Override
public synchronized void setThreadPool(ExecutorService threadPool) {
this.threadPool = threadPool;
}
@Override
public synchronized void setBasicAuth(String username, String password) {
if (basicAuthInterceptorHasSet.get()) {
removeInterceptors(BasicAuthInterceptor.class);
}
basicAuthCredentials = new UsernamePasswordCredentials(username, password);
basicAuthInterceptorHasSet.set(false);
}
private void doInit() {
if (prefixUrl == null) {
synchronized (this) {
if (prefixUrl == null) {
initPrefixUrl(null);
}
}
}
// 设置 http basic auth 拦截器
if (basicAuthCredentials != null && !basicAuthInterceptorHasSet.get()) {
synchronized (this) {
if (!basicAuthInterceptorHasSet.get()) {
interceptors.addFirst(new BasicAuthInterceptor(
basicAuthCredentials.getUserName(), basicAuthCredentials.getPassword(), charset));
basicAuthInterceptorHasSet.set(true);
}
}
}
}
@Override
public T exchange(@Nonnull String url, @Nonnull HttpMethod method, @Nonnull BodyType bodyType,
@Nonnull ResponseType responseType, @Nonnull Type responseEntityType, @Nullable Map httpHeaders,
@Nullable Map uriVariables, @Nullable Object requestBody) {
throw new UnsupportedOperationException("Delegate to private exchange method");
}
@SuppressWarnings({"unchecked", "unused"})
private T exchange(@Nonnull Method invokeMethod, @Nonnull String url, @Nonnull HttpMethod method,
@Nonnull BodyType bodyType, @Nonnull ResponseType responseType, @Nonnull Type responseEntityType,
@Nullable Map httpHeaders, @Nullable Map uriVariables,
@Nullable Object requestBody) {
doInit();
MultiMap uriVariableMultiMap = Resolver.resolveParameters(uriVariables);
Request request = Builder.newBuilder(method.name())
.setPrefixUrl(prefixUrl)
.setSuffixUrl(url)
.addHeaders(headers)
.addHeaders(Optional.ofNullable(httpHeaders).orElseGet(Collections::emptyMap))
.addParameters(parameters)
.addParameters(uriVariableMultiMap)
.setBody(requestBody)
.addQueryParameterNames(uriVariableMultiMap.keySet())
.build();
Supplier supplier = new HttpInvokerSupplier(invokeMethod, request, bodyType, responseType,
responseEntityType);
if (!TypeUtil.isAsyncType(responseEntityType)) {
return (T) supplier.get();
}
return (T) CompletableFuture.supplyAsync(supplier, getThreadPool());
}
@AllArgsConstructor
@Getter
@ToString
static class Timeout {
private final int socketTimeout;
private final int connectTimeout;
public Timeout() {
this(-1, -1);
}
}
@AllArgsConstructor
@Getter
private static class RequestMethodHolder {
private final String method;
private final String suffixUrl;
private final BodyType bodyType;
private final ResponseType responseType;
}
private class HttpInvokerSupplier implements Supplier {
private final Method method;
private final Request request;
private final BodyType bodyType;
private final ResponseType responseType;
private final Type responseEntityType;
HttpInvokerSupplier(Method method, Request request, RequestMethodHolder requestMethodHolder) {
this(method, request, requestMethodHolder.getBodyType(), requestMethodHolder.getResponseType(),
method.getGenericReturnType());
}
HttpInvokerSupplier(Method method, Request request, BodyType bodyType, ResponseType responseType,
Type responseEntityType) {
this.method = method;
this.request = request;
this.bodyType = bodyType;
this.responseType = responseType;
this.responseEntityType = responseEntityType;
}
@Override
public Object get() {
HttpExecutor executor = createHttpExecutor(bodyType);
try {
// 执行前置拦截
for (Interceptor interceptor : interceptors) {
Object result = interceptor.beforeRequest(method, request, bodyType, responseType, executor);
if (result != null) {
return result;
}
}
} catch (Exception e) {
if (e instanceof HttpClientException) {
throw e;
}
throw new HttpClientException("error executing pre interceptor", e);
}
// 执行HTTP请求
HttpResponse httpResponse;
try {
httpResponse = executor
.execute(request, bodyType, responseType);
} catch (IOException e) {
throw new HttpClientException("unexpected error on request url " + request.getUrl(), e);
}
HttpParser httpParser = parserFactory.getHttpParser(responseType);
try {
// 执行后置拦截
for (Interceptor interceptor : interceptors) {
Object result = interceptor
.beforeParse(method, request, bodyType, responseType, httpResponse, httpParser);
if (result != null) {
closeHttpResponse(httpResponse);
return result;
}
}
} catch (Exception e) {
closeHttpResponse(httpResponse);
if (e instanceof HttpClientException) {
throw (HttpClientException) e;
}
throw new HttpClientException("error executing post interceptor", e);
}
Object result;
try {
result = httpParser.parse(httpResponse, charset, responseEntityType);
} catch (Exception e) {
closeHttpResponse(httpResponse);
if (e instanceof HttpClientException) {
throw (HttpClientException) e;
}
throw new HttpClientException("error parsing http response", e);
}
if (httpParser instanceof StreamHttpParser) {
return result;
} else {
closeHttpResponse(httpResponse);
return result;
}
}
}
}