<|start_filename|>src/test/java/com/github/dadiyang/httpinvoker/serializer/JsonSerializerDeciderTest.java<|end_filename|>
package com.github.dadiyang.httpinvoker.serializer;

import org.junit.Test;

import static org.junit.Assert.*;

public class JsonSerializerDeciderTest {
    @Test
    public void getJsonSerializer() {
        JsonSerializer jsonSerializer = JsonSerializerDecider.getJsonSerializer();
        assertTrue("默认使用FastJson的实现", jsonSerializer instanceof FastJsonJsonSerializer);

        JsonSerializerDecider.registerJsonSerializer("Gson", GsonJsonSerializer.getInstance());
        JsonSerializerDecider.setJsonInstanceKey("Gson");
        JsonSerializer gson = JsonSerializerDecider.getJsonSerializer();
        assertTrue("指定使用Gson实现，则必须返回gson实现", gson instanceof GsonJsonSerializer);
    }
}

<|start_filename|>src/main/java/com/github/dadiyang/httpinvoker/requestor/HttpRequest.java<|end_filename|>
package com.github.dadiyang.httpinvoker.requestor;

import java.util.HashMap;
import java.util.Map;

/**
 * @author huangxuyang
 * date 2018/12/7
 */
public class HttpRequest {
    private String method = "GET";
    private int timeout = 5000;
    private String url;
    private Map<String, String> headers;
    private Map<String, String> cookies;
    private Map<String, Object> data;
    private Object body;
    private String fileFormKey;

    public HttpRequest(String url) {
        this.url = url;
    }

    public HttpRequest(String url, String method, int timeout) {
        this.method = method;
        this.timeout = timeout;
        this.url = url;
    }

