在开发工程中,咱们常常会遇到和其它第三方厂家有接口数据来往的状况。这是一次咱们做为客户端
去获取另外一个厂家推流的直播地址的接口,这个接口状况有些不同,咱们须要在一个接口中封装两次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方法是这样写的:java
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 中不会作这个验证。因此问题找到了,因此咱们把这个方法换成其它方式来构造就正常返回了。apache
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。json
最后附上封装的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; } }