一个http请求工具类的接口化(接口设计)

   我们项目中可能会使用很多的http请求的库,比如URLConnection、ApacheClient、OkHttp库等。每个库的操作方式都不一样,这是一个巨大的困扰。如果需要换一个库,那需要改变的东西就很多。利用抽象的思想,将http请求的整个过程抽象出来,包括异常处理,以后我们只需要面向接口编程即可。

首先设计了http的模板类HttpTemplate,不同的实现类只需要实现此模板,http参数是最全的,基本上就代表了http请求所需要的全部参数。然后设计了简化操作的HttpClient、SmartHttpClient及结果自动转换为Bean的ConverterSmartHttpClient。如此设计的一个好处是接口与实现分离,系统内部可以无限优化HttpTemplate,但是面向终端用户的HttpClient-SmartHttpClient-ConverterSmartConvert几乎不用变化。继承和实现的树为:

继承和实现树

最顶级的HttpTemplate为

package cn.zytx.common.http.base;

import cn.zytx.common.http.Method;
import cn.zytx.common.utils.ArrayListMultimap;

import java.io.IOException;

/**
 * http请求的模板方法接口
 * @author xiongshiyan at 2018/6/19
 */
public interface HttpTemplate<C>{
    /**
     * http请求的模板方法
     *
     * @param url URL
     * @param method 请求方法
     * @param contentType 请求体MIME类型
     * @param contentCallback 处理请求体的
     * @param headers headers
     * @param connectTimeout 连接超时时间
     * @param readTimeout 读取超时时间
     * @param resultCharset 结果字符集
     * @param includeHeaders 是否结果包含header
     * @param resultCallback 结果处理器
     * @param <R> 处理的结果
     * @return 处理的结果
     * @throws IOException IOException
     */
    <R> R template(String url, Method method, String contentType, ContentCallback<C> contentCallback,
                   ArrayListMultimap<String, String> headers, int connectTimeout, int readTimeout,
                   String resultCharset, boolean includeHeaders, ResultCallback<R> resultCallback) throws IOException;
}

其中ContentCallback用于将数据发送出去、ResultCallback用于封装结果。

package cn.zytx.common.http.base;

import okhttp3.RequestBody;
import org.apache.http.HttpEntity;

import java.io.IOException;
import java.net.HttpURLConnection;

/**
 * 写入body数据,属于一个回调方法
 * @author xiongshiyan at 2018/6/7
 * @param <CC> 处理的泛型,不同的实现方式有不同的写入方式
 * @see HttpURLConnection#getOutputStream()
 * @see org.apache.http.client.methods.HttpEntityEnclosingRequestBase#setEntity(HttpEntity)
 * @see okhttp3.Request.Builder#method(String, RequestBody)
 */
@FunctionalInterface
public interface ContentCallback<CC> {
    /**
     * 往connection中写入数据
     * @param cc 连接
     * @throws IOException IOException
     */
    void doWriteWith(CC cc) throws IOException;
}
package cn.zytx.common.http.base;

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

/**
 * 结果封装,更通用的封装方式,输入更通用,自己转化成需要的数据结构string、bytes、file...
 * @author xiongshiyan at 2018/6/7
 */
@FunctionalInterface
public interface ResultCallback<R> {
    /**
     * 转换结果
     *
     * @param stream body,代表输入流,自己转化成需要的数据结构string、bytes、file...
     * @param resultCharset 编码
     * @param headers headers
     * @return <R>
     * @throws IOException IOException
     * @see cn.zytx.common.http.smart.Response
     */
    R convert(InputStream stream, String resultCharset, Map<String, List<String>> headers) throws IOException;
}

http接口类,主要提供get、post方法。实现类只需要实现最全参数的get和post方法,其他都是调用此方法的简化方法。

String get(String url, Map<String, String> params,Map<String,String> headers,int connectTimeout,int readTimeout , String resultCharset) throws IOException;
String post(String url, String body, String contentType,Map<String,String> headers,int connectTimeout,int readTimeout, String bodyCharset , String resultCharset) throws IOException;

方法都声明抛出IOException,可以代表java.net.SocketTimeoutException connect timed out/read time out。另外如果过程中发生了任何异常都会抛出HttpException,可以处理内部错误和服务器错误,通过code区分。

package cn.zytx.common.http.basic;

import cn.zytx.common.http.HttpException;
import cn.zytx.common.http.HttpUtil;
import cn.zytx.common.http.smart.SmartHttpClient;
import cn.zytx.common.utils.ArrayListMultimap;

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

/**
 * GET POST接口
 * @author 熊诗言2017/11/24
 * @see SmartHttpClient
 * 针对Http超时和各种错误码分别处理
 * 使用时,可以直接new实现类,也可以通过{@link cn.zytx.common.utils.SpringFactoriesLoader }获取,这样就不会与实现类绑定
 */