    public HttpRequest(int timeout, String method) {
        this.method = method;
        this.timeout = timeout;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public Map<String, String> getHeaders() {
        return headers;
    }

    public void setHeaders(Map<String, String> headers) {
        this.headers = new HashMap<String, String>(headers);
    }

    public void addHeader(String key, String value) {
        if (headers == null) {
            headers = new HashMap<String, String>(8);
        }
        headers.put(key, value);
    }

    public void addCookie(String key, String value) {
        if (cookies == null) {
            cookies = new HashMap<String, String>(8);
        }
        cookies.put(key, value);
    }

    public Map<String, String> getCookies() {
        return cookies;
    }

    public void setCookies(Map<String, String> cookies) {
        this.cookies = new HashMap<String, String>(cookies);
    }

    public Map<String, Object> getData() {
        return data;
    }

    public void setData(Map<String, Object> data) {
        this.data = data;
    }

    public void addParam(String key, String value) {
        if (data == null) {
            data = new HashMap<String, Object>(8);
        }
        data.put(key, value);
    }

    public Object getBody() {
        return body;
    }

    public void setBody(Object body) {
        this.body = body;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getFileFormKey() {
        return fileFormKey;
    }

    public void setFileFormKey(String fileFormKey) {
        this.fileFormKey = fileFormKey;
    }

}


<|start_filename|>src/main/java/com/github/dadiyang/httpinvoker/util/ParamUtils.java<|end_filename|>
package com.github.dadiyang.httpinvoker.util;

import com.github.dadiyang.httpinvoker.requestor.HttpRequest;
import com.github.dadiyang.httpinvoker.requestor.MultiPart;
import com.github.dadiyang.httpinvoker.serializer.JsonSerializerDecider;

import java.io.*;
import java.lang.reflect.Array;
import java.net.URLEncoder;
import java.util.*;

/**
 * utils for handling param
 *
 * @author dadiyang
 * @since 1.1.2
 */
public class ParamUtils {
    private static final char UPPER_A = 'A';
    private static final char UPPER_Z = 'Z';
    private static final char LOWER_A = 'a';
    private static final char LOWER_Z = 'z';
    private static final String FILE_NAME = "fileName";
    private static final String DEFAULT_UPLOAD_FORM_KEY = "media";
    private static final String FORM_KEY = "formKey";
    private static final List<Class<?>> BASIC_TYPE = Arrays.asList(Byte.class, Short.class,
            Integer.class, Long.class, Float.class, Double.class, Character.class,
            Boolean.class, String.class, Void.class, Date.class);
    /**
     * for JDK6/7 compatibility
     */
    private static final List<String> BASIC_TYPE_NAME = Arrays.asList("java.time.LocalDate", "java.time.LocalDateTime");

    private ParamUtils() {
        throw new UnsupportedOperationException("utils should not be initialized!");
    }

    /**
     * check if the clz is primary type, primary type's wrapper, String or Void
     *
     * @param clz the type
     * @return check if the clz is basic type
     */
    public static boolean isBasicType(Class<?> clz) {
        if (clz == null) {
            return false;
        }
        // for JDK6/7 compatibility
        if (BASIC_TYPE_NAME.contains(clz.getName())) {
            return true;
        }
        return clz.isPrimitive() || BASIC_TYPE.contains(clz);
    }

    /**
     * check if the arg is a collection
     *
     * @param arg object to be checked
     * @return if the arg is a array/collection
     */
    public static boolean isCollection(Object arg) {
        if (arg == null) {
            return false;
        }
        return arg.getClass().isArray()
                || arg instanceof Collection;
    }

    /**
     * convert an object to Map&lt;String, String&gt;
     *
     * @param value  object to be converted
     * @param prefix key's prefix
     * @return Map&lt;String, String&gt; represent the value
     */
    public static Map<String, String> toMapStringString(Object value, String prefix) {
        if (value == null) {
            return Collections.emptyMap();
        }
        if (isBasicType(value.getClass())) {
            return Collections.singletonMap(prefix, String.valueOf(value));
        }
        Map<String, String> map = new HashMap<String, String>();
        if (value.getClass().isArray()) {
            for (int i = 0; i < Array.getLength(value); i++) {
                String key = prefix + "[" + i++ + "]";
                Object item = Array.get(value, 0);
                if (isBasicType(item.getClass())) {
                    map.put(prefix + "[" + i + "]", String.valueOf(item));
                } else {
                    map.putAll(toMapStringString(item, key));
                }

            }
        } else if (value instanceof Collection) {
            Collection collection = (Collection) value;
            Iterator it = collection.iterator();
            int i = 0;
            while (it.hasNext()) {
                String key = prefix + "[" + i++ + "]";
                Object item = it.next();
                if (isBasicType(item.getClass())) {
                    map.put(prefix + "[" + i++ + "]", String.valueOf(item));
                } else {
                    map.putAll(toMapStringString(item, key));
                }
            }
        } else {
            Map<String, Object> obj = JsonSerializerDecider.getJsonSerializer().toMap(JsonSerializerDecider.getJsonSerializer().serialize(value));
            for (Map.Entry<String, Object> entry : obj.entrySet()) {
                String key;
                if (prefix == null || prefix.isEmpty()) {
                    key = entry.getKey();
                } else {
                    key = prefix + "[" + entry.getKey() + "]";
                }
                if (isBasicType(value.getClass())) {
                    map.put(key, String.valueOf(value));
                } else {
                    map.putAll(toMapStringString(entry.getValue(), key));
                }
            }
        }
        return map;
    }

    /**
     * convert param object to query string
     * <p>
     * collection fields will be convert to a form of duplicated key such as id=1&amp;id=2&amp;id=3
     *
     * @param arg the param args
     * @return query string
     */
    public static String toQueryString(Object arg) {
        if (arg == null) {
            return "";
        }
        StringBuilder qs = new StringBuilder("?");
        Map<String, Object> obj = JsonSerializerDecider.getJsonSerializer().toMap(JsonSerializerDecider.getJsonSerializer().serialize(arg));
        for (Map.Entry<String, Object> entry : obj.entrySet()) {
            if (isCollection(entry.getValue())) {
                qs.append(collectionToQueryString(obj, entry));
            } else {
                String value = entry.getValue() == null ? "" : entry.getValue().toString();
                try {
                    value = URLEncoder.encode(value, "UTF-8");
                } catch (UnsupportedEncodingException ignored) {
                }
                qs.append(entry.getKey()).append("=").append(value).append("&");
            }
        }
        return qs.substring(0, qs.length() - 1);
    }

    private static String collectionToQueryString(Map<String, Object> obj, Map.Entry<String, Object> entry) {
        List<Object> arr = JsonSerializerDecider.getJsonSerializer().parseArray(ObjectUtils.toString(obj.get(entry.getKey())));
        StringBuilder valBuilder = new StringBuilder();
        for (Object item : arr) {
            valBuilder.append(entry.getKey()).append("=").append(item).append("&");
        }
        return valBuilder.toString();
    }

    public static char changeCase(char c) {
        if (c >= UPPER_A && c <= UPPER_Z) {
            return c += 32;
        } else if (c >= LOWER_A && c <= LOWER_Z) {
            return c -= 32;
        } else {
            return c;
        }
    }

    public static String changeInitialCase(String c) {
        if (c == null || c.isEmpty()) {
            return c;
        }
        return changeCase(c.charAt(0)) + c.substring(1);
    }

    public static MultiPart convertInputStreamAndFile(HttpRequest request) throws IOException {
        Map<String, Object> paramMap = request.getData();
        String formKey = DEFAULT_UPLOAD_FORM_KEY;
        if (request.getFileFormKey() != null
                && !request.getFileFormKey().isEmpty()) {
            formKey = request.getFileFormKey();
        } else if (paramMap != null && paramMap.containsKey(FORM_KEY)) {
            formKey = paramMap.get(FORM_KEY).toString();
        }
        List<MultiPart.Part> parts = new ArrayList<MultiPart.Part>();
        String fileName = FILE_NAME;
        if (paramMap != null) {
            for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                if (entry.getKey() != null && entry.getValue() != null) {
                    if (ObjectUtils.equals(entry.getKey(), FILE_NAME)) {
                        fileName = String.valueOf(entry.getValue());
                    }
                    parts.add(new MultiPart.Part(entry.getKey(), String.valueOf(entry.getValue())));
                }
            }
        }
        InputStream in;
        if (File.class.isAssignableFrom(request.getBody().getClass())) {
            File file = (File) request.getBody();
            in = new FileInputStream(file);
            fileName = file.getName();
        } else {
            in = (InputStream) request.getBody();
        }
        parts.add(new MultiPart.Part(formKey, fileName, in));
        return new MultiPart(parts);
    }

    public static boolean isUploadRequest(Object bodyParam) {
        return bodyParam != null && (bodyParam instanceof MultiPart
                || InputStream.class.isAssignableFrom(bodyParam.getClass())
                || File.class.isAssignableFrom(bodyParam.getClass()));
    }
}


<|start_filename|>src/main/java/com/github/dadiyang/httpinvoker/requestor/HttpClientRequestor.java<|end_filename|>
package com.github.dadiyang.httpinvoker.requestor;

import com.github.dadiyang.httpinvoker.serializer.JsonSerializerDecider;
import com.github.dadiyang.httpinvoker.util.ObjectUtils;
import com.github.dadiyang.httpinvoker.util.ParamUtils;
import com.github.dadiyang.httpinvoker.util.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpMessage;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.github.dadiyang.httpinvoker.enumeration.ReqMethod.*;
import static com.github.dadiyang.httpinvoker.util.ParamUtils.*;

/**
 * an http requestor base on HttpClient
 *
 * @author huangxuyang
 * @since 2019-06-13
 */
public class HttpClientRequestor implements Requestor {
    private static final String FORM_URLENCODED = "application/x-www-form-urlencoded";
    private static final String APPLICATION_JSON = "application/json";
    private static final String CONTENT_TYPE = "Content-Type";
    private CloseableHttpClient httpClient;

