在開發工程中,咱們常常會遇到和其它第三方廠家有接口數據來往的狀況。這是一次咱們做爲客戶端
去獲取另外一個廠家推流的直播地址的接口,這個接口狀況有些不同,咱們須要在一個接口中封裝兩次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; } }