HTTP的GET请求RUL保留字处理方式(多个解决方案)

阅读更多

背景:

最近花了一天在处理一个生产环境问题,

客户端(发送数据):通过HTTP的GET请求,传输参数中带有“+”加号。

服务端(接收数据):“+”加号变为空格。

因为是签名数据,导致服务端验证签名不通过,算比较严重的问题。

 

解决问题示例(多个解决方案):

示例1(请求url的参数采用直接拼装的方式)(失败):

package com.qhfax.test;

import com.qhfax.common.util.HttpClientUtil;

public class HttpGetTest1 {

    public static void main(String[] args) throws Exception {
        //签名
        String sign = "abcde+fghij";
        //请求的服务地址
        String serviceUrl = "http://localhost:8080/qhfaxWeb/httpGetTest/paramTest";//请求路径
        //请求参数
        String queryString = "?sign="+sign;
        //URI编码处理
        String getUrl = serviceUrl + queryString;
        String response = HttpClientUtil.get(getUrl);
        System.out.println(response);
    }
}

服务端接收的的值为:abcde fghij

服务端返回:false

 

示例2(使用URIUtil.encodeQuery方法对请求参数进行编码)(失败)

package com.qhfax.test;

import org.apache.commons.httpclient.util.URIUtil;

import com.qhfax.common.util.HttpClientUtil;

public class HttpGetTest2 {

    public static void main(String[] args) throws Exception {
        //签名
        String sign = "abcde+fghij";
        //请求的服务地址
        String serviceUrl = "http://localhost:8080/qhfaxWeb/httpGetTest/paramTest";//请求路径
        //请求参数
        String queryString = "?sign="+sign;
        //URI编码处理
        String getUrl = serviceUrl + URIUtil.encodeQuery(queryString);
        String response = HttpClientUtil.get(getUrl);
        System.out.println(response);
    }
}

 

服务端接收的的值为:abcde fghij

服务端返回:false

 

示例3(对URL保留字符进行ASCII码转换,把“+”替换为“%2B”)(成功)

package com.qhfax.test;

import com.qhfax.common.util.HttpClientUtil;

public class HttpGetTest3 {

    public static void main(String[] args) throws Exception {
        //签名
        String sign = "abcde+fghij";
        //把“+”替换为“%2B”
        sign = sign.replaceAll("\\+", "%2B");
        //请求的服务地址
        String serviceUrl = "http://localhost:8080/qhfaxWeb/httpGetTest/paramTest";//请求路径
        //请求参数
        String queryString = "?sign="+sign;
        //URI编码处理
        String getUrl = serviceUrl + queryString;
        String response = HttpClientUtil.get(getUrl);
        System.out.println(response);
    }
}

服务端接收的的值为:abcde+fghij

服务端返回:true

 

示例4(基于示例3,使用java自带的URLEncoder.encode方法对参数进行编码)(成功)

package com.qhfax.test;

import com.qhfax.common.util.HttpClientUtil;

public class HttpGetTest4 {

    public static void main(String[] args) throws Exception {
        //签名
        String sign = "abcde+fghij";
        //使用java自带的URLEncoder.encode方法对参数进行编码
        sign = java.net.URLEncoder.encode(sign);
        //请求的服务地址
        String serviceUrl = "http://localhost:8080/qhfaxWeb/httpGetTest/paramTest";//请求路径
        //请求参数
        String queryString = "?sign="+sign;
        //URI编码处理
        String getUrl = serviceUrl + queryString;
        String response = HttpClientUtil.get(getUrl);
        System.out.println(response);
    }
}

服务端接收的的值为:abcde+fghij

服务端返回:true

 

示例5(基于示例3、4,使用httpClient包中的UrlEncodedFormEntity类,详见上面的HttpClientUtil工具类)(最终版本)(成功)

package com.qhfax.test;

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

import com.qhfax.common.util.HttpClientUtil;

public class HttpGetTest5 {

    public static void main(String[] args) throws Exception {
        //签名
        String sign = "abcde+fghij";
        //请求的服务地址
        String serviceUrl = "http://localhost:8080/qhfaxWeb/httpGetTest/paramTest";//请求路径
        //请求参数
        Map params = new HashMap();
        params.put("sign", sign);
        String response = HttpClientUtil.get(serviceUrl, params);
        System.out.println(response);
    }
}

服务端接收的的值为:abcde+fghij

服务端返回:true

 

示例6(换一种方式,也是Httpclientjar包中的类,使用Request.Get)(成功)

 

package com.qhfax.test;

import org.apache.http.client.fluent.Request;
import org.apache.http.client.utils.URIBuilder;