    /**
     * 使用默认的 httpClient 实现和配置
     */
    public HttpClientRequestor() {
        httpClient = createHttpClient();
    }

    /**
     * 自定义配置 httpClient
     */
    public HttpClientRequestor(CloseableHttpClient httpClient) {
        this.httpClient = httpClient;
    }

    private CloseableHttpClient createHttpClient() {
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
        poolingHttpClientConnectionManager.setMaxTotal(32);
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(16);
        return HttpClients.custom()
                .setConnectionManager(poolingHttpClientConnectionManager)
                .build();
    }

    @Override
    public HttpResponse sendRequest(HttpRequest request) throws IOException {
        String method = StringUtils.upperCase(request.getMethod());
        if (ObjectUtils.equals(method, GET)) {
            return sendGet(request);
        } else if (ObjectUtils.equals(method, POST)) {
            return sendPost(request);
        } else if (ObjectUtils.equals(method, PUT)) {
            return sendPut(request);
        } else if (ObjectUtils.equals(method, DELETE)) {
            return sendDelete(request);
        } else if (ObjectUtils.equals(method, PATCH)) {
            return sendPatch(request);
        } else if (ObjectUtils.equals(method, HEAD)) {
            return sendHead(request);
        } else if (ObjectUtils.equals(method, OPTIONS)) {
            return sendOptions(request);
        } else if (ObjectUtils.equals(method, TRACE)) {
            return sendTrace(request);
        } else {
            throw new IllegalArgumentException("Unsupported http method: " + method);
        }
    }

    private HttpResponse sendTrace(HttpRequest request) throws IOException {
        String fullUrl = request.getUrl() + toQueryString(request.getData());
        HttpTrace httpTrace = new HttpTrace(fullUrl);
        return sendRequest(request, httpTrace);
    }

    private HttpResponse sendOptions(HttpRequest request) throws IOException {
        String fullUrl = request.getUrl() + toQueryString(request.getData());
        HttpOptions httpOptions = new HttpOptions(fullUrl);
        return sendRequest(request, httpOptions);
    }

    private HttpResponse sendHead(HttpRequest request) throws IOException {
        String fullUrl = request.getUrl() + toQueryString(request.getData());
        HttpHead httpHead = new HttpHead(fullUrl);
        return sendRequest(request, httpHead);
    }

    private HttpResponse sendPatch(HttpRequest request) throws IOException {
        HttpEntity entity = createHttpEntity(request);
        HttpPatch httpPatch = new HttpPatch(request.getUrl());
        httpPatch.setEntity(entity);
        return sendRequest(request, httpPatch);
    }

    private HttpResponse sendDelete(HttpRequest request) throws IOException {
        String fullUrl = request.getUrl() + toQueryString(request.getData());
        HttpDelete httpDelete = new HttpDelete(fullUrl);
        return sendRequest(request, httpDelete);
    }

    private HttpResponse sendPut(HttpRequest request) throws IOException {
        HttpEntity entity = createHttpEntity(request);
        HttpPut httpPut = new HttpPut(request.getUrl());
        httpPut.setEntity(entity);
        return sendRequest(request, httpPut);
    }

