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.
top.jfunc.http.interfacing.HttpRequestFactory Maven / Gradle / Ivy
package top.jfunc.http.interfacing;
import top.jfunc.http.annotation.*;
import top.jfunc.http.base.*;
import top.jfunc.http.config.Config;
import top.jfunc.http.holderrequest.DefaultBodyRequest;
import top.jfunc.http.holderrequest.DefaultRequest;
import top.jfunc.http.holderrequest.DefaultFormBodyRequest;
import top.jfunc.http.holderrequest.DefaultUploadRequest;
import top.jfunc.http.request.HttpRequest;
import top.jfunc.common.utils.ArrayListMultiValueMap;
import top.jfunc.common.utils.ArrayUtil;
import top.jfunc.common.utils.MultiValueMap;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URL;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static top.jfunc.http.interfacing.TypeUtils.methodError;
import static top.jfunc.http.interfacing.TypeUtils.parameterError;
/**
* @author xiongshiyan at 2019/5/24 , contact me with email [email protected] or phone 15208384257
*/
class HttpRequestFactory implements RequestFactory {
private static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
/**
* 形如{id}这样的路径参数
*/
private static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");
private static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);
/**
* 传入的方法
*/
private java.lang.reflect.Method method;
/**
* 通过method计算获取请求方法
*/
private Method httpMethod;
/**
* 全局配置
*/
private Config config;
/**
* 参数对应的处理器
*/
private final AbstractParameterHandler>[] parameterHandlers;
/**
* 是否有Body
* @see POST
* @see GET
*/
private boolean hasBody = false;
/**
* 是否是文件上传
* @see Multipart
*/
private boolean multiPart = false;
/**
* 是否是FormUrlEncoded
* @see FormUrlEncoded
*/
private boolean formEncoded = false;
/**
* Content-Type
*/
private MediaType contentType;
/**
* @Headers
注解添加的header
*/
private MultiValueMap headers;
/**
* 请求的相对URL
*/
private String relativeUrl;
/**
* 路径参数名称集合
*/
private Set relativeUrlParamNames;
private boolean gotField;
private boolean gotPart;
private boolean gotBody;
private boolean gotPath;
private boolean gotQuery;
private boolean gotQueryMap;
private boolean gotUrl;
public HttpRequestFactory(java.lang.reflect.Method method, Config config) {
this.method = method;
this.config = config;
//处理方法上的注解
for (Annotation annotation : method.getAnnotations()) {
parseMethodAnnotation(annotation);
}
if (httpMethod == null) {
throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
if (!hasBody) {
if (multiPart) {
throw methodError(method,
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (formEncoded) {
throw methodError(method, "FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
}
//方法泛型参数类型
Type[] genericParameterTypes = method.getGenericParameterTypes();
//方法的实际类型
Class>[] parameterTypes = method.getParameterTypes();
//方法参数的注解
Annotation[][] parameterAnnotationsArray = method.getParameterAnnotations();
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new AbstractParameterHandler>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
if(HttpRequest.class.isAssignableFrom(parameterTypes[p])){
//HttpRequest已经有URL了
gotUrl = true;
parameterHandlers[p] = AbstractParameterHandler.DoNothing.INSTANCE;
}else {
parameterHandlers[p] = parseParameter(p, genericParameterTypes[p], parameterAnnotationsArray[p]);
}
}
if (relativeUrl == null && !gotUrl) {
throw methodError(method, "Missing either @%s URL or @Url parameter.", httpMethod);
}
if (!formEncoded && !multiPart && !hasBody && gotBody) {
throw methodError(method, "Non-body HTTP method cannot contain @Body.");
}
if (formEncoded && !gotField) {
throw methodError(method, "Form-encoded method must contain at least one @Field.");
}
if (multiPart && !gotPart) {
throw methodError(method, "Multipart method must contain at least one @Part.");
}
}
@Override
public Method getHttpMethod() {
return httpMethod;
}
@Override
public HttpRequest httpRequest(Object[] args){
int argumentCount = args.length;
if (argumentCount != parameterHandlers.length) {
throw new IllegalArgumentException("Argument count (" + argumentCount
+ ") doesn't match expected count (" + parameterHandlers.length + ")");
}
HttpRequest httpRequest = initHttpRequest();
//如果直接传递的是HttpRequest,就忽略其他的注解,因为他已经包含了请求所需的所有信息
if(ArrayUtil.isNotEmpty(args)){
if(args[0] instanceof HttpRequest){
httpRequest = (HttpRequest) (args[0]);
}
}
AbstractParameterHandler[] handlers = (AbstractParameterHandler[]) parameterHandlers;
for (int p = 0; p < argumentCount; p++) {
handlers[p].apply(httpRequest, args[p]);
}
validateRouteParams(httpRequest);
//处理方法上的Headers
if(null != this.headers && !this.headers.isEmpty()){
this.headers.forEachKeyValue(httpRequest::addHeader);
}
if(null != contentType){
httpRequest.addHeader(HttpHeaders.CONTENT_TYPE , contentType.toString());
}
return httpRequest;
}
/**
* 初始化HttpRequest
*/
private HttpRequest initHttpRequest() {
HttpRequest httpRequest;
if(hasBody){
httpRequest = DefaultBodyRequest.of(relativeUrl);
}else {
httpRequest = DefaultRequest.of(relativeUrl);
}
if(multiPart){
httpRequest = DefaultUploadRequest.of(relativeUrl);
}
if (formEncoded){
httpRequest = DefaultFormBodyRequest.of(relativeUrl);
}
return httpRequest;
}
private void validateRouteParams(HttpRequest httpRequest) {
Map routeParams = httpRequest.getRouteParams();
//校验路径参数是否一致
if(null != routeParams && !routeParams.isEmpty()){
int size = routeParams.size();
if(size != relativeUrlParamNames.size()){
throw new IllegalArgumentException("路径参数个数不匹配");
}
int x= 0;
for (String paramName : relativeUrlParamNames) {
for (Map.Entry entry : routeParams.entrySet()) {
if (entry.getKey().equals(paramName)) {
x ++;
}
}
}
if(x != size){
throw new IllegalArgumentException("路径参数名称对应不上");
}
}
}
/**
* 一个参数多个注解解析
*/
private AbstractParameterHandler> parseParameter(int pos, Type parameterType, Annotation[] annotations) {
AbstractParameterHandler> result = null;
if (annotations != null) {
for (Annotation annotation : annotations) {
AbstractParameterHandler> annotationAction =
parseParameterAnnotation(pos, parameterType, annotations, annotation);
if (annotationAction == null) {
continue;
}
if (result != null) {
throw parameterError(method, pos,
"Multiple HttpService annotations found, only one allowed.");
}
result = annotationAction;
}
}
if (result == null) {
throw parameterError(method, pos, "No HttpService annotation found.");
}
return result;
}
/**
* 一个参数上的注解解析
*/
private AbstractParameterHandler> parseParameterAnnotation(int p, Type type, Annotation[] annotations, Annotation annotation) {
if (annotation instanceof Url) {
validateResolvableType(p, type);
if (gotUrl) {
throw parameterError(method, p, "Multiple @Url method annotations found.");
}
if (gotPath) {
throw parameterError(method, p, "@Path parameters may not be used with @Url.");
}
if (gotQuery) {
throw parameterError(method, p, "A @Url parameter must not come after a @Query.");
}
if (gotQueryMap) {
throw parameterError(method, p, "A @Url parameter must not come after a @QueryMap.");
}
if (relativeUrl != null) {
throw parameterError(method, p, "@Url cannot be used with @%s URL", httpMethod);
}
gotUrl = true;
if (type == String.class
|| type == URL.class
|| type == URI.class) {
return new AbstractParameterHandler.Url();
} else {
throw parameterError(method, p,
"@Url must be okhttp3.HttpUrl, String, java.net.URI, or android.net.Uri type.");
}
} else if (annotation instanceof Path) {
validateResolvableType(p, type);
if (gotQuery) {
throw parameterError(method, p, "A @Path parameter must not come after a @Query.");
}
if (gotQueryMap) {
throw parameterError(method, p, "A @Path parameter must not come after a @QueryMap.");
}
if (gotUrl) {
throw parameterError(method, p, "@Path parameters may not be used with @Url.");
}
if (relativeUrl == null) {
throw parameterError(method, p, "@Path can only be used with relative url on @%s",
httpMethod);
}
gotPath = true;
Path path = (Path) annotation;
String name = path.value();
validatePathName(p, name);
return new AbstractParameterHandler.Route(name);
} else if (annotation instanceof PathMap) {
validateResolvableType(p, type);
Class> rawParameterType = TypeUtils.getRawType(type);
if (!Map.class.isAssignableFrom(rawParameterType)) {
throw parameterError(method, p, "@PathMap parameter type must be Map.");
}
Type mapType = TypeUtils.getSupertype(type, rawParameterType, Map.class);
if (!(mapType instanceof ParameterizedType)) {
throw parameterError(method, p,
"Map must include generic types (e.g., Map)");
}
ParameterizedType parameterizedType = (ParameterizedType) mapType;
Type keyType = TypeUtils.getParameterUpperBound(0, parameterizedType);
if (String.class != keyType) {
throw parameterError(method, p, "@PathMap keys must be of type String: " + keyType);
}
return new AbstractParameterHandler.RouteMap();
} else if (annotation instanceof Query) {
validateResolvableType(p, type);
Query query = (Query) annotation;
String name = query.value();
gotQuery = true;
return new AbstractParameterHandler.Query(name);
} else if (annotation instanceof QueryMap) {
validateResolvableType(p, type);
Class> rawParameterType = TypeUtils.getRawType(type);
gotQueryMap = true;
if (!Map.class.isAssignableFrom(rawParameterType)) {
throw parameterError(method, p, "@QueryMap parameter type must be Map.");
}
Type mapType = TypeUtils.getSupertype(type, rawParameterType, Map.class);
if (!(mapType instanceof ParameterizedType)) {
throw parameterError(method, p,
"Map must include generic types (e.g., Map)");
}
ParameterizedType parameterizedType = (ParameterizedType) mapType;
Type keyType = TypeUtils.getParameterUpperBound(0, parameterizedType);
if (String.class != keyType) {
throw parameterError(method, p, "@QueryMap keys must be of type String: " + keyType);
}
return new AbstractParameterHandler.QueryMap();
} else if (annotation instanceof Header) {
validateResolvableType(p, type);
Header header = (Header) annotation;
String name = header.value();
return new AbstractParameterHandler.Header(name);
} else if (annotation instanceof HeaderMap) {
validateResolvableType(p, type);
Class> rawParameterType = TypeUtils.getRawType(type);
if (!Map.class.isAssignableFrom(rawParameterType)) {
throw parameterError(method, p, "@HeaderMap parameter type must be Map.");
}
Type mapType = TypeUtils.getSupertype(type, rawParameterType, Map.class);
if (!(mapType instanceof ParameterizedType)) {
throw parameterError(method, p,
"Map must include generic types (e.g., Map)");
}
ParameterizedType parameterizedType = (ParameterizedType) mapType;
Type keyType = TypeUtils.getParameterUpperBound(0, parameterizedType);
if (String.class != keyType) {
throw parameterError(method, p, "@HeaderMap keys must be of type String: " + keyType);
}
return new AbstractParameterHandler.HeaderMap();
} else if (annotation instanceof Field) {
validateResolvableType(p, type);
if (!formEncoded) {
throw parameterError(method, p, "@Field parameters can only be used with form encoding.");
}
Field field = (Field) annotation;
String name = field.value();
gotField = true;
return new AbstractParameterHandler.Field(name);
} else if (annotation instanceof FieldMap) {
validateResolvableType(p, type);
if (!formEncoded) {
throw parameterError(method, p,
"@FieldMap parameters can only be used with form encoding.");
}
Class> rawParameterType = TypeUtils.getRawType(type);
if (!Map.class.isAssignableFrom(rawParameterType)) {
throw parameterError(method, p, "@FieldMap parameter type must be Map.");
}
Type mapType = TypeUtils.getSupertype(type, rawParameterType, Map.class);
if (!(mapType instanceof ParameterizedType)) {
throw parameterError(method, p,
"Map must include generic types (e.g., Map)");
}
ParameterizedType parameterizedType = (ParameterizedType) mapType;
Type keyType = TypeUtils.getParameterUpperBound(0, parameterizedType);
if (String.class != keyType) {
throw parameterError(method, p, "@FieldMap keys must be of type String: " + keyType);
}
gotField = true;
return new AbstractParameterHandler.FieldMap();
} else if (annotation instanceof Part) {
validateResolvableType(p, type);
if (!multiPart) {
throw parameterError(method, p,
"@Part parameters can only be used with multipart encoding.");
}
Part part = (Part) annotation;
gotPart = true;
return new AbstractParameterHandler.Part(part.value());
} else if (annotation instanceof Body) {
validateResolvableType(p, type);
if (formEncoded || multiPart) {
throw parameterError(method, p,
"@Body parameters cannot be used with form or multi-part encoding.");
}
if (gotBody) {
throw parameterError(method, p, "Multiple @Body method annotations found.");
}
gotBody = true;
return new AbstractParameterHandler.Body();
}
// Not a HttpService annotation.
return null;
}
private void validateResolvableType(int p, Type type) {
if (TypeUtils.hasUnresolvableType(type)) {
throw parameterError(method, p,
"Parameter type must not include a type variable or wildcard: %s", type);
}
}
private void validatePathName(int p, String name) {
if (!PARAM_NAME_REGEX.matcher(name).matches()) {
throw parameterError(method, p, "@Path parameter name must match %s. Found: %s",
PARAM_URL_REGEX.pattern(), name);
}
// Verify URL replacement name is actually present in the URL path.
if (!relativeUrlParamNames.contains(name)) {
throw parameterError(method, p, "URL \"%s\" does not contain \"{%s}\".", relativeUrl, name);
}
}
/**
* 解析方法上的注解
*/
private void parseMethodAnnotation(Annotation annotation) {
if (annotation instanceof DELETE) {
parseHttpMethodAndPath(Method.DELETE, ((DELETE) annotation).value());
} else if (annotation instanceof GET) {
parseHttpMethodAndPath(Method.GET, ((GET) annotation).value());
} else if (annotation instanceof HEAD) {
parseHttpMethodAndPath(Method.HEAD, ((HEAD) annotation).value());
} else if (annotation instanceof PATCH) {
parseHttpMethodAndPath(Method.PATCH, ((PATCH) annotation).value());
} else if (annotation instanceof POST) {
parseHttpMethodAndPath(Method.POST, ((POST) annotation).value());
} else if (annotation instanceof PUT) {
parseHttpMethodAndPath(Method.PUT, ((PUT) annotation).value());
} else if (annotation instanceof OPTIONS) {
parseHttpMethodAndPath(Method.OPTIONS, ((OPTIONS) annotation).value());
} else if (annotation instanceof HTTP) {
HTTP http = (HTTP) annotation;
parseHttpMethodAndPath(http.method(), http.path());
} else if (annotation instanceof Headers) {
String[] headersToParse = ((Headers) annotation).value();
if (headersToParse.length == 0) {
throw methodError(method, "@Headers annotation is empty.");
}
headers = parseHeaders(headersToParse);
}else if (annotation instanceof Multipart) {
if (formEncoded) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
multiPart = true;
} else if (annotation instanceof FormUrlEncoded) {
if (multiPart) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
formEncoded = true;
}
}
/**
* 解析方法上的Headers注解,用于静态header 形如 @Headers("k1:v1" , "k2:v2")
*/
private MultiValueMap parseHeaders(String[] headers) {
if(ArrayUtil.isEmpty(headers)){
return null;
}
MultiValueMap h = new ArrayListMultiValueMap<>(headers.length);
for (String header : headers) {
int colon = header.indexOf(':');
if (colon == -1 || colon == 0 || colon == header.length() - 1) {
throw methodError(method,
"@Headers value must be in the form \"Name: Value\". Found: \"%s\"", header);
}
String headerName = header.substring(0, colon);
String headerValue = header.substring(colon + 1).trim();
if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(headerName)) {
try {
contentType = MediaType.parse(headerValue);
} catch (Exception e) {
throw methodError(method, e, "Malformed content type: %s", headerValue);
}
} else {
h.add(headerName, headerValue);
}
}
return h;
}
/**
* 1.请求方法注解上的URL,可以包括query参数,但是不能在query参数中使用路径参数,即 ?k={v},可以使用@Query注解传递
* 2.路径上的path参数保存到集合中,供后面配合@Path注解使用
* @param httpMethod 请求方法
* @param value 请求方法注解中的URL
*/
private void parseHttpMethodAndPath(Method httpMethod, String value) {
if (this.httpMethod != null) {
throw methodError(method, "Only one HTTP method is allowed. Found: %s and %s.",
this.httpMethod, httpMethod);
}
this.httpMethod = httpMethod;
this.hasBody = config.getMethodContentStrategy().supportContent(httpMethod);
if (value.isEmpty()) {
return;
}
// Get the relative URL path and existing query string, if present.
int question = value.indexOf('?');
if (question != -1 && question < value.length() - 1) {
// Ensure the query string does not have any named parameters.
String queryParams = value.substring(question + 1);
Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
if (queryParamMatcher.find()) {
throw methodError(method, "URL query string \"%s\" must not have replace block. "
+ "For dynamic query parameters use @Query.", queryParams);
}
}
this.relativeUrl = value;
this.relativeUrlParamNames = parsePathParameters(value);
}
/**
* Gets the set of unique path parameters used in the given URI. If a parameter is used twice
* in the URI, it will only show up once in the set.
*/
static Set parsePathParameters(String path) {
Matcher m = PARAM_URL_REGEX.matcher(path);
Set patterns = new LinkedHashSet<>();
while (m.find()) {
patterns.add(m.group(1));
}
return patterns;
}
}