package com.github.bingoohuang.springrestclient.utils; import com.github.bingoohuang.springrestclient.annotations.SuccInResponseJSONProperty; import com.github.bingoohuang.springrestclient.exception.RestException; import com.github.bingoohuang.springrestclient.provider.BaseUrlProvider; import com.github.bingoohuang.springrestclient.provider.BasicAuthProvider; import com.github.bingoohuang.springrestclient.provider.SignProvider; import com.github.bingoohuang.springrestclient.xml.Xmls; import com.github.bingoohuang.utils.codec.Json; import com.google.common.base.Strings; import com.google.common.collect.Maps; import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; import com.mashape.unirest.http.exceptions.UnirestException; import com.mashape.unirest.request.BaseRequest; import com.mashape.unirest.request.HttpRequest; import com.mashape.unirest.request.HttpRequestWithBody; import com.mashape.unirest.request.ValueUtils; import com.mashape.unirest.request.body.MultipartBody; import lombok.val; import org.springframework.context.ApplicationContext; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.InputStream; import java.util.Collection; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class RestReq { final SuccInResponseJSONProperty succInResponseJSONProperty; final Map<String, Object> fixedRequestParams; final Map<Integer, Class<? extends Throwable>> sendStatusExceptionMappings; final Class<?> apiClass; final BaseUrlProvider baseUrlProvider; final String prefix; final Map<String, Object> routeParams; final Map<String, Object> requestParams; final Map<String, Object> cookies; final RestLog restLog; final SignProvider signProvider; final ApplicationContext appContext; final RequestParamsHelper requestParamsHelper; final String firstConsume; final BasicAuthProvider basicAuthProvider; RestReq( BasicAuthProvider basicAuthProvider, String firstConsume, SuccInResponseJSONProperty succInResponseJSONProperty, Map<String, Object> fixedRequestParams, Map<Integer, Class<? extends Throwable>> sendStatusExceptionMappings, Class<?> apiClass, BaseUrlProvider baseUrlProvider, String prefix, Map<String, Object> routeParams, Map<String, Object> requestParams, Map<String, Object> cookies, boolean async, SignProvider signProvider, ApplicationContext appContext) { this.basicAuthProvider = basicAuthProvider; this.firstConsume = firstConsume; this.succInResponseJSONProperty = succInResponseJSONProperty; this.fixedRequestParams = fixedRequestParams; this.sendStatusExceptionMappings = sendStatusExceptionMappings; this.apiClass = apiClass; this.baseUrlProvider = baseUrlProvider; this.prefix = prefix; this.routeParams = routeParams; this.requestParams = requestParams; this.cookies = cookies; this.restLog = new RestLog(apiClass, async); this.signProvider = signProvider; this.appContext = appContext; this.requestParamsHelper = new RequestParamsHelper( fixedRequestParams, requestParams, appContext); } static ThreadLocal<HttpResponse<?>> lastResponseTL; static { lastResponseTL = new ThreadLocal<HttpResponse<?>>(); } public static HttpResponse<?> lastResponse() { return lastResponseTL.get(); } public String get() throws Throwable { String url = createUrl(); HttpRequest get = Unirest.get(url); setRouteParamsAndCookie(get); get.queryString(requestParamsHelper.mergeRequestParamsForGet()); return request(null, get); } public InputStream getBinary() throws Throwable { String url = createUrl(); HttpRequest get = Unirest.get(url); setRouteParamsAndCookie(get); get.queryString(requestParamsHelper.mergeRequestParamsForGet()); return requestBinary(null, get); } public Future<HttpResponse<String>> getAsync() throws Throwable { String url = createUrl(); HttpRequest get = Unirest.get(url); setRouteParamsAndCookie(get); get.queryString(requestParamsHelper.mergeRequestParamsForGet()); return requestAsync(null, get); } public Future<HttpResponse<InputStream>> getAsyncBinary() throws Throwable { String url = createUrl(); HttpRequest get = Unirest.get(url); setRouteParamsAndCookie(get); get.queryString(requestParamsHelper.mergeRequestParamsForGet()); return requestAsyncBinary(null, get); } public String post() throws Throwable { String url = createUrl(); HttpRequestWithBody post = Unirest.post(url); setRouteParamsAndCookie(post); post.queryString(requestParamsHelper.createQueryParamsForPost()); val requestParams = requestParamsHelper.mergeRequestParamsWithoutQueryParams(); BaseRequest fields = fields(post, requestParams); return request(requestParams, fields); } public InputStream postBinary() throws Throwable { String url = createUrl(); HttpRequestWithBody post = Unirest.post(url); setRouteParamsAndCookie(post); post.queryString(requestParamsHelper.createQueryParamsForPost()); val requestParams = requestParamsHelper.mergeRequestParamsWithoutQueryParams(); BaseRequest fields = fields(post, requestParams); return requestBinary(requestParams, fields); } private BaseRequest fields( HttpRequestWithBody post, Map<String, Object> requestParams) { MultipartBody field = null; for (Map.Entry<String, Object> entry : requestParams.entrySet()) { Object value = entry.getValue(); boolean isFileCollection = false; if (value instanceof Collection) { isFileCollection = true; for (Object o : (Collection) value) { if (o instanceof File || o instanceof MultipartFile) field = fieldFileOrElse(post, field, entry, o); else { isFileCollection = false; break; } } } if (!isFileCollection) { field = fieldFileOrElse(post, field, entry, value); } } return field == null ? post : field; } private MultipartBody fieldFileOrElse(HttpRequestWithBody post, MultipartBody field, Map.Entry<String, Object> entry, Object value) { if (value instanceof File) { if (field != null) field.field(entry.getKey(), (File) value); else field = post.field(entry.getKey(), (File) value); } else if (value instanceof MultipartFile) { if (field != null) field.field(entry.getKey(), (MultipartFile) value); else field = post.field(entry.getKey(), (MultipartFile) value); } else { if (field != null) field.field(entry.getKey(), value); else field = post.field(entry.getKey(), value); } return field; } public Future<HttpResponse<String>> postAsync() throws Throwable { String url = createUrl(); val post = Unirest.post(url); setRouteParamsAndCookie(post); post.queryString(requestParamsHelper.createQueryParamsForPost()); val requestParams = requestParamsHelper.mergeRequestParamsWithoutQueryParams(); BaseRequest fields = fields(post, requestParams); return requestAsync(requestParams, fields); } public Future<HttpResponse<InputStream>> postAsyncBinary() throws Throwable { String url = createUrl(); val post = Unirest.post(url); setRouteParamsAndCookie(post); post.queryString(requestParamsHelper.createQueryParamsForPost()); val requestParams = requestParamsHelper.mergeRequestParamsWithoutQueryParams(); BaseRequest fields = fields(post, requestParams); return requestAsyncBinary(requestParams, fields); } private void setRouteParamsAndCookie(HttpRequest httpRequest) { Blackcats.prepareRPC(httpRequest); for (Map.Entry<String, Object> entry : routeParams.entrySet()) { String value = String.valueOf(entry.getValue()); httpRequest.routeParam(entry.getKey(), value); } val cookieStr = new StringBuilder(); for (Map.Entry<String, Object> entry : cookies.entrySet()) { String value = String.valueOf(entry.getValue()); cookieStr.append(' ').append(entry.getKey()).append("=").append(value).append(";"); } if (cookieStr.length() > 0) httpRequest.header("Cookie", cookieStr.toString()); if (basicAuthProvider != null) { httpRequest.basicAuth(basicAuthProvider.username(), basicAuthProvider.password()); } } public String postBody(Object bean) throws Throwable { val post = createPost(); val body = createBody(post, bean); post.body(body); val requestParams = createJsonBody(body); return request(requestParams, post); } public InputStream postBodyBinary(Object bean) throws Throwable { val post = createPost(); val body = createBody(post, bean); post.body(body); val requestParams = createJsonBody(body); return requestBinary(requestParams, post); } public Future<HttpResponse<String>> postBodyAsync(Object bean) throws Throwable { val post = createPost(); val body = createBody(post, bean); post.body(body); Map<String, Object> requestParams = createJsonBody(body); return requestAsync(requestParams, post); } public Future<HttpResponse<InputStream>> postBodyAsyncBinary(Object bean) throws Throwable { val post = createPost(); val body = createBody(post, bean); post.body(body); val requestParams = createJsonBody(body); return requestAsyncBinary(requestParams, post); } private Map<String, Object> createJsonBody(String body) { Map<String, Object> requestParams = Maps.newHashMap(); requestParams.put("_json", body); return requestParams; } private HttpRequestWithBody createPost() { String url = createUrl(); val post = Unirest.post(url); setRouteParamsAndCookie(post); post.queryString(requestParamsHelper.mergeRequestParamsForGet()); return post; } private String createBody(HttpRequestWithBody post, Object bean) { if (firstConsume != null && firstConsume.indexOf("/xml") >= 0) { post.header("Content-Type", "application/xml;charset=UTF-8"); return Xmls.marshal(bean); } else { post.header("Content-Type", "application/json;charset=UTF-8"); return ValueUtils.processValue(bean); } } private String request(Map<String, Object> reqParams, BaseRequest httpReq) throws Throwable { boolean loggedResponse = false; try { restLog.logAndSign(signProvider, reqParams, httpReq.getHttpRequest()); lastResponseTL.remove(); val response = httpReq.asString(); restLog.log(response); loggedResponse = true; lastResponseTL.set(response); if (isSuccessful(response)) return RestClientUtils.nullOrBody(response); throw processStatusExceptionMappings(response); } catch (UnirestException e) { if (!loggedResponse) restLog.log(e); throw new RuntimeException(e); } catch (Throwable e) { if (!loggedResponse) restLog.log(e); throw e; } } private InputStream requestBinary(Map<String, Object> reqParams, BaseRequest httpReq) throws Throwable { boolean loggedResponse = false; try { restLog.logAndSign(signProvider, reqParams, httpReq.getHttpRequest()); lastResponseTL.remove(); val response = httpReq.asBinary(); restLog.log(response); loggedResponse = true; lastResponseTL.set(response); if (isSuccessful(response)) return RestClientUtils.nullOrBody(response); throw processStatusExceptionMappings(response); } catch (UnirestException e) { if (!loggedResponse) restLog.log(e); throw new RuntimeException(e); } catch (Throwable e) { if (!loggedResponse) restLog.log(e); throw e; } } private Future<HttpResponse<String>> requestAsync( Map<String, Object> reqParams, BaseRequest httpReq) throws Throwable { restLog.logAndSign(signProvider, reqParams, httpReq.getHttpRequest()); lastResponseTL.remove(); // clear response threadlocal before execution val callback = new UniRestCallback<String>(apiClass, restLog); val future = httpReq.asStringAsync(callback); return new Future<HttpResponse<String>>() { @Override public boolean cancel(boolean mayInterruptIfRunning) { return future.cancel(mayInterruptIfRunning); } @Override public boolean isCancelled() { return callback.isCancelled(); } @Override public boolean isDone() { return callback.isDone(); } @Override public HttpResponse<String> get() throws InterruptedException, ExecutionException { return callback.get(); } @Override public HttpResponse<String> get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return callback.get(unit.toMillis(timeout)); } }; } private Future<HttpResponse<InputStream>> requestAsyncBinary( Map<String, Object> requestParams, BaseRequest httpRequest) throws Throwable { restLog.logAndSign(signProvider, requestParams, httpRequest.getHttpRequest()); lastResponseTL.remove(); // clear response threadlocal before execution val callback = new UniRestCallback<InputStream>(apiClass, restLog); val future = httpRequest.asBinaryAsync(callback); return new Future<HttpResponse<InputStream>>() { @Override public boolean cancel(boolean mayInterruptIfRunning) { return future.cancel(mayInterruptIfRunning); } @Override public boolean isCancelled() { return callback.isCancelled(); } @Override public boolean isDone() { return callback.isDone(); } @Override public HttpResponse<InputStream> get() throws InterruptedException, ExecutionException { return callback.get(); } @Override public HttpResponse<InputStream> get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return callback.get(unit.toMillis(timeout)); } }; } public Throwable processStatusExceptionMappings(HttpResponse<?> response) throws Throwable { Class<? extends Throwable> exceptionClass; exceptionClass = sendStatusExceptionMappings.get(response.getStatus()); String msg = response.header("error-msg"); if (Strings.isNullOrEmpty(msg)) { Object body = response.getBody(); msg = body instanceof InputStream ? "" : ("" + body); } if (exceptionClass == null) throw new RestException(response.getStatus(), msg); throw Obj.createObject(exceptionClass, msg); } private String createUrl() { String baseUrl = baseUrlProvider.getBaseUrl(apiClass); if (Strings.isNullOrEmpty(baseUrl)) { throw new RuntimeException( "base url cannot be null generated by provider " + baseUrlProvider.getClass()); } return baseUrl + prefix; } public boolean isSuccessful(HttpResponse<?> response) { int status = response.getStatus(); boolean isHttpSucc = status >= 200 && status < 300; if (!isHttpSucc) return false; if (succInResponseJSONProperty == null) return true; if (!RestClientUtils.isResponseJsonContentType(response)) return true; Object body = response.getBody(); if (body instanceof InputStream) return true; Map<String, Object> map = Json.unJson("" + body); String key = succInResponseJSONProperty.key(); Object realValue = map.get(key); String expectedValue = succInResponseJSONProperty.value(); return expectedValue.equals("" + realValue); } }