    private HttpResponse sendPost(HttpRequest request) throws IOException {
        // handle MultiPart
        if (isUploadRequest(request.getBody())) {
            MultiPart multiPart;
            if (!(request.getBody() instanceof MultiPart)) {
                multiPart = ParamUtils.convertInputStreamAndFile(request);
            } else {
                multiPart = (MultiPart) request.getBody();
            }
            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            builder.setLaxMode();
            for (MultiPart.Part part : multiPart.getParts()) {
                if (part.getInputStream() != null) {
                    builder.addBinaryBody(part.getKey(), part.getInputStream(), ContentType.DEFAULT_BINARY, part.getValue());
                } else {
                    ContentType contentType = ContentType.create("text/plain", "UTF-8");
                    builder.addTextBody(part.getKey(), part.getValue(), contentType);
                }
            }
            HttpEntity entity = builder.build();
            HttpPost httpPost = new HttpPost(request.getUrl());
            httpPost.setEntity(entity);
            return sendMultiPartRequest(request, httpPost);
        }
        HttpEntity entity = createHttpEntity(request);
        HttpPost httpPost = new HttpPost(request.getUrl());
        httpPost.setEntity(entity);
        return sendRequest(request, httpPost);
    }

    private HttpEntity createHttpEntity(HttpRequest request) throws IOException {
        HttpEntity entity;
        // handle x-www-form-urlencoded
        if (request.getHeaders() != null
                && ObjectUtils.equals(FORM_URLENCODED, request.getHeaders().get(CONTENT_TYPE))) {
            List<BasicNameValuePair> parameters = new ArrayList<BasicNameValuePair>();
            Map<String, String> map = toMapStringString(request.getData(), "");
            for (Map.Entry<String, String> entry : map.entrySet()) {
                parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            entity = new UrlEncodedFormEntity(parameters, "UTF-8");
        } else {
            if (request.getBody() != null) {
                entity = new ByteArrayEntity(JsonSerializerDecider.getJsonSerializer().serialize(request.getBody()).getBytes(Charset.forName("UTF-8")),
                        ContentType.create(APPLICATION_JSON, "UTF-8"));
            } else if (request.getData() != null) {
                entity = new ByteArrayEntity(JsonSerializerDecider.getJsonSerializer().serialize(request.getData()).getBytes(Charset.forName("UTF-8")),
                        ContentType.create(APPLICATION_JSON, "UTF-8"));
            } else {
                BasicHttpEntity basicHttpEntity = new BasicHttpEntity();
                basicHttpEntity.setContentLength(0);
                entity = basicHttpEntity;
            }
        }
        return entity;
    }

    private HttpResponse sendGet(HttpRequest request) throws IOException {
        String fullUrl = request.getUrl() + toQueryString(request.getData());
        HttpGet httpGet = new HttpGet(fullUrl);
        return sendRequest(request, httpGet);
    }

    private HttpResponse sendRequest(HttpRequest request, HttpRequestBase httpRequestBase) throws IOException {
        prepare(request, httpRequestBase);
        CloseableHttpResponse response = httpClient.execute(httpRequestBase);
        if (response.getEntity() != null) {
            response.setEntity(new BufferedHttpEntity(response.getEntity()));
            EntityUtils.consume(response.getEntity());
        }
        return new HttpClientResponse(response);
    }

    private HttpResponse sendMultiPartRequest(HttpRequest request, HttpRequestBase httpRequestBase) throws IOException {
        prepare(request, httpRequestBase);
        CloseableHttpClient httpClient = null;
        try {
            httpClient = createHttpClient();
            CloseableHttpResponse response = httpClient.execute(httpRequestBase);
            response.setEntity(new BufferedHttpEntity(response.getEntity()));
            EntityUtils.consume(response.getEntity());
            return new HttpClientResponse(response);
        } finally {
            if (httpClient != null) {
                httpClient.close();
            }
        }
    }

    private void prepare(HttpRequest request, HttpRequestBase httpRequestBase) {
        addHeaders(request, httpRequestBase);
        addCookies(request, httpRequestBase);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(request.getTimeout())
                .build();
        httpRequestBase.setConfig(requestConfig);
    }

    private void addCookies(HttpRequest request, HttpMessage msg) {
        Map<String, String> cookies = request.getCookies();
        if (cookies == null || cookies.isEmpty()) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : cookies.entrySet()) {
            sb.append(entry.getKey()).append("=").append(entry.getValue()).append(";");
        }
        msg.addHeader("Cookie", sb.substring(0, sb.length()));
    }

    private void addHeaders(HttpRequest request, HttpMessage msg) {
        Map<String, String> headers = request.getHeaders();
        if (headers != null && !headers.isEmpty()) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                msg.addHeader(entry.getKey(), entry.getValue());
            }
        }
    }

    public CloseableHttpClient getHttpClient() {
        return httpClient;
    }

    public void setHttpClient(CloseableHttpClient httpClient) {
        this.httpClient = httpClient;
    }
}


<|start_filename|>src/main/java/com/github/dadiyang/httpinvoker/serializer/FastJsonJsonSerializer.java<|end_filename|>
package com.github.dadiyang.httpinvoker.serializer;

import com.alibaba.fastjson.JSON;

import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

/**
 * 基于 FastJson 的序列化器，仅在类路径中有 fastjson 且没有其他 Json 序列化器时使用
 * <p>
 * 默认首选的序列化器
 *
 * @author dadiyang
 * @since 2019/3/1
 */
public class FastJsonJsonSerializer implements JsonSerializer {
    private static final FastJsonJsonSerializer INSTANCE = new FastJsonJsonSerializer();

    public static FastJsonJsonSerializer getInstance() {
        return INSTANCE;
    }