public interface HttpClient {
    int CONNECT_TIMEOUT                         = 15000;
    int READ_TIMEOUT                            = 15000;
    String EXCEPTION_UNKOWN                     = "未知异常";
    String DEFAULT_CHARSET                      = "UTF-8";
    String FORM_URLENCODED                      = "application/x-www-form-urlencoded";
    String JSON                                 = "application/json";
    String TEXT_XML                             = "text/xml";
    String FORM_URLENCODED_WITH_DEFAULT_CHARSET = FORM_URLENCODED + ";charset=" + DEFAULT_CHARSET;
    String TEXT_XML_WITH_DEFAULT_CHARSET        = TEXT_XML + ";charset=" + DEFAULT_CHARSET;
    String JSON_WITH_DEFAULT_CHARSET            = JSON + ";charset=" + DEFAULT_CHARSET;

     /**
     *HTTP GET请求
     * @param url URL,可以帶参数
     * @param params 参数列表,可以为 null
     * @param headers HTTP header 可以为 null
     * @param connectTimeout 连接超时时间
     * @param readTimeout 读超时时间
     * @param resultCharset 返回编码
     * @return 返回的内容
     * @throws IOException 超时异常 {@link java.net.SocketTimeoutException connect timed out/read time out}
     * @throws HttpException 非200请求异常或者其他异常抛出
     */
    String get(String url, Map<String, String> params,Map<String,String> headers,int connectTimeout,int readTimeout , String resultCharset) throws IOException;

    default String get(String url, Map<String, String> params,Map<String,String> headers,int connectTimeout,int readTimeout) throws IOException{
        return get(url, params, headers, connectTimeout, readTimeout , DEFAULT_CHARSET);
    }
    default String get(String url, Map<String, String> params,Map<String,String> headers , String resultCharset) throws IOException{
        return get(url,params,headers,CONNECT_TIMEOUT,READ_TIMEOUT,resultCharset);
    }
    default String get(String url, Map<String, String> params,Map<String,String> headers) throws IOException{
        return get(url,params,headers,CONNECT_TIMEOUT,READ_TIMEOUT);
    }
    default String get(String url, Map<String, String> params,int connectTimeout,int readTimeout , String resultCharset) throws IOException{
        return get(url,params,null,connectTimeout,readTimeout,resultCharset);
    }
    default String get(String url, Map<String, String> params,int connectTimeout,int readTimeout) throws IOException{
        return get(url,params,null,connectTimeout,readTimeout);
    }
    default String get(String url,Map<String, String> params , String resultCharset) throws IOException{
        return get(url,params,null,CONNECT_TIMEOUT,READ_TIMEOUT,resultCharset);
    }
    default String get(String url,Map<String, String> params) throws IOException{
        return get(url,params,null,CONNECT_TIMEOUT,READ_TIMEOUT);
    }
    default String get(String url , String resultCharset) throws IOException{
        return get(url,null,null,CONNECT_TIMEOUT,READ_TIMEOUT , resultCharset);
    }
    default String get(String url) throws IOException{
        return get(url,null,null,CONNECT_TIMEOUT,READ_TIMEOUT);
    }


    /**
     * HTTP POST
     * @param url URL
     * @param body 请求体
     * @param contentType 请求体类型
     * @param headers 头
     * @param connectTimeout 连接超时时间
     * @param readTimeout 读超时时间
     * @param bodyCharset 请求体编码
     * @param resultCharset 返回编码
     * @return 请求返回
     * @throws IOException 超时异常 {@link java.net.SocketTimeoutException connect timed out/read time out}
     * @throws HttpException 非200请求异常或者其他异常抛出
     */
    String post(String url, String body, String contentType,Map<String,String> headers,int connectTimeout,int readTimeout, String bodyCharset , String resultCharset) throws IOException;

    default String post(String url, String body, String contentType,Map<String,String> headers,int connectTimeout,int readTimeout) throws IOException{
        return post(url,body,contentType,headers,connectTimeout,readTimeout, DEFAULT_CHARSET,DEFAULT_CHARSET);
    }
    default String post(String url, String body, String contentType,Map<String,String> headers, String bodyCharset , String resultCharset) throws IOException{
        return post(url,body,contentType,headers,CONNECT_TIMEOUT,READ_TIMEOUT,bodyCharset,resultCharset);
    }
    default String post(String url, String body, String contentType,Map<String,String> headers) throws IOException{
        return post(url,body,contentType,headers,CONNECT_TIMEOUT,READ_TIMEOUT);
    }
    default String post(String url, String body, String contentType,int connectTimeout,int readTimeout, String bodyCharset , String resultCharset) throws IOException{
        return post(url,body,contentType,null,connectTimeout,readTimeout,bodyCharset,resultCharset);
    }
    default String post(String url, String body, String contentType,int connectTimeout,int readTimeout) throws IOException{
        return post(url,body,contentType,null,connectTimeout,readTimeout);
    }
    /**
     * @see HttpClient#post(String, String, String, Map, int, int, String, String)
     */
    default String post(String url, String body, String contentType, String bodyCharset , String resultCharset) throws IOException{
        return post(url,body,contentType,null,CONNECT_TIMEOUT,READ_TIMEOUT,bodyCharset,resultCharset);
    }
    default String post(String url, String body, String contentType) throws IOException{
        return post(url,body,contentType,null,CONNECT_TIMEOUT,READ_TIMEOUT);
    }
    default String postJson(String url, String body, String bodyCharset , String resultCharset) throws IOException{
        return post(url,body,JSON_WITH_DEFAULT_CHARSET,null,CONNECT_TIMEOUT,READ_TIMEOUT,bodyCharset,resultCharset);
    }
    default String postJson(String url, String body) throws IOException{
        return post(url,body,JSON_WITH_DEFAULT_CHARSET,null,CONNECT_TIMEOUT,READ_TIMEOUT);
    }

