我們到底能走多遠系列(41)
扯淡:
好久沒總結點東西了,技術上沒什么總結,感覺做事空牢牢的。最近也比較疲憊。
分享些東西,造福全人類~
主題:
1,java模擬發起一個http請求
使用HttpURLConnection,可以通過setRequestProperty方法來設置http header的內容。
/** * post請求 * @param strUrl * @param content * @param charset * @return */ public static String sendPost(String strUrl, String content, String charset) { URL httpurl = null; HttpURLConnection httpConn = null; String returnStr = ""; PrintWriter outs = null; try { httpurl = new URL(strUrl); httpConn = (HttpURLConnection) httpurl.openConnection(); httpConn.setRequestMethod( "POST"); // 默認是post // 設置是否向httpUrlConnection輸出,因為這個是post請求,參數要放在 http正文內,因此需要設為true, 默認情況下是false; httpConn.setDoOutput( true); // 設置是否從httpUrlConnection讀入,默認情況下是true; httpConn.setDoInput( true); httpConn.setRequestProperty( "Content-Type", "text/xml"); outs = new PrintWriter(httpConn.getOutputStream()); outs.print(content); outs.flush(); outs.close(); // 字節流 讀取全部內容 包括換行符 returnStr = inputStreamToString(httpConn.getInputStream(), charset); } catch (Exception e) { log.error( "執行HTTP Post請求" + strUrl + "時,發生異常!" , e); if(outs != null){ outs.close(); outs = null; } return returnStr; } finally { if (httpConn != null) httpConn.disconnect(); if(outs != null){ outs.close(); outs = null; } } return returnStr; } public static String inputStreamToString(InputStream in,String encoding) throws Exception{ ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] data = new byte[ BUFFER_SIZE]; int count = -1; while((count = in.read(data,0, BUFFER_SIZE)) != -1) outStream.write(data, 0, count); in.close(); data = null; return new String(outStream.toByteArray(),encoding); }
在下面的代碼中,我們單獨分了一個inputStreamToString方法來讀取請求響應的內容。
這里使用了ByteArrayOutputStream來讀取,可以讀取response的body的完整內容,不會丟失換行,這個要注意一下。比如我們使用BufferedReader,代碼會類似於這樣:
String line, result = ""; BufferedReader in = new BufferedReader( new InputStreamReader(conn.getInputStream(), "utf-8" )); while ((line = in. readLine()) != null) { result += line + "\n"; } in.close();
為了不丟掉換行,需要自己拼接"\n",還有這地方還有個問題,那就是在循環中使用“+”來拼接字符串的問題,性能不行。參考:
參考
也可以使用HttpClient 來發送post請求,感受下:
public static String sendPost(String url, Map<String, String> params, String charset) { StringBuffer response = new StringBuffer(); HttpClient client = new HttpClient(); HttpMethod method = new PostMethod(url); method.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES); method.setRequestHeader("Cookie", "special-cookie=value"); method.setRequestHeader("", ""); // 設置Http Post數據 if (params != null) { HttpMethodParams p = new HttpMethodParams(); for (Map.Entry<String, String> entry : params.entrySet()) { p.setParameter(entry.getKey(), entry.getValue()); } method.setParams(p); } try { client.executeMethod(method); if (method.getStatusCode() == HttpStatus.SC_OK) { BufferedReader reader = new BufferedReader( new InputStreamReader(method.getResponseBodyAsStream(), charset)); String line; while ((line = reader.readLine()) != null) { response.append(line); } reader.close(); } } catch (IOException e) { log.error("執行HTTP Post請求" + url + "時,發生異常!", e); } finally { method.releaseConnection(); } return response.toString(); }
2,模擬登錄微信公眾平台
既然我們可以模擬post請求,那么理論上我們也可以模擬登錄,一般來說登錄的過程就是發起一個post請求,把賬號密碼發送給服務器,服務器驗證通過后,在resopnse里寫cookies,然后跳轉頁面,接下來和服務器的交互就要基於cookies了。
那么我們要做的就是發起一個請求,然后把成功返回的cookies保存起來,每次后續需要登錄狀態才能和服務器交互的操作都帶上cookies 。關於cookies
其實就是這么個過程:
client ---post請求---> server
client <--返回cookies--server
client -請求帶cookies--server
首先,登錄的請求是這樣的:
我們還可以注意到header中有一個
Referer, 也是很有意思的,它的意思是這個請求來自於哪里。這個可以做統計啊,限制啊什么的。雖然我們可以模擬,哈哈。但是很多正規的網站都會用這個東西。有興趣可以了解下~
代碼類似如下:
public final static String REFERER_H = "Referer"; private LoginResult _login(String username, String pwd) { LoginResult loginResult = new LoginResult(); try { PostMethod post = new PostMethod(LOGIN_URL); post.setRequestHeader("Referer", "https://mp.weixin.qq.com/"); post.setRequestHeader(USER_AGENT_H, USER_AGENT); NameValuePair[] params = new NameValuePair[] { new NameValuePair("username", username), new NameValuePair("pwd", DigestUtils.md5Hex(pwd .getBytes())), new NameValuePair("f", "json"), new NameValuePair("imagecode", "") }; post.setQueryString(params); int status = client.executeMethod(post); if (status == HttpStatus.SC_OK) { String ret = post.getResponseBodyAsString(); LoginJson retcode = JSON.parseObject(ret, LoginJson.class); // System.out.println(retcode.getRet()); if ((retcode.getBase_resp().getRet() == 302 || retcode .getBase_resp().getRet() == 0)) { loginResult.setCookie(client.getState().getCookies()); StringBuffer cookie = new StringBuffer(); // 處理cookies for (Cookie c : client.getState().getCookies()) { cookie.append(c.getName()).append("=") .append(c.getValue()).append(";"); cookiemap.put(c.getName(), c.getValue()); } loginResult.setCookiestr(cookie.toString()); loginResult.setToken(getToken(retcode.getRedirect_url())); loginResult.setLogin(true); return loginResult; }else{ loginResult.setLogin(false); return loginResult; } } } catch (Exception e) { String info = "【登錄失敗】【發生異常:" + e.getMessage() + "】"; System.err.println(info); log.debug(info); log.info(info); return loginResult; } return loginResult; }
全部代碼已經共享出來了:github 希望能幫到你~
3,重定向問題
面試中是不是曾經也被問到過:forword和redirect的區別。
網上的回答也有很多了,回過頭來再看看這個問題。其實forword,可以說是服務器發起一個請求,訪問自身應用下的資源。因為是服務器自己發起的,所以先前由客戶端傳遞過來的參數就可以保持住了,而且是把訪問的這個內部資源返回的內容作為客戶端最初請求的響應,所以瀏覽器的url不會變。
那么redirect的原理:其實是服務端在接到客戶端請求后,在響應中的header中加上了一個location的東西,這個東西的原理又是:
當瀏覽器接受到頭信息中的 Location: xxxx 后,就會自動跳轉到 xxxx 指向的URL地址,這點有點類似用 js 寫跳轉。但是這個跳轉只有瀏覽器知道,不管體內容里有沒有東西,用戶都看不到。例:header("Location: http://www.xker.com/");也就是說
redirect他的實現也依賴了瀏覽器的機制配合的。
那么這樣就明白了,先回一個帶有location的響應,然后瀏覽器自己發起一個新的向location下的值的請求。因為是瀏覽器自己發起的請求,所以第一次發起的請求中的參數就不能保持,而因為是一個新的請求,所以瀏覽器的url會改變。
還可以知道,在帶有location的響應中,HTTP STATUS CODE 還是302。
這也許是更深一點的理解吧~
讓我們繼續前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不會成功。