    @Override
    public String serialize(Object object) {
        if (object == null) {
            return "";
        }
        return JSON.toJSONString(object);
    }

    @Override
    public <T> T parseObject(String json, Type type) {
        return JSON.parseObject(json, type);
    }

    @Override
    public List<Object> parseArray(String json) {
        return JSON.parseArray(json);
    }

    @Override
    public Map<String, Object> toMap(String json) {
        return JSON.parseObject(json);
    }
}


<|start_filename|>src/main/java/com/github/dadiyang/httpinvoker/requestor/JsoupRequestor.java<|end_filename|>
package com.github.dadiyang.httpinvoker.requestor;

import com.github.dadiyang.httpinvoker.serializer.JsonSerializerDecider;
import com.github.dadiyang.httpinvoker.util.ObjectUtils;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Map;

import static com.github.dadiyang.httpinvoker.util.ParamUtils.*;
import static org.jsoup.Connection.Method;
import static org.jsoup.Connection.Response;

/**
 * The default implementation of {@link Requestor} that send the request using Jsoup which is a elegant http client I've ever use.
 * <p>
 * the parameter will be formatted according to the request method.
 *
 * @author huangxuyang
 * date 2018/11/1
 */
public class JsoupRequestor implements Requestor {
    private static final Logger log = LoggerFactory.getLogger(JsoupRequestor.class);
    private static final String CONTENT_TYPE = "Content-Type";
    private static final String APPLICATION_JSON = "application/json";

    /**
     * {@inheritDoc}
     */
    @Override
    public HttpResponse sendRequest(HttpRequest request) throws IOException {
        // send request
        Method m = Method.valueOf(request.getMethod().toUpperCase());
        Response response;
        String url = request.getUrl();
        int timeout = request.getTimeout();
        if (!m.hasBody()) {
            String qs = toQueryString(request.getData());
            String fullUrl = request.getUrl() + qs;
            log.debug("send {} request to {}", m, fullUrl);
            Connection conn = Jsoup.connect(fullUrl)
                    .method(m)
                    .timeout(timeout)
                    // unlimited size
                    .maxBodySize(0)
                    .ignoreContentType(true)
                    .ignoreHttpErrors(true);
            addHeadersAndCookies(request, conn);
            setContentType(request, conn);
            response = conn.execute();
        } else {
            Connection conn = Jsoup.connect(url)
                    .method(m)
                    .timeout(timeout)
                    // unlimited size
                    .maxBodySize(0)
                    .ignoreContentType(true)
                    .ignoreHttpErrors(true);
            addHeadersAndCookies(request, conn);
            setContentType(request, conn);
            Map<String, Object> data = request.getData();
            // body first
            if (request.getBody() != null) {
                Object bodyParam = request.getBody();
                // if the body param is MultiPart or InputStream, submit a multipart form
                if (isUploadRequest(bodyParam)) {
                    log.debug("upload file {} request to {} ", m, url);
                    response = uploadFile(request);
                } else {
                    if (useJson(request, bodyParam)) {
                        response = conn.requestBody(JsonSerializerDecider.getJsonSerializer().serialize(bodyParam)).execute();
                    } else {
                        Map<String, String> map = toMapStringString(bodyParam, "");
                        response = conn.data(map).execute();
                    }
                }
            } else if (data == null
                    || data.isEmpty()) {
                log.debug("send {} request to {}", m, url);
                response = conn.execute();
            } else {
                if (useJson(request, data)) {
                    if (m == Method.PATCH) {
                        // use X-HTTP-Method-Override header to send a fake PATCH request
                        conn.method(Method.POST).header("X-HTTP-Method-Override", "PATCH");
                    }
                    response = conn.requestBody(JsonSerializerDecider.getJsonSerializer().serialize(data)).execute();
                } else {
                    Map<String, String> map = toMapStringString(data, "");
                    response = conn.data(map).execute();
                }
            }
        }
        return new JsoupHttpResponse(response);
    }

    private void setContentType(HttpRequest request, Connection conn) {
        // set a default Content-Type if not provided
        if (request.getHeaders() == null || !request.getHeaders().containsKey(CONTENT_TYPE)) {
            conn.header(CONTENT_TYPE, APPLICATION_JSON);
        }
    }

    /**
     * either param is a collection or Content-Type absence or equals to APPLICATION_JSON
     */
    private boolean useJson(HttpRequest request, Object param) {
        // collection can only be send by json currently
        return isCollection(param) || request.getHeaders() == null
                || !request.getHeaders().containsKey(CONTENT_TYPE)
                || ObjectUtils.equals(request.getHeaders().get(CONTENT_TYPE), APPLICATION_JSON);
    }

    private void addHeadersAndCookies(HttpRequest request, Connection conn) {
        if (request.getHeaders() != null) {
            conn.headers(request.getHeaders());
        }
        if (request.getCookies() != null) {
            conn.cookies(request.getCookies());
        }
    }