    /**参数用 =和& 连接*/
    default String post(String url, Map<String, String> params,Map<String,String> headers, String bodyCharset , String resultCharset) throws IOException{
        return post(url, HttpUtil.contactMap(params),FORM_URLENCODED_WITH_DEFAULT_CHARSET,headers,bodyCharset,resultCharset);
    }
    default String post(String url, Map<String, String> params,Map<String,String> headers) throws IOException{
        return post(url, HttpUtil.contactMap(params),FORM_URLENCODED_WITH_DEFAULT_CHARSET,headers);
    }
    default String post(String url, Map<String, String> params, String bodyCharset , String resultCharset) throws IOException{
        return post(url,HttpUtil.contactMap(params),FORM_URLENCODED_WITH_DEFAULT_CHARSET,null,bodyCharset,resultCharset);
    }
    default String post(String url, Map<String, String> params) throws IOException{
        return post(url,HttpUtil.contactMap(params),FORM_URLENCODED_WITH_DEFAULT_CHARSET,null);
    }

    /**
     * 文件下载相关,下载为字节数组
     */
    byte[] getAsBytes(String url , ArrayListMultimap<String,String> headers, int connectTimeout, int readTimeout) throws IOException;
    default byte[] getAsBytes(String url,int connectTimeout,int readTimeout) throws IOException{
        return getAsBytes(url , null , connectTimeout , readTimeout);
    }
    default byte[] getAsBytes(String url,ArrayListMultimap<String,String> headers) throws IOException{
        return getAsBytes(url , headers , CONNECT_TIMEOUT , READ_TIMEOUT);
    }
    default byte[] getAsBytes(String url) throws IOException{
        return getAsBytes(url , null , CONNECT_TIMEOUT , READ_TIMEOUT);
    }

    /**
     * 文件下载相关,下载为文件
     */
    File getAsFile(String url , ArrayListMultimap<String,String> headers , File file ,int connectTimeout,int readTimeout) throws IOException;
    default File getAsFile(String url , File file ,int connectTimeout,int readTimeout) throws IOException{
        return getAsFile(url , null  , file , connectTimeout , readTimeout);
    }
    default File getAsFile(String url , ArrayListMultimap<String,String> headers , File file) throws IOException{
        return getAsFile(url , headers  , file , CONNECT_TIMEOUT , READ_TIMEOUT);
    }
    default File getAsFile(String url , File file) throws IOException{
        return getAsFile(url , null  , file , CONNECT_TIMEOUT , READ_TIMEOUT);
    }

    /**
     * 上传文件
     * @param url URL
     * @param files 多个文件信息
     */
    String upload(String url , ArrayListMultimap<String,String> headers, int connectTimeout , int readTimeout , String resultCharset , FormFile... files) throws IOException;

    default String upload(String url , ArrayListMultimap<String,String> headers, int connectTimeout , int readTimeout , FormFile... files) throws IOException{
        return upload(url, headers ,connectTimeout , readTimeout , DEFAULT_CHARSET , files);
    }
    default String upload(String url , ArrayListMultimap<String,String> headers , FormFile... files) throws IOException{
        return upload(url, headers ,CONNECT_TIMEOUT , READ_TIMEOUT, DEFAULT_CHARSET , files);
    }
    default String upload(String url , int connectTimeout , int readTimeout , FormFile... files) throws IOException{
        return upload(url, null ,connectTimeout , readTimeout , DEFAULT_CHARSET , files);
    }
    default String upload(String url , FormFile... files) throws IOException{
        return upload(url, null , CONNECT_TIMEOUT , READ_TIMEOUT , DEFAULT_CHARSET , files);
    }

    /**
     * 上传文件和key-value数据
     * @param url URL
     * @param files 多个文件信息
     */
    String upload(String url , ArrayListMultimap<String , String> params , ArrayListMultimap<String,String> headers, int connectTimeout , int readTimeout , String resultCharset , FormFile... files) throws IOException;
    default String upload(String url , ArrayListMultimap<String , String> params ,ArrayListMultimap<String,String> headers, int connectTimeout , int readTimeout , FormFile... files) throws IOException{
        return upload(url, params ,headers ,connectTimeout , readTimeout , DEFAULT_CHARSET , files);
    }
    default String upload(String url , ArrayListMultimap<String , String> params ,ArrayListMultimap<String,String> headers , FormFile... files) throws IOException{
        return upload(url, params ,headers ,CONNECT_TIMEOUT , READ_TIMEOUT, DEFAULT_CHARSET , files);
    }
    default String upload(String url , int connectTimeout , int readTimeout , ArrayListMultimap<String , String> params ,FormFile... files) throws IOException{
        return upload(url, params ,null ,connectTimeout , readTimeout , DEFAULT_CHARSET , files);
    }
    default String upload(String url ,Map<String , String> params , FormFile... files) throws IOException{
        ArrayListMultimap<String , String> multimap = ArrayListMultimap.fromMap(params);
        return upload(url, multimap ,null , CONNECT_TIMEOUT , READ_TIMEOUT , DEFAULT_CHARSET , files);
    }
}

