在开发工程中,我们经常会遇到和其它第三方厂家有接口数据来往的情况。这是一次我们作为客户端
去获取另一个厂家推流的直播地址的接口,这个接口情况有些不一样,我们需要在一个接口中封装两次http请求,大概场景是这样的:
第一次调用方式是POST,第二次是Get,第一次调用会获取到一个地址,返回结果类似这样的 {“resultcode”:”0”,”desc”:”配置url成功”,”playurl”:” http://aep.cmvideo.cn:1100/pushUserInfo/v1/7+MbU0JGqdzxR4r9WppYdwgiEBj1vS3sjmIJGKzyd1Uo7Aiw8n5TGdmmh+6t7v1caKu15I+WJKsQRZzQ3rtT519E0R9pO4XHygB+bx0irIFIhuLVREDEOS0+OKpwcFWrIVRTurT8nTEdB8LACa5KifWfUPB6Emm5grPgROocpTZIWoo7c78ezILqwAMZ4flghHLSoucOW6S/OO86fUfNxg==/3006686669_52.mp4.m3u8?ec=1/type=aepadapter/fixed/ip/hls/config
2016-8-25 18-441”},这里获取到接口返回一个带参数的地址,这里注意一下,这个地址里有些特殊符号,如:下划线、等号之类的等等。我们还需要将返回的这个带参数的URL地址作为第二个请求的URL,并携带上第二个请求播放接口要求的请求参数使用get方式调用接口请求从而获取到一个重定向的地址,请求地址类似这样的:http://aep.sdp.com/pushUserInfo/v1/7Jo8ty86AUKmr6fTuRYQ6Ylf+YaMXYF6GTPPWPchbvvIMG9MADvMOQtk9pOF0VjDSBuMxDZ4TPap2G5uhoCuurjoLwyNhbaPxV0T4XJFUK4CRUlwskU5eA4ImCjdv06plhYlvEeHW+IEASnEbvRhnuwJSXJUDfm8XTScBLnyuDEyTmPlrZTRwoUqFH7Bri8i0pW1M0OlpjB8fzWjTudRiA==/3006686669_52.mp4.m3u8?ec=1/type=aepadapter/fixed/ip/hls/config0&Id_type=1&userid=15000000000&ipaddress=10.1.1.1 HTTP/1.1,http返回码应该是302,这个地址才是真正的播放地址,其实是一个m3u8的播放流。对方通过这样的一个地址进行推流播放。很快这个接口封装好了,自己写了个单元测试进行调用测试;在做单元测试的过程中发现最终的返回码是200,而且响应的报文是空的,什么信息都没有。正常应该是返回302,
单独测试第一个接口的时候响应是正常的,测试第二个的时候出问题了,反复确认封装的请求参数是没有问题的,开始怀疑是对方响应有问题。后来我单独用第一个返回的地址带上参数在浏览器中请求是可以的,于是知道大概是URL被转码了的原因,因为第一个接口返回的地址是带了参数的,如上面参数里有很多特殊字符。我在请求的时候自己封装了一个httpclient工具类,里面有封装的带参数的get方法是这样写的:
public static String httpGetRequest(String url, Map<String, Object> params){
String result=EMPTY_STR;
URIBuilder ub = new URIBuilder();
ub.setPath(url);
ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
ub.setParameters(pairs);
try{
HttpGet httpGet = new HttpGet(ub.build());
result = getResult(httpGet);
}catch(URISyntaxException e){
e.printStackTrace();
}
return result;
}
这里在构造请求时使用的是 Java.NET.URI 来构造的,而经过查询,从jdk的一个bug回复中知道:
* RFC 952 disallows _ underscores in hostnames. So, this is not a bug.
RFC 952 说明了java.net.URI 的域名只能由 字母 (A-Z), 数字(0-9), 减号 (-), 和 点 (.) 组成。也就是说 java.Net.URI 验证了 hostname。同时也看到了在 java.net.URL 中不会做这个验证。所以问题找到了,所以我们把这个方法换成其它方式来构造就正常返回了。
private static String createParamUrl(String url, Map<String, Object> params) {
Iterator<String> it = params.keySet().iterator();
StringBuilder sb = new StringBuilder();
boolean isIncludeQuestionMark =url.contains("?");
if (!isIncludeQuestionMark) {
sb.append("?");
}
while (it.hasNext()) {
String key = it.next();
String value = (String) params.get(key);
sb.append("&");
sb.append(key);
sb.append("=");
sb.append(value);
}
url += sb.toString();
return url;
}
赞同提交bug的网友的意见,这样会隐藏很多的坑。为什么一个验证了域名一个却不做验证。我们也看到bug提交之后得到的回复是不是一个bug。
最后附上封装的httpclient代码一份:
package com.richinfo.util.http;
import java.util.Map;
import java.util.Iterator;
import java.io.IOException;
import java.util.ArrayList;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import com.richinfo.util.json.JsonUtil;
import org.apache.http.util.EntityUtils;
import org.apache.http.entity.StringEntity;
import java.io.UnsupportedEncodingException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.client.config.RequestConfig;
import java.nio.charset.UnsupportedCharsetException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
/**
*
*function: APACHE HTTP-CLIENT 封装工具
*
*/
public class HttpClientUtil {
private static PoolingHttpClientConnectionManager cm;
private static String EMPTY_STR = "";
private static String UTF_8 = "UTF-8";
private static final int timeout =15000;
private static final boolean defaultRedirectsEnabled = false;
// 整个连接池最大连接数
private static final int maxTotal =200;
// 每路由最大连接数,默认值是2
private static final int defaultMaxPerRoute=maxTotal;
/**
* function:发送get请求
* @param url 请求地址
* @return String
*/
public static String httpGetRequest(String url) {
HttpGet httpGet = new HttpGet(url);
return getResult(httpGet);
}
/**
* function:发送get请求
* @param url 请求地址
* @param headers 头信息
* @return String
*/
public static String httpGetRequestWithHeaders(String url, Map<String, Object> headers){
HttpGet httpGet = new HttpGet(url);
for (Map.Entry<String, Object> param : headers.entrySet()) {
httpGet.addHeader(param.getKey(), String.valueOf(param.getValue()));
}
return getResult(httpGet);
}
/**
* function:发送get请求
* @param url 请求地址
* @param headers 头信息
* @param params 参数
* @return String
*/
public static String httpGetRequest(String url, Map<String, Object> headers, Map<String, Object> params){
String result=EMPTY_STR;
HttpGet httpGet = new HttpGet(createParamUrl(url,params));
for (Map.Entry<String, Object> param : headers.entrySet()) {
httpGet.addHeader(param.getKey(), String.valueOf(param.getValue()));
}
result=getResult(httpGet);
return result;
}
/**
* function:发送get请求
* @param url 请求地址
* @param headers 头信息
* @return String
*/
public static String httpGetRequestWithParams(String url, Map<String, Object> params){
HttpGet httpGet = new HttpGet(createParamUrl(url,params));
return getResult(httpGet);
}
/**
* function:创建带参数的URL
* @param url 无参URL
* @param params 参数
* @return String 带参数URL
*/
private static String createParamUrl(String url, Map<String, Object> params) {
Iterator<String> it = params.keySet().iterator();
StringBuilder sb = new StringBuilder();
boolean isIncludeQuestionMark =url.contains("?");
if (!isIncludeQuestionMark) {
sb.append("?");
}
while (it.hasNext()) {
String key = it.next();
String value = (String) params.get(key);
sb.append("&");
sb.append(key);
sb.append("=");
sb.append(value);
}
url += sb.toString();
return url;
}
/**
* function:post请求访问
* @param url 请求地址
* @return String
*/
public static String httpPostRequest(String url) {
HttpPost httpPost = new HttpPost(url);
return getResult(httpPost);
}
/**
* function:post请求访问
* @param url 地址
* @param params 参数
* @return String
*
*/
public static String httpPostRequest(String url, Map<String, Object> params){
HttpPost httpPost = new HttpPost(url);
ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
try {
httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return getResult(httpPost);
}
/**
* function:post请求访问
* @param url 地址
* @param headers 头信息
* @param params 参数
* @return String
*
*/
public static String httpPostRequest(String url, Map<String, Object> headers, Map<String, Object> params) {
HttpPost httpPost = new HttpPost(url);
for (Map.Entry<String, Object> headerParam : headers.entrySet()) {
httpPost.addHeader(headerParam.getKey(), String.valueOf(headerParam.getValue()));
}
ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
try {
httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return getResult(httpPost);
}
/**
* function:post以JSON格式发送
* @param url 地址
* @param headers 头信息
* @param params 参数
* @return String
*
*/
public static String httpPostRequestByJson(String url, Map<String, Object> headers, Map<String, Object> params) {
HttpPost httpPost = new HttpPost(url);
for (Map.Entry<String, Object> headerParam : headers.entrySet()) {
httpPost.addHeader(headerParam.getKey(), String.valueOf(headerParam.getValue()));
}
try {
String json = JsonUtil.readMaptoJson(params);
httpPost.setEntity(new StringEntity(json, "UTF-8"));
} catch (UnsupportedCharsetException e) {
e.printStackTrace();
}
return getResult(httpPost);
}
/**
* function:把参数转换为名值对数组
* @param params 参数
* @return ArrayList<NameValuePair>
*/
private static ArrayList<NameValuePair> covertParams2NVPS(Map<String, Object> params) {
ArrayList<NameValuePair> pairs = new ArrayList<NameValuePair>();
for (Map.Entry<String, Object> param : params.entrySet()) {
pairs.add(new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue())));
}
return pairs;
}
/**
* 执行 HTTP请求
* @param request
* @return String 若重定向返回重定向地址
*/
private static String getResult(HttpRequestBase request) {
String result=EMPTY_STR;
request.setConfig(createConfig(timeout, defaultRedirectsEnabled));
CloseableHttpClient httpClient = getHttpClient();
try {
CloseableHttpResponse response = httpClient.execute(request);
if (isRedirected(response)){
result=getRedirectedUrl(response);
}else{
result=getEntityData(response);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* function:创建HTTP请求配置
* @param timeout 超时
* @param redirectsEnabled 是否开启重定向
* @return RequestConfig
*/
private static RequestConfig createConfig(int timeout, boolean redirectsEnabled) {
return RequestConfig.custom()
.setSocketTimeout(timeout)
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setRedirectsEnabled(redirectsEnabled)
.build();
}
/**
* 通过连接池获取HttpClient
*
* @return
*/
private static CloseableHttpClient getHttpClient() {
init();
return HttpClients.custom().setConnectionManager(cm).build();
}
/**
*
* function:初始化方法
*
*/
private static void init() {
if (cm == null) {
cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(maxTotal);
cm.setDefaultMaxPerRoute(defaultMaxPerRoute);
}
}
/**
* function:判断发送请求是否重定向跳转过
* @param response 请求响应
* @return boolean
*/
private static boolean isRedirected(CloseableHttpResponse response){
int statusCode = response.getStatusLine().getStatusCode();
return statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||statusCode == HttpStatus.SC_MOVED_TEMPORARILY;
}
/**
* function:获得重定向跳转地址
* @param response 请求响应
* @return String 重定向地址
*/
private static String getRedirectedUrl(CloseableHttpResponse response){
String result = EMPTY_STR;
Header[] hs = response.getHeaders("Location");
if(hs.length>0){
result = hs[0].getValue();
}
return result;
}
/**
* function:获得响应实体信息
* @param response 请求响应
* @return String 消息实体信息
* @throws ParseException
* @throws IOException
*/
private static String getEntityData(CloseableHttpResponse response) throws ParseException, IOException {
String result = EMPTY_STR;
HttpEntity entity = response.getEntity();
if (entity != null) {
result = EntityUtils.toString(entity);
response.close();
}
return result;
}
}