    /**
     * @param request the request
     */
    private Response uploadFile(HttpRequest request) throws IOException {
        Connection conn = Jsoup.connect(request.getUrl());
        conn.method(Method.POST)
                .timeout(request.getTimeout())
                .ignoreHttpErrors(true)
                // unlimited size
                .maxBodySize(0)
                .ignoreContentType(true);
        addHeadersAndCookies(request, conn);
        Object body = request.getBody();
        // handle MultiPart
        if (body instanceof MultiPart) {
            return handleMultiPart(conn, (MultiPart) body);
        } else {
            return handleMultiPart(conn, convertInputStreamAndFile(request));
        }
    }

    private Response handleMultiPart(Connection conn, MultiPart body) throws IOException {
        for (MultiPart.Part part : body.getParts()) {
            if (part.getKey() == null || part.getValue() == null) {
                throw new IllegalArgumentException("both key and value of part must not be null");
            }
            if (part.getInputStream() != null) {
                conn.data(part.getKey(), part.getValue(), part.getInputStream());
            } else {
                conn.data(part.getKey(), part.getValue());
            }
        }
        return conn.execute();
    }
}


<|start_filename|>src/main/java/com/github/dadiyang/httpinvoker/serializer/JsonSerializer.java<|end_filename|>
package com.github.dadiyang.httpinvoker.serializer;

import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

/**
 * 序列化器
 *
 * @author dadiyang
 * @since 2019/3/1
 */
public interface JsonSerializer {
    /**
     * 将对象序列化为字符串
     *
     * @param object 对象
     * @return 字符串
     */
    String serialize(Object object);

    <T> T parseObject(String json, Type type);

    List<Object> parseArray(String json);

    Map<String, Object> toMap(String json);
}


<|start_filename|>src/main/java/com/github/dadiyang/httpinvoker/serializer/JsonSerializerDecider.java<|end_filename|>
package com.github.dadiyang.httpinvoker.serializer;

import com.github.dadiyang.httpinvoker.util.ReflectionUtils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 默认依次检测当前类路径是否有 FastJson 和 Gson 以决定采用哪种实现
 * <p>
 * 使用者可以通过 registerJsonSerializer 注册自己指定的 Json 实现，然后调用 setJsonInstanceKey 指定已注册的 Json 实现
 *
 * @author dadiyang
 * @since 2020/12/5
 */
public class JsonSerializerDecider {
    private static final String FAST_JSON_CLASS = "com.alibaba.fastjson.JSON";
    private static final String GSON_CLASS = "com.google.gson.Gson";
    private static Map<String, JsonSerializer> map = new ConcurrentHashMap<String, JsonSerializer>();
    private static String jsonInstanceKey;

    /**
     * 根据规则获取JSON序列化器实例
     *
     * @return JSON序列化器实例
     */
    public static JsonSerializer getJsonSerializer() {
        if (jsonInstanceKey != null) {
            JsonSerializer instance = map.get(jsonInstanceKey);
            if (instance == null) {
                throw new IllegalStateException("已指定了JSON序列化实现为: " + jsonInstanceKey + "，但是没有实际注册这个实现，请调用 registerJsonSerializer 方法先注册");
            }
            return instance;
        }
        return getDefaultInstance();
    }

    private static JsonSerializer getDefaultInstance() {
        // 默认使用 FAST_JSON
        if (ReflectionUtils.classExists(FAST_JSON_CLASS)) {
            return FastJsonJsonSerializer.getInstance();
        }
        if (ReflectionUtils.classExists(GSON_CLASS)) {
            return GsonJsonSerializer.getInstance();
        }
        throw new IllegalStateException("当前没有可用的JSON序列化器");
    }

    public static String getJsonInstanceKey() {
        return jsonInstanceKey;
    }

    /**
     * 指定使用哪一个 jsonSerializer 实现，使用这个特性必须先使用 registerJsonSerializer 方法把这个key对应的序列化进行注册，否则无法正常使用
     *
     * @param jsonInstanceKey 实例key
     */
    public static void setJsonInstanceKey(String jsonInstanceKey) {
        JsonSerializerDecider.jsonInstanceKey = jsonInstanceKey;
    }

    /**
     * 注册 json 序列化器
     *
     * @param key            实例key
     * @param jsonSerializer 序列化器实例
     */
    public static void registerJsonSerializer(String key, JsonSerializer jsonSerializer) {
        map.put(key, jsonSerializer);
    }
}


<|start_filename|>src/main/java/com/github/dadiyang/httpinvoker/requestor/DefaultResponseProcessor.java<|end_filename|>
package com.github.dadiyang.httpinvoker.requestor;

import com.github.dadiyang.httpinvoker.serializer.JsonSerializerDecider;
import com.github.dadiyang.httpinvoker.util.ObjectUtils;

import java.io.BufferedInputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 * @author huangxuyang
 * date 2019/2/21
 */
public class DefaultResponseProcessor implements ResponseProcessor {

    @Override
    public Object process(HttpResponse response, Method method) {
        // not need a return value
        if (ObjectUtils.equals(method.getReturnType(), Void.class)
                || ObjectUtils.equals(method.getReturnType(), void.class)) {
            return null;
        }
        String body = response.getBody();
        if (body == null || body.trim().isEmpty()) {
            return null;
        }
        // return body if return type is Object
        if (method.getReturnType() == Object.class) {
            return response.getBody();
        }
        if (method.getReturnType() == String.class
                || method.getReturnType() == CharSequence.class) {
            return body;
        }
        if (method.getReturnType() == byte[].class) {
            return response.getBodyAsBytes();
        }
        if (method.getReturnType().isAssignableFrom(BufferedInputStream.class)) {
            return response.getBodyStream();
        }
        if (method.getReturnType().isAssignableFrom(response.getClass())) {
            return response;
        }
        // get generic return type
        Type type = method.getGenericReturnType();
        type = type == null ? method.getReturnType() : type;
        return JsonSerializerDecider.getJsonSerializer().parseObject(response.getBody(), type);
    }
}


<|start_filename|>src/test/java/com/github/dadiyang/httpinvoker/serializer/JsonSerializerTest.java<|end_filename|>
package com.github.dadiyang.httpinvoker.serializer;

import com.github.dadiyang.httpinvoker.entity.City;
import org.junit.Test;

import java.math.BigDecimal;
import java.util.*;

import static org.junit.Assert.*;

/**
 * 本单测主要尽最大可能让不同的序列化实现互相兼容
 *
 * @author huangxuyang
 * @since 2020/12/6
 */
public class JsonSerializerTest {
    private GsonJsonSerializer gsonJsonSerializer = new GsonJsonSerializer();
    private FastJsonJsonSerializer fastJsonJsonSerializer = new FastJsonJsonSerializer();

    @Test
    public void serialize() {
        Date now = new Date();
        Map<String, Object> origMap = new LinkedHashMap<String, Object>();
        origMap.put("date", now);
        origMap.put("double", 1.1);
        origMap.put("integer", 1);
        origMap.put("arr", Arrays.asList(1, 2, 3, 4, 5));
        String gsonJson = gsonJsonSerializer.serialize(origMap);
        String fastJsonJson = fastJsonJsonSerializer.serialize(origMap);
        assertEquals("gson序列化结果应与fastJson序列化结果一致", gsonJson, fastJsonJson);
        String expectJson = "{\"date\":" + now.getTime() + ",\"double\":1.1,\"integer\":1,\"arr\":[1,2,3,4,5]}";
        assertEquals("序列化结果应与期望的一致", expectJson, gsonJson);

        Map<String, Object> map = gsonJsonSerializer.toMap(gsonJson);
        Map<String, Object> fastJsonMap = fastJsonJsonSerializer.toMap(gsonJson);

        assertMapEquals(map, fastJsonMap);

        assertEquals("再序列化回来，然后再序列化，应该结果一致", expectJson, gsonJsonSerializer.serialize(map));
    }

    private void assertMapEquals(Map<String, Object> m1, Map<String, Object> m2) {
        assertFalse("两个空map比对没有意义", m1.isEmpty());
        assertFalse("两个空map比对没有意义", m2.isEmpty());
        assertEquals("两个map的大小应该一致", m1.size(), m2.size());
        assertTrue("两个map的key应互相全包含", m1.keySet().containsAll(m2.keySet()));
        assertTrue("两个map的key应互相全包含", m2.keySet().containsAll(m1.keySet()));

        for (Map.Entry<String, Object> entry : m1.entrySet()) {
            assertEquals(entry.getKey() + "应相等", entry.getValue(), m2.get(entry.getKey()));
        }
    }

    @Test
    public void parseObject() {
        City city = new City();
        city.setId(1);
        city.setName("北京");

        String gsonJson = gsonJsonSerializer.serialize(city);
        String fastJsonJson = fastJsonJsonSerializer.serialize(city);
        assertEquals("gson序列化结果应与fastJson序列化结果一致", gsonJson, fastJsonJson);

        City gsonCity = gsonJsonSerializer.parseObject(gsonJson, City.class);
        City fastCity = fastJsonJsonSerializer.parseObject(gsonJson, City.class);
        assertEquals("gson反序列化结果应与fastJson反序列化结果一致", gsonCity, fastCity);

        assertEquals("序列化后再反序列化回来应该相等", city, gsonCity);
    }

    @Test
    public void parseArray() {
        String arrJson = "[1.0,2,3,4,5]";
        List<Object> gsonArr = gsonJsonSerializer.parseArray(arrJson);
        List<Object> fastJsonArr = fastJsonJsonSerializer.parseArray(arrJson);
        assertEquals(5, gsonArr.size());
        assertEquals(5, fastJsonArr.size());
        assertTrue("应包含指定的元素" + gsonArr, gsonArr.contains(new BigDecimal("1.0")));
        assertTrue("应包含指定的元素" + gsonArr, gsonArr.contains(2));
        assertTrue("应包含指定的元素" + gsonArr, gsonArr.contains(3));
        assertTrue("应包含指定的元素" + gsonArr, gsonArr.contains(4));
        assertTrue("应包含指定的元素" + gsonArr, gsonArr.contains(5));

        assertTrue("应包含指定的元素" + fastJsonArr, fastJsonArr.contains(new BigDecimal("1.0")));
        assertTrue("应包含指定的元素" + fastJsonArr, fastJsonArr.contains(2));
        assertTrue("应包含指定的元素" + fastJsonArr, fastJsonArr.contains(3));
        assertTrue("应包含指定的元素" + fastJsonArr, fastJsonArr.contains(4));
        assertTrue("应包含指定的元素" + fastJsonArr, fastJsonArr.contains(5));
    }
}