FormFile代表文件上传类:

package cn.zytx.common.http.basic;

import java.io.*;

/**
 * 上传文件表单数据
 * @author xiongshiyan
 */
public class FormFile{
    /**上传文件的数据 */
    /*private byte[]      data;*/
    private InputStream inStream;
    private long        fileLen;
    /**文件名称 */
    private String filName;
    /**请求参数名称 */
    private String      parameterName;
    /**内容类型 */
    private String      contentType = "application/octet-stream";

    public FormFile(String filName, byte[] data, String parameterName, String contentType){
        this.inStream = new ByteArrayInputStream(data);
        init(filName, data.length , parameterName, contentType);
    }

    public FormFile(File file, String parameterName, String contentType) {
        try {
            this.inStream = new FileInputStream(file);
            init(file.getName() , file.length() , parameterName , contentType);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public FormFile(String fileName, InputStream inStream, long fileLen, String parameterName, String contentType){
        this.inStream = inStream;
        init(fileName , fileLen , parameterName , contentType);
    }

    private void init(String filName, long fileLen , String parameterName, String contentType) {
        this.filName = filName;
        this.parameterName = parameterName;
        this.fileLen = fileLen;
        if(contentType != null){
            this.contentType = contentType;
        }
    }

    public InputStream getInStream(){
        return inStream;
    }

    public long getFileLen(){
        return fileLen;
    }

    public String getFilName(){
        return filName;
    }

    public void setFilName(String filName){
        this.filName = filName;
    }

    public String getParameterName(){
        return parameterName;
    }

    public void setParameterName(String parameterName){
        this.parameterName = parameterName;
    }

    public String getContentType(){
        return contentType;
    }

    public void setContentType(String contentType){
        this.contentType = contentType;
    }

}

Http请求异常类:

package cn.zytx.common.http;

/**
 * 1.HTTP请求异常,包括http组件内部错误-1,服务器返回错误(错误码和错误信息)
 * 2.超时异常由专门的IOException表达
 * @author 熊诗言 2017/11/24
 */
public class HttpException extends RuntimeException{
    private int responseCode = -1;
    private String errorMessage = "error happens in client";

    public HttpException(int responseCode,String errorMessage){
        super(errorMessage);
        this.responseCode = responseCode;
        this.errorMessage = errorMessage;
    }
    public HttpException(String errorMessage){
        super(errorMessage);
        this.errorMessage = errorMessage;
    }
    public HttpException(Exception e){
        super(e);
        this.errorMessage = e.getMessage();
    }
    public HttpException(){
    }


    public int getResponseCode() {
        return responseCode;
    }

    public String getErrorMessage() {
        return errorMessage;
    }
}

另外定义了一些工具类和枚举类,HttpUtil用于连接参数为“k1=v1&k2=v2”的形式,绑定一个静态的HttpClient接口和实现并暴露,调用者可以初始化其为其他的实现类。

package cn.zytx.common.http;

import cn.zytx.common.Editor;
import cn.zytx.common.http.basic.HttpClient;
import cn.zytx.common.http.basic.NativeHttpClient;
import cn.zytx.common.http.smart.NativeSmartHttpClient;
import cn.zytx.common.http.smart.SmartHttpClient;
import cn.zytx.common.utils.ArrayListMultimap;
import cn.zytx.common.utils.CharsetUtil;
import cn.zytx.common.utils.Joiner;

import java.io.*;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author xiongshiyan at 2017/12/11
 */
public class HttpUtil {
    private HttpUtil(){}
    /**
     * key1=value1&key2=value2,如果value=null 或者 size=0 返回 ""
     * @param value 键值对
     */
    public static String contactMap(Map<String, String> value){
        if(null == value){return "";}
		/*value.forEach((k,v)->{
            try {
                value.put(k,URLEncoder.encode(v, CharsetUtil.UTF_8));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        });*/
        Editor<String> editor = (v)->{
            try {
                return URLEncoder.encode(v, CharsetUtil.UTF_8);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return null;
        };
        return Joiner.on("&").withKeyValueSeparator("=",editor).useForNull("").join(value);
		/*try {
			String params;
			params = "";
			Iterator<String> iterator = value.keySet().iterator();
			while(iterator.hasNext()){
                String key = iterator.next().toString();
                params += key + "=" + URLEncoder.encode(value.get(key), "UTF8") + "&";
            }
			if(params.length() > 0){
                params = params.substring(0, params.length() - 1);
            }
			return params;
		} catch (UnsupportedEncodingException e){
			e.printStackTrace();
			return "";
		}*/
    }
    /**
     * key1=value1&key2=value2&key2=value3,如果value=null 或者 size=0 返回 ""
     * @param value 键值对
     */
    public static String contactMap(ArrayListMultimap<String, String> value){
        if(null == value){return "";}
        try {
            String params;
            params = "";
            Iterator<String> iterator = value.keySet().iterator();
            while(iterator.hasNext()){
                String key = iterator.next();
                List<String> vList = value.get(key);
                int len = vList.size();
                for (int i = 0; i < len; i++) {
                    params += key + "=" + URLEncoder.encode(vList.get(i), "UTF8") + "&";
                }
            }
            if(params.length() > 0){
                params = params.substring(0, params.length() - 1);
            }
            return params;
        } catch (UnsupportedEncodingException e){
            e.printStackTrace();
            return "";
        }
    }

    /**
     * URL和参数
     * @param actionName URL,可以包含?
     * @param paramString 参数字符串,可以为"",null,k1=v1&k2=v2
     * @return 连接后的字符串
     */
    public static String contactUrlParams(String actionName, String paramString) {
        String url = actionName;
        if(!"".equals(paramString)) {
            //如果包含?,則直接追加//不包含?,則用?追加
            url = actionName.contains("?") ? actionName + "&" + paramString : actionName + "?" + paramString;
        }
        return url;
    }

    /**
     * @see HttpUtil#contactMap(ArrayListMultimap)
     * @see HttpUtil#contactUrlParams(String, String)
     */
    public static String contactUrlParams(String actionName, ArrayListMultimap<String , String> params) {
        Objects.requireNonNull(actionName);
        return contactUrlParams(actionName , contactMap(params));
    }
    /**
     * @see HttpUtil#contactMap(Map)
     * @see HttpUtil#contactUrlParams(String, String)
     */
    public static String contactUrlParams(String actionName, Map<String , String> params) {
        Objects.requireNonNull(actionName);
        return contactUrlParams(actionName , contactMap(params));
    }

    /**
     * 静态绑定
     */
    private static HttpClient BASIC_HTTP_CLIENT = new NativeHttpClient();
    private static SmartHttpClient SMART_HTTP_CLIENT = new NativeSmartHttpClient();

    public static HttpClient getBasicHttpClient() {
        return BASIC_HTTP_CLIENT;
    }
    public static void setBasicHttpClient(HttpClient httpClient){BASIC_HTTP_CLIENT = httpClient;}
    public static SmartHttpClient getSmartHttpClient() {
        return SMART_HTTP_CLIENT;
    }
    public static void setSmartHttpClient(SmartHttpClient smartHttpClient){SMART_HTTP_CLIENT = smartHttpClient;}
}
/**
 * Http方法枚举
 * @author Looly
 */
public enum Method {
	/**
	 * HTTP相关方法
	 */
	GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE, CONNECT, PATCH
}

在HttpClient的基础上再定义了一个SmartHttpClient,更好的模拟了http请求(主要可以设置一些header参数),参数也更加面向对象。

package cn.zytx.common.http.smart;

import cn.zytx.common.http.basic.HttpClient;
import cn.zytx.common.http.HttpException;

import java.io.File;
import java.io.IOException;
import java.util.Objects;

/**
 * @author xiongshiyan at 2017/12/9
 * 针对Http超时和各种错误码分别处理
 * @see HttpClient
 * 使用时,可以直接new实现类,也可以通过{@link cn.zytx.common.utils.SpringFactoriesLoader }获取,这样就不会与实现类绑定
 */
public interface SmartHttpClient extends HttpClient{
    /**
     * GET方法
     * @param request 请求参数
     * @return 响应
     * @throws IOException 超时等IO异常
     * @throws HttpException 404,500等或者其他异常都转换为该异常
     */
    Response get(Request request) throws IOException;
    /**
     * POST方法
     * @param request 请求参数
     * @return 响应
     * @throws IOException 超时等IO异常
     * @throws HttpException 404,500等或者其他异常都转换为该异常
     */
    Response post(Request request) throws IOException;

    /**
     * 下载为字节数组
     * @param request 请求参数
     * @return byte[]
     * @throws IOException IOException
     */
    byte[] getAsBytes(Request request) throws IOException;

    /**
     * 下载文件
     * @param request 请求参数
     * @return File 下载的文件
     * @throws IOException IOException
     */
    File getAsFile(Request request) throws IOException;

    /**
     * 文件上传
     * @param request 请求参数
     * @return Response
     * @throws IOException IOException
     */
    Response upload(Request request) throws IOException;

    /**
     * 对请求参数拦截处理 , 比如统一添加header , 参数加密 , 默认不处理
     * @param request Request
     * @return Request
     */
    default Request beforeTemplate(Request request){
        return Objects.requireNonNull(request);
    }

    /**
     * 对返回结果拦截处理 , 比如统一解密 , 默认不处理
     * @param response Response
     * @return Response
     */
    default Response afterTemplate(Response response){
        return response;
    }
}

其中Request和Response定义如下,可以视为一个JavaBean。

package cn.zytx.common.http.smart;

import cn.zytx.common.http.HttpUtil;
import cn.zytx.common.http.basic.FormFile;
import cn.zytx.common.utils.ArrayListMultimap;
import cn.zytx.common.utils.StrUtil;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * @author xiongshiyan at 2017/12/9
 */
public class Request {
    /**
     * 请求的URL
     */
    private String url;
    /**
     * 请求参数,针对GET请求存在
     */
    private ArrayListMultimap<String,String> params = new ArrayListMultimap<>();
    /**
     * 请求头
     */
    private ArrayListMultimap<String,String> headers = new ArrayListMultimap<>();
    /**
     * 针对POST存在,params这种加进来的参数最终拼接之后保存到这里
     */
    private String body;
    /**
     * 资源类型
     */
    private String contentType = null;
    /**
     * 连接超时时间
     */
    private int connectionTimeout = SmartHttpClient.CONNECT_TIMEOUT;
    /**
     * 读数据超时时间
     */
    private int readTimeout = SmartHttpClient.READ_TIMEOUT;
    /**
     * 请求体编码
     */
    private String bodyCharset = SmartHttpClient.DEFAULT_CHARSET;
    /**
     * 返回体编码
     */
    private String resultCharset = SmartHttpClient.DEFAULT_CHARSET;

    /**
     * 返回结果中是否包含headers
     */
    private boolean includeHeaders = false;

    /**
     * 2018-06-18为了文件上传增加的
     */
    private List<FormFile> formFiles = new ArrayList<>();
    /**
     * 为文件下载确定信息
     */
    private File file = null;

    private Request(String url){this.url = url;}

    /**
     * 静态方法创建请求
     * @param url URL
     * @return Request
     */
    public static Request of(String url){
        return new Request(url);
    }

    /**************************变种的Setter*******************************/
    public Request setUrl(String url) {
        this.url = url;
        return this;
    }

    public Request setParams(ArrayListMultimap<String, String> params) {
        this.params = params;
        return this;
    }
    public Request setParams(Map<String, String> params) {
        params.forEach((k,v)->this.params.put(k,v));
        return this;
    }
    public Request addParam(String key,String value){
        this.params.put(key, value);
        return this;
    }
    public Request setHeaders(ArrayListMultimap<String, String> headers) {
        this.headers = headers;
        return this;
    }
    public Request setHeaders(Map<String, String> headers) {
        headers.forEach((k,v)->this.headers.put(k,v));
        return this;
    }
    public Request addHeader(String key, String value){
        this.headers.put(key, value);
        return this;
    }

    public Request setBody(String body) {
        this.body = body;
        return this;
    }

    public Request setContentType(String contentType) {
        this.contentType = contentType;
        return this;
    }

    public Request setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
        return this;
    }

    public Request setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
        return this;
    }

    /****************************Getter**************************/
    public String getUrl() {
        return url;
    }

    public ArrayListMultimap<String, String> getParams() {
        return params;
    }

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

    public String getBody() {
        //如果没有Body就将params的参数拼接
        if(StrUtil.isBlank(body)){
            return HttpUtil.contactMap(params);
        }
        return body;
    }

    public String getContentType() {
        return contentType;
    }

    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public String getBodyCharset() {
        return bodyCharset;
    }

    public Request setBodyCharset(String bodyCharset) {
        this.bodyCharset = bodyCharset;
        return this;
    }

    public String getResultCharset() {
        return resultCharset;
    }

    public Request setResultCharset(String resultCharset) {
        this.resultCharset = resultCharset;
        return this;
    }

    public boolean isIncludeHeaders() {
        return includeHeaders;
    }

    public Request setIncludeHeaders(boolean includeHeaders) {
        this.includeHeaders = includeHeaders;
        return this;
    }

    public FormFile[] getFormFiles() {
        return this.formFiles.toArray(new FormFile[this.formFiles.size()]);
    }

    public Request addFormFile(FormFile... formFiles) {
        if(null != formFiles){
            this.formFiles.addAll(Arrays.asList(formFiles));
        }
        return this;
    }

    public File getFile() {
        return file;
    }

    public Request setFile(File file) {
        this.file = file;
        return this;
    }
    public Request setFile(String filePath) {
        this.file = new File(filePath);
        return this;
    }
}
package cn.zytx.common.http.smart;


import cn.zytx.common.http.HttpException;
import cn.zytx.common.utils.IoUtil;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author xiongshiyan at 2017/12/9
 */
public class Response {
    /**
     * 返回体
     */
    private String body;
    /**
     * 返回体编码
     */
    private String resultCharset = SmartHttpClient.DEFAULT_CHARSET;
    /**
     * 返回的header
     */
    private Map<String,List<String>> headers = new HashMap<>();