public class HttpGetTest6 {

    public static void main(String[] args) throws Exception {
        //签名
        String sign = "abcde+fghij";
        //请求的服务地址
        String serviceUrl = "http://localhost:8080/qhfaxWeb/httpGetTest/paramTest";//请求路径

        String result = Request.Get(
                new URIBuilder(serviceUrl)
                    .addParameter("sign", sign)
                    .build())
            .connectTimeout(5000)
            .socketTimeout(5000).execute()
            .returnContent().asString();
        System.out.println(result);
    }
}

服务端接收的的值为:abcde+fghij

服务端返回:true

 

总结:

1、找到问题:

根据示例1中问题,HTTP的GET请求URL中有包括“! * ' ( ) ; : @ & =+ $ , / ? # [ ]”的保留字符,需要对它们进行转码,这是解决问题思路的第一步,找到原因。

2、验证解决方案:

根据示例3中的处理方式,将把“+”替换为“%2B”,验证处理方法是正确的。这是解决问题思路的第二步,尝试方案,验证能否解决问题。

3、完善解决方案:

示例4、5、6是基于示例3的完善,利用现有的工具类,更好更全面的处理问题。这是解决问题思路的第三步,完善解决方案。

(备注:示例2中的URIUtil.encodeQuery是有编码效果的,只是好像对“+”不会编码,这个点在我处理时有点误导了我的思路)

 

演示准备前需准备的代码(注:SpringMVC框架环境需自己搭建,httpclient的jar版本为4.4.1):

客户端:

HttpClientUtil工具类代码如下:

package com.qhfax.common.util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Description: 使用httpclient4.0以上组件
 * @author : huangaming
 * @date : 2017年4月6日 上午11:49:48
 */
@SuppressWarnings("deprecation")
public class HttpClientUtil {