<|start_filename|>src/main/java/com/github/dadiyang/httpinvoker/util/ReflectionUtils.java<|end_filename|>
package com.github.dadiyang.httpinvoker.util;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 反射工具类
 *
 * @author dadiyang
 * @since 2019/3/1
 */
public class ReflectionUtils {
    private static Map<String, Boolean> existCache = new ConcurrentHashMap<String, Boolean>();

    private ReflectionUtils() {
        throw new UnsupportedOperationException("静态工具类不允许被实例化");
    }

    /**
     * 检查某个全类名是否存在于 classpath 中
     */
    public static boolean classExists(String clzFullName) {
        if (StringUtils.isBlank(clzFullName)) {
            return false;
        }
        Boolean rs = existCache.get(clzFullName);
        if (rs != null && rs) {
            return true;
        }
        try {
            Class<?> clz = Class.forName(clzFullName);
            existCache.put(clzFullName, clz != null);
            return clz != null;
        } catch (ClassNotFoundException e) {
            existCache.put(clzFullName, false);
            return false;
        }
    }
}


<|start_filename|>src/main/java/com/github/dadiyang/httpinvoker/serializer/GsonJsonSerializer.java<|end_filename|>
package com.github.dadiyang.httpinvoker.serializer;

import com.google.gson.*;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 基于 gson 的 json 序列化器，仅在类路径中有 Gson 并且没有注册其他的 json 序列化器时使用
 *
 * @author dadiyang
 * @since 2019/3/1
 */
public class GsonJsonSerializer implements JsonSerializer {
    private static final Gson GSON = new GsonBuilder()
            .registerTypeAdapter(new TypeToken<Map<String, Object>>() {
            }.getType(), NumberTypeAdapter.INSTANCE)
            .registerTypeAdapter(new TypeToken<List<Object>>() {
            }.getType(), NumberTypeAdapter.INSTANCE)
            .registerTypeAdapter(java.util.Date.class, new DateSerializer()).setDateFormat(DateFormat.LONG)
            .registerTypeAdapter(java.util.Date.class, new DateDeserializer()).setDateFormat(DateFormat.LONG)
            .create();

    private static final GsonJsonSerializer INSTANCE = new GsonJsonSerializer();

    public static GsonJsonSerializer getInstance() {
        return INSTANCE;
    }

    @Override
    public String serialize(Object object) {
        return GSON.toJson(object);
    }

    @Override
    public <T> T parseObject(String json, Type type) {
        return GSON.fromJson(json, type);
    }

    @Override
    public List<Object> parseArray(String json) {
        Type type = new TypeToken<List<Object>>() {
        }.getType();
        return GSON.fromJson(json, type);
    }

    @Override
    public Map<String, Object> toMap(String json) {
        return GSON.fromJson(json, new TypeToken<Map<String, Object>>() {
        }.getType());
    }

    /**
     * 解决 int 类型序列化时变成 double 类型的问题
     */
    public static class NumberTypeAdapter extends TypeAdapter<Object> {
        private static final NumberTypeAdapter INSTANCE = new NumberTypeAdapter();

        @Override
        public Object read(JsonReader in) throws IOException {
            JsonToken token = in.peek();
            switch (token) {
                case BEGIN_ARRAY:
                    List<Object> list = new ArrayList<Object>();
                    in.beginArray();
                    while (in.hasNext()) {
                        list.add(read(in));
                    }
                    in.endArray();
                    return list;
                case BEGIN_OBJECT:
                    Map<String, Object> map = new LinkedTreeMap<String, Object>();
                    in.beginObject();
                    while (in.hasNext()) {
                        map.put(in.nextName(), read(in));
                    }
                    in.endObject();
                    return map;
                case STRING:
                    return in.nextString();
                case NUMBER:
                    // 改写数字的处理逻辑，将数字值分为整型与浮点型。
                    String str = in.nextString();
                    // 有小数点直接返回 BigDecimal 类弄浮点数
                    if (str.contains(".")) {
                        return new BigDecimal(str);
                    }
                    long lngNum = Long.parseLong(str);
                    if (lngNum > Integer.MAX_VALUE || lngNum < Integer.MIN_VALUE) {
                        return lngNum;
                    } else {
                        return (int) lngNum;
                    }
                case BOOLEAN:
                    return in.nextBoolean();
                case NULL:
                    in.nextNull();
                    return null;
                default:
                    throw new IllegalStateException();
            }
        }

        @Override
        public void write(JsonWriter out, Object value) {
            // 序列化无需实现
        }
    }

    /**
     * 日期类型的字段反序列化器
     */
    public static class DateDeserializer implements JsonDeserializer<Date> {

        @Override
        public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            return new Date(json.getAsJsonPrimitive().getAsLong());
        }
    }

    /**
     * 时间类型序列化为时间戳
     */
    public static class DateSerializer implements com.google.gson.JsonSerializer<Date> {
        @Override
        public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src.getTime());
        }
    }
}