    private Response(String body) {
        this.body = body;
    }

    public static Response with(String body){
        return new Response(body);
    }
    public static Response with(String body , String resultCharset , Map<String , List<String>> headers){
        return new Response(body).setResultCharset(resultCharset).setHeaders(headers);
    }
    public static Response with(InputStream body , String resultCharset , Map<String , List<String>> headers){
        String read = null;
        try {
            read = IoUtil.read(body, resultCharset);
        } catch (IOException e) {
            throw new HttpException(e);
        }
        return new Response(read).setResultCharset(resultCharset).setHeaders(headers);
    }


    public String getBody() {
        return body;
    }

    public Response setBody(String body) {
        this.body = body;
        return this;
    }

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

    public List<String> getHeader(String key) {
        return headers.get(key);
    }
    public String getOneHeader(String key) {
        List<String> list = headers.get(key);
        if(null == list){return null;}
        return list.get(0);
    }

    public Response addHeader(String key,String value){
        List<String> list = headers.get(key);
        if(list == null){
            list = new ArrayList<>();
            if(list.add(value)){
                headers.put(key, list);
                return this;
            } else{
                throw new AssertionError("New list violated the list spec");
            }
        } else {
            list.add(value);
            return this;
        }
    }
    public Response setHeaders(Map<String, List<String>> headers) {
        this.headers = headers;
        return this;
    }