    private static Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);

    public static String postJsonString(String uri, String jsonStr) {
        String result = "";
        Charset charset = Charset.forName("UTF-8");
        // 实例化http客户端
        HttpClient httpClient = HttpClientBuilder.create().build();
        HttpPost post = null;
        try {
            post = new HttpPost(uri);
            StringEntity stringEntity = new StringEntity(jsonStr, ContentType.create("application/json", charset));
            // 实例化post提交方式
            post.addHeader(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
            // 将参数加入post请求体中
            post.setEntity(stringEntity);
            // 执行post请求并得到返回对象 [ 到这一步我们的请求就开始了 ]
            HttpResponse resp = httpClient.execute(post);
            // 解析返回请求结果
            HttpEntity entity = resp.getEntity();
            result = IOUtils.toString(entity.getContent(), charset);
            logger.info("[postJsonString response:{}]", result);
            // 输出结果
        } catch (Exception exception) {
            logger.error("postJsonString exception", exception);
        } finally {
            if (post != null) {
                post.releaseConnection();
            }
        }
        return result;
    }
    
    public static final int connTimeout=10000;//连接超时参数
    public static final int readTimeout=10000;//读取超时参数
    public static final String charset="UTF-8";//字符编码
    private static HttpClient client = null;
    
    static {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(128);
        cm.setDefaultMaxPerRoute(128);
        client = HttpClients.custom().setConnectionManager(cm).build();
    }
    
    public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
        return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }
    
    public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
        return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }
    
    public static String postParameters(String url, Map params) throws ConnectTimeoutException,  
        SocketTimeoutException, Exception {
         return postForm(url, params, null, connTimeout, readTimeout);
    }
    
    public static String postParameters(String url, Map params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,  
        SocketTimeoutException, Exception {
         return postForm(url, params, null, connTimeout, readTimeout);
    }
      
    public static String get(String url) throws Exception {  
        return get(url, charset, connTimeout, readTimeout);  
    }
    
    public static String get(String url, String charset) throws Exception {  
        return get(url, charset, connTimeout, readTimeout);  
    }
    
    public static String get(String url, Map params) throws Exception {  
        return get(url, params, charset, connTimeout, readTimeout);  
    }
    
    /** 
     * 发送一个 Post 请求, 使用指定的字符集编码. 
     *  
     * @param url 
     * @param body RequestBody 
     * @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
     * @param charset 编码 
     * @param connTimeout 建立链接超时时间,毫秒. 
     * @param readTimeout 响应超时时间,毫秒. 
     * @return ResponseBody, 使用指定的字符集编码. 
     * @throws ConnectTimeoutException 建立链接超时异常 
     * @throws SocketTimeoutException  响应超时 
     * @throws Exception 
     */  
    public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout) 
            throws ConnectTimeoutException, SocketTimeoutException, Exception {
        HttpClient client = null;
        HttpPost post = new HttpPost(url);
        String result = "";
        try {
            if (StringUtils.isNotBlank(body)) {
                HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
                
                post.setEntity(entity);
            }
            // 设置参数
            Builder customReqConf = RequestConfig.custom();
            if (connTimeout != null) {
                customReqConf.setConnectTimeout(connTimeout);
            }
            if (readTimeout != null) {
                customReqConf.setSocketTimeout(readTimeout);
            }
            post.setConfig(customReqConf.build());

            HttpResponse res;
            if (url.startsWith("https")) {
                // 执行 Https 请求.
                client = createSSLInsecureClient();
                res = client.execute(post);
            } else {
                // 执行 Http 请求.
                client = HttpClientUtil.client;
                res = client.execute(post);
            }
            result = IOUtils.toString(res.getEntity().getContent(), charset);
        } finally {
            post.releaseConnection();
            if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
                ((CloseableHttpClient) client).close();
            }
        }
        return result;
    }  
    
    
    /** 
     * 提交form表单 
     *  
     * @param url 
     * @param params 
     * @param connTimeout 
     * @param readTimeout 
     * @return 
     * @throws ConnectTimeoutException 
     * @throws SocketTimeoutException 
     * @throws Exception 
     */  
    public static String postForm(String url, Map params, Map headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,  
            SocketTimeoutException, Exception {
        HttpClient client = null;  
        HttpPost post = new HttpPost(url);  
        try {  
            if (params != null && !params.isEmpty()) {  
                List formParams = new ArrayList();  
                Set> entrySet = params.entrySet();  
                for (Entry entry : entrySet) {  
                    formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));  
                }  
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);  
                post.setEntity(entity);  
            }
            
            if (headers != null && !headers.isEmpty()) {  
                for (Entry entry : headers.entrySet()) {  
                    post.addHeader(entry.getKey(), entry.getValue());  
                }  
            }  
            // 设置参数  
            Builder customReqConf = RequestConfig.custom();  
            if (connTimeout != null) {  
                customReqConf.setConnectTimeout(connTimeout);  
            }  
            if (readTimeout != null) {  
                customReqConf.setSocketTimeout(readTimeout);  
            }  
            post.setConfig(customReqConf.build());  
            HttpResponse res = null;  
            if (url.startsWith("https")) {  
                // 执行 Https 请求.  
                client = createSSLInsecureClient();  
                res = client.execute(post);  
            } else {  
                // 执行 Http 请求.  
                client = HttpClientUtil.client;  
                res = client.execute(post);  
            }  
            return IOUtils.toString(res.getEntity().getContent(), "UTF-8");  
        } finally {  
            post.releaseConnection();  
            if (url.startsWith("https") && client != null  
                    && client instanceof CloseableHttpClient) {  
                ((CloseableHttpClient) client).close();  
            }  
        }  
    } 
    
    /** 
     * 发送一个 GET 请求 
     *  
     * @param url 
     * @param charset 
     * @param connTimeout  建立链接超时时间,毫秒. 
     * @param readTimeout  响应超时时间,毫秒. 
     * @return 
     * @throws ConnectTimeoutException   建立链接超时 
     * @throws SocketTimeoutException   响应超时 
     * @throws Exception 
     */  
    public static String get(String url, String charset, Integer connTimeout,Integer readTimeout) 
            throws ConnectTimeoutException,SocketTimeoutException, Exception {
        HttpClient client = null;  
        HttpGet get = new HttpGet(url);  
        String result = "";  
        try {  
            // 设置参数  
            Builder customReqConf = RequestConfig.custom();  
            if (connTimeout != null) {  
                customReqConf.setConnectTimeout(connTimeout);  
            }  
            if (readTimeout != null) {  
                customReqConf.setSocketTimeout(readTimeout);  
            }  
            get.setConfig(customReqConf.build());  
  
            HttpResponse res = null;  
            if (url.startsWith("https")) { 
                logger.info("httpClientUtil|get|执行https的get请求|开始");
                // 执行 Https 请求.  
                client = createSSLInsecureClient();  
                res = client.execute(get);  
                logger.info("httpClientUtil|get|执行https的get请求|结束");
            } else {  
                logger.info("httpClientUtil|get|执行http的get请求|开始");
                // 执行 Http 请求.  
                client = HttpClientUtil.client;  
                res = client.execute(get);  
                logger.info("httpClientUtil|get|执行http的get请求|结束");
            }  
  
            result = IOUtils.toString(res.getEntity().getContent(), charset);  
        } finally {  
            get.releaseConnection();  
            if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {  
                ((CloseableHttpClient) client).close();  
            }  
        }  
        return result;  
    }  
    
    
    /** 
     * 从 response 里获取 charset 
     *  
     * @param ressponse 
     * @return 
     */  
    @SuppressWarnings("unused")  
    private static String getCharsetFromResponse(HttpResponse ressponse) {  
        // Content-Type:text/html; charset=GBK  
        if (ressponse.getEntity() != null  && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {  
            String contentType = ressponse.getEntity().getContentType().getValue();  
            if (contentType.contains("charset=")) {  
                return contentType.substring(contentType.indexOf("charset=") + 8);  
            }  
        }  
        return null;  
    }  
    
    
    
    /**
     * 创建 SSL连接
     * @return
     * @throws GeneralSecurityException
     */
    private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
        try {
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                        public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
                            return true;
                        }
                    }).build();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {
                        @Override
                        public boolean verify(String arg0, SSLSession arg1) {
                            return true;
                        }
                        @Override
                        public void verify(String host, SSLSocket ssl)
                                throws IOException {
                        }
                        @Override
                        public void verify(String host, X509Certificate cert)
                                throws SSLException {
                        }
                        @Override
                        public void verify(String host, String[] cns,
                                String[] subjectAlts) throws SSLException {
                        }

                    });
            return HttpClients.custom().setSSLSocketFactory(sslsf).build();
        } catch (GeneralSecurityException e) {
            throw e;
        }
    }
    
    /**
     * @param url http://taobao.com/test.action
     * @param params 参数,编码之前的参数
     * @return
     * @throws IOException 
     * @throws UnsupportedEncodingException 
     * @throws ParseException 
     * @throws GeneralSecurityException 
     */
    public static String get(String url, Map params,String charset, Integer connTimeout,Integer readTimeout) throws ParseException, UnsupportedEncodingException, IOException, GeneralSecurityException {
        HttpClient client = null;
        if(StringUtils.isBlank(url)){
            return null;
        }
        if(params != null && !params.isEmpty()){
            List pairs = new ArrayList(params.size());
            for(Map.Entry entry : params.entrySet()){
                String value = entry.getValue();
                if(value != null){
                    pairs.add(new BasicNameValuePair(entry.getKey(),value));
                }
            }
            url += "?" + EntityUtils.toString(new UrlEncodedFormEntity(pairs, charset));
        }
        HttpGet httpget = new HttpGet(url);
        CloseableHttpResponse response = null;
        // 设置参数  
        Builder customReqConf = RequestConfig.custom();  
        if (connTimeout != null) {  
            customReqConf.setConnectTimeout(connTimeout);  
        }  
        if (readTimeout != null) {  
            customReqConf.setSocketTimeout(readTimeout);  
        }  
        httpget.setConfig(customReqConf.build());
        if (url.startsWith("https")) { 
            logger.info("httpClientUtil|get|执行https的get请求|开始");
            // 执行 Https 请求.  
            client = createSSLInsecureClient();  
            response = (CloseableHttpResponse) client.execute(httpget);  
            logger.info("httpClientUtil|get|执行https的get请求|结束");
        } else {  
            logger.info("httpClientUtil|get|执行http的get请求|开始");
            // 执行 Http 请求.  
            client = HttpClientUtil.client;  
            response = (CloseableHttpResponse) client.execute(httpget);  
            logger.info("httpClientUtil|get|执行http的get请求|结束");
        }
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != 200) {
            httpget.abort();
            throw new RuntimeException("HttpClient,error status code :" + statusCode);
        }
        HttpEntity entity = response.getEntity();
        String result = null;
        if (entity != null) {
                result = EntityUtils.toString(entity, "utf-8");
        }
        EntityUtils.consume(entity);
        response.close();
        return result;
    }
}

服务端

HttpGetTestController代码如下:

package com.qhfax.controller.test;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 
 * HttpGet测试Controller
 * 
 * @author zhanghaitao
 *
 */
@Controller
@RequestMapping(value = "/httpGetTest")
public class HttpGetTestController {
	
    /**
     * 参数测试
     * 
     * @param request 请求
     * @param session 会话
     * @return
     */
    @RequestMapping(value = "/paramTest", method = RequestMethod.GET)
    @ResponseBody
    public String paramTest(HttpServletRequest request, HttpSession session) {
        //获取签名
        String sign = request.getParameter("sign");
        System.out.println(sign);
        
        //原签名
        String oldSign = "abcde+fghij";
        //验证服务端接收到的签名是否与客户端一致
        boolean isEqual = oldSign.equals(sign);
        
        return isEqual+"";
    }
}

 

 

你可能感兴趣的:(http,url,加号,保留字,编码)