    public String getResultCharset() {
        return resultCharset;
    }

    public Response setResultCharset(String resultCharset) {
        this.resultCharset = resultCharset;
        return this;
    }

    @Override
    public String toString() {
        return body;
    }
}

ArrayListMultiMap代表一个key有多个值的map结构,在表达k=v1&k=v2和一个key对应多个header很有用。感谢L.cm。

package cn.zytx.common.utils;

import java.util.*;

/**
 * 自己实现的ArrayListMultimap 重复key的map.
 * 目前使用的地方 :
 * 1.使用监听的type,取出所有的监听器
 * 2.Http请求封装参数和header
 * @author L.cm email: [email protected] site:http://www.dreamlu.net date 2015年6月25日下午8:36:17
 */
public class ArrayListMultimap<K, V> {

    private transient final Map<K, List<V>> map;

    public ArrayListMultimap(){
        map = new HashMap<>();
    }
    public ArrayListMultimap(int capacity){
        map = new HashMap<>(capacity);
    }

    private static <V1> List<V1> createList(){
        return new ArrayList<>();
    }
    private static <V1> List<V1> createList(int capacity){
        return new ArrayList<>(capacity);
    }

    /**
     * put to ArrayListMultimap
     * @param key 键
     * @param value 值
     * @return boolean
     */
    public boolean put(K key, V value){
        List<V> list = map.get(key);
        if(list == null){
            list = createList();
            if(list.add(value)){
                map.put(key, list);
                return true;
            } else{
                throw new AssertionError("New list violated the list spec");
            }
        } else if(list.add(value)){
            return true;
        } else{
            return false;
        }
    }

    public Map<K, List<V>> getMap() {
        return map;
    }

    /**
     * get List by key
     * @param key 键
     * @return List
     */
    public List<V> get(K key){
        List<V> list = map.get(key);
        if(list == null){
            list = createList();
        }
        return list;
    }

    /**
     * 获取第一个,对于只有一个值的来说
     */
    public V getFirst(K key){
        List<V> list = map.get(key);
        return (null == list || list.size() == 0) ? null : list.get(0);
    }

    public Set<K> keySet(){
        return map.keySet();
    }
    /**
     * clear ArrayListMultimap
     */
    public void clear(){
        map.clear();
    }

    /**
     * 从普通map转换而来
     */
    public static <K1,V1> ArrayListMultimap<K1,V1> fromMap(Map<K1,V1> map){
        if(null == map){
            return null;
        }
        ArrayListMultimap<K1, V1> mapList = new ArrayListMultimap<>(map.size());
        map.forEach(mapList::put);
        return mapList;
    }
}

在此基础上再定义将结果转换为Bean的接口

package cn.zytx.common.http.withconverter;

import cn.zytx.common.http.HttpUtil;
import cn.zytx.common.http.basic.HttpClient;
import cn.zytx.common.http.smart.Request;
import cn.zytx.common.http.smart.Response;
import cn.zytx.common.http.smart.SmartHttpClient;

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

/**
 * @author xiongshiyan at 2018/7/16
 * 返回的结果值直接封装为bean
 */
public interface ConverterSmartHttpClient extends SmartHttpClient , Converter{
    ConverterSmartHttpClient setConverter(Converter converter);

    default <T> T get(String url, Map<String, String> params, Map<String, String> headers, int connectTimeout, int readTimeout, String resultCharset , Class<T> clazz) throws IOException{
        String result = get(url, params, headers, connectTimeout, readTimeout, resultCharset);
        return convert(result , clazz);
    }
    default <T> T get(String url, Map<String, String> params,Map<String,String> headers,int connectTimeout,int readTimeout , Class<T> clazz) throws IOException{
        return get(url, params, headers, connectTimeout, readTimeout , DEFAULT_CHARSET , clazz);
    }
    default <T> T get(String url, Map<String, String> params,Map<String,String> headers , String resultCharset, Class<T> clazz) throws IOException{
        return get(url,params,headers,CONNECT_TIMEOUT,READ_TIMEOUT,resultCharset , clazz);
    }
    default <T> T get(String url, Map<String, String> params,Map<String,String> headers, Class<T> clazz) throws IOException{
        return get(url,params,headers,CONNECT_TIMEOUT,READ_TIMEOUT , clazz);
    }
    default <T> T get(String url, Map<String, String> params,int connectTimeout,int readTimeout , String resultCharset, Class<T> clazz) throws IOException{
        return get(url,params,null,connectTimeout,readTimeout,resultCharset , clazz);
    }
    default <T> T get(String url, Map<String, String> params,int connectTimeout,int readTimeout, Class<T> clazz) throws IOException{
        return get(url,params,null,connectTimeout,readTimeout , clazz);
    }
    default <T> T get(String url,Map<String, String> params , String resultCharset, Class<T> clazz) throws IOException{
        return get(url,params,null,CONNECT_TIMEOUT,READ_TIMEOUT,resultCharset , clazz);
    }
    default <T> T get(String url,Map<String, String> params, Class<T> clazz) throws IOException{
        return get(url,params,null,CONNECT_TIMEOUT,READ_TIMEOUT , clazz);
    }
    default <T> T get(String url , String resultCharset, Class<T> clazz) throws IOException{
        return get(url,null,null,CONNECT_TIMEOUT,READ_TIMEOUT , resultCharset , clazz);
    }
    default <T> T get(String url, Class<T> clazz) throws IOException{
        return get(url,null,null,CONNECT_TIMEOUT,READ_TIMEOUT , clazz);
    }


    default <T> T post(String url, String body, String contentType, Map<String,String> headers, int connectTimeout, int readTimeout, String bodyCharset , String resultCharset , Class<T> clazz) throws IOException{
        String result = post(url, body, contentType, headers, connectTimeout, readTimeout, bodyCharset, resultCharset);
        return convert(result , clazz);
    }

    default <T> T post(String url, String body, String contentType,Map<String,String> headers,int connectTimeout,int readTimeout, Class<T> clazz) throws IOException{
        return post(url,body,contentType,headers,connectTimeout,readTimeout, DEFAULT_CHARSET,DEFAULT_CHARSET , clazz);
    }
    default <T> T post(String url, String body, String contentType,Map<String,String> headers, String bodyCharset , String resultCharset, Class<T> clazz) throws IOException{
        return post(url,body,contentType,headers,CONNECT_TIMEOUT,READ_TIMEOUT,bodyCharset,resultCharset , clazz);
    }
    default <T> T  post(String url, String body, String contentType,Map<String,String> headers, Class<T> clazz) throws IOException{
        return post(url,body,contentType,headers,CONNECT_TIMEOUT,READ_TIMEOUT , clazz);
    }
    default <T> T  post(String url, String body, String contentType,int connectTimeout,int readTimeout, String bodyCharset , String resultCharset, Class<T> clazz) throws IOException{
        return post(url,body,contentType,null,connectTimeout,readTimeout,bodyCharset,resultCharset , clazz);
    }
    default <T> T  post(String url, String body, String contentType,int connectTimeout,int readTimeout, Class<T> clazz) throws IOException{
        return post(url,body,contentType,null,connectTimeout,readTimeout , clazz);
    }
    /**
     * @see HttpClient#post(String, String, String, Map, int, int, String, String)
     */
    default <T> T  post(String url, String body, String contentType, String bodyCharset , String resultCharset, Class<T> clazz) throws IOException{
        return post(url,body,contentType,null,CONNECT_TIMEOUT,READ_TIMEOUT,bodyCharset,resultCharset , clazz);
    }
    default <T> T  post(String url, String body, String contentType, Class<T> clazz) throws IOException{
        return post(url,body,contentType,null,CONNECT_TIMEOUT,READ_TIMEOUT , clazz);
    }
    default <T> T  postJson(String url, String body, String bodyCharset , String resultCharset, Class<T> clazz) throws IOException{
        return post(url,body,JSON_WITH_DEFAULT_CHARSET,null,CONNECT_TIMEOUT,READ_TIMEOUT,bodyCharset,resultCharset , clazz);
    }
    default <T> T  postJson(String url, String body, Class<T> clazz) throws IOException{
        return post(url,body,JSON_WITH_DEFAULT_CHARSET,null,CONNECT_TIMEOUT,READ_TIMEOUT , clazz);
    }

    /**参数用 =和& 连接*/
    default <T> T  post(String url, Map<String, String> params,Map<String,String> headers, String bodyCharset , String resultCharset, Class<T> clazz) throws IOException{
        return post(url, HttpUtil.contactMap(params),FORM_URLENCODED_WITH_DEFAULT_CHARSET,headers,bodyCharset,resultCharset , clazz);
    }
    default <T> T  post(String url, Map<String, String> params,Map<String,String> headers, Class<T> clazz) throws IOException{
        return post(url, HttpUtil.contactMap(params),FORM_URLENCODED_WITH_DEFAULT_CHARSET,headers , clazz);
    }
    default <T> T  post(String url, Map<String, String> params, String bodyCharset , String resultCharset, Class<T> clazz) throws IOException{
        return post(url,HttpUtil.contactMap(params),FORM_URLENCODED_WITH_DEFAULT_CHARSET,null,bodyCharset,resultCharset , clazz);
    }
    default <T> T  post(String url, Map<String, String> params, Class<T> clazz) throws IOException{
        return post(url,HttpUtil.contactMap(params),FORM_URLENCODED_WITH_DEFAULT_CHARSET,null , clazz);
    }

    default <T> T  get(Request request , Class<T> clazz) throws IOException{
        Response response = get(request);
        return convert(response.getBody() , clazz);
    }
    default <T> T  post(Request request , Class<T> clazz) throws IOException{
        Response response = post(request);
        return convert(response.getBody() , clazz);
    }
}

该接口还实现了Converter接口,于是此接口中的所有方法就可以直接调用Converter接口的convert方法,Converter接口用于将字符串转换为Bean。

package cn.zytx.common.http.withconverter;

/**
 * @author xiongshiyan at 2018/7/16
 */
public interface Converter {
    /**
     * 将字符串[可能是Json、Xml或者其他格式]转为Bean
     * @param src 原字符串
     * @param clazz BeanClass
     * @param <R> Bean
     * @return R
     */
    <R> R convert(String src , Class<R> clazz);
}

猜你喜欢

转载自blog.csdn.net/xxssyyyyssxx/article/details/80715202