使用HttpClient做請求響應中轉

現網中有個應用A,之前一直是請求透傳訪問的,最近從安全方面考慮將該A應用不直接暴露給客戶端訪問,而是從有一定安全校驗機制的應用B做訪問入口,由B的後端將HTTP請求中轉到A,再將A的響應通過B輸出到客戶端。這種方案有兩個好處,1.可以利用應用B已有的安全校驗機制,而不需要應用A再複製一份安全校驗。2.原來在客戶端需要同時訪問應用A和應用B,這就涉及到瀏覽器的同源策略的安全性問題,所以在配置上必須讓應用A和應用B在同一個域名之下,經過這種改造就不存在這種問題了。

 

看起來功能實現很簡單,客戶端訪問應用B,應用B的後端用apache的httpclient訪問應用A,再將應用A的響應寫入B應用的響應中。HttpClient調用一般步驟:構造請求方法(HttpPost),構造請求參數內容,將請求參數塞入請求方法對象,發送請求,解析響應。代碼如下:

Java代碼  下載

  1. HttpClient httpclient = new DefaultHttpClient();  

  2. try {  

  3.     //new一個HttpPost  

  4.     HttpPost httppost = new HttpPost(url);  

  5.     if (paramMap != null) {  

  6.         //構造NameValuePair的內容塞到HttpPost中  

  7.         List<NameValuePair> formparams = new ArrayList<>();// 用於存放請求參數  

  8.         for (Map.Entry<String, Object> entry : paramMap.entrySet()) {  

  9.             String key = entry.getKey();  

  10.             Object value = entry.getValue();  

  11.   

  12.             if (value instanceof String) {  

  13.                 formparams.add(new BasicNameValuePair(key, String.valueOf(value)));  

  14.             } else if (value instanceof List) {  

  15.                 List<Object> objectList = (List) value;  

  16.                 if (CollectionUtils.isNotEmpty(objectList)) {  

  17.                     for (Object object : objectList) {  

  18.                         formparams.add(new BasicNameValuePair(key, String.valueOf(object)));  

  19.                     }  

  20.                 }  

  21.             }else if(value instanceof Number){  

  22.                 formparams.add(new BasicNameValuePair(key, String.valueOf(value)));  

  23.             }else{  

  24.                 throw new Exception("不支持該類型");  

  25.             }  

  26.   

  27.         }  

  28.         UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, encode);  

  29.         httppost.setEntity(entity);  

  30.     }  

  31.     httppost.setHeader("Connection""close");  

  32.     HttpParams params = httpclient.getParams();  

  33.     HttpConnectionParams.setConnectionTimeout(params, connectiontimeout);  

  34.     HttpConnectionParams.setSoTimeout(params, readtimeout);  

  35.     //發送post請求  

  36.     HttpResponse httpResponse = httpclient.execute(httppost);  

  37.     //解析響應內容,將輸出流寫入響應  

  38.     if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {  

  39.         HttpEntity httpEntity = httpResponse.getEntity();  

  40.         httpEntity.writeTo(response.getOutputStream());  

  41.     }  

  42.   

  43.     EntityUtils.consume(httpResponse.getEntity());  

  44. catch (Exception e) {  

  45.     e.printStackTrace();  

  46. finally {  

  47.     if (httpclient != null) {  

  48.         httpclient.getConnectionManager().shutdown();  

  49.     }  

  50. }  

就是這麼簡單一需求還是有坑,測試發現原來的導出文件請求通過這種流中轉之後文件名不顯示了。

 

這種情況一看就知道是響應頭中丟失了信息,通過HttpResponse能夠獲取到所有A響應的的Header,取出來塞到B的響應中不就行了?代碼如下:

Java代碼  下載

  1. HttpResponse httpResponse = httpclient.execute(httppost);// 發送post 請求  

  2. if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {  

  3.     HttpEntity httpEntity = httpResponse.getEntity();  

  4.     httpEntity.writeTo(response.getOutputStream());  

  5.     if (httpResponse.getFirstHeader("Content-Disposition") != null) {  

  6.         //文件導出時在響應中添加文件內容的響應header  

  7.         response.setHeader("Content-Disposition", httpResponse.getFirstHeader("Content-Disposition").getValue());  

  8.     }  

  9. }  

結果發現這樣寫卻並沒有放到響應頭報文中。

 

跟蹤源碼,調用response.setHeader方法最終會執行到具體web容器的setHeader方法,我本地跑的tomcat,所以最終執行的是org.apache.catalina.connector.ResponseFacade類的setHeader方法:

Java代碼  

  1. @Override  

  2. public void setHeader(String name, String value) {  

  3.   

  4.     if (isCommitted()) {  

  5.         return;  

  6.     }  

  7.   

  8.     response.setHeader(name, value);  

  9.   

  10. }  

原來在設置header之前會調用一下isCommitted方法判斷要輸出的內容是否已經提交:

Java代碼  下載

  1. @Override  

  2. public boolean isCommitted() {  

  3.   

  4.     if (response == null) {  

  5.         throw new IllegalStateException(  

  6.                         sm.getString("responseFacade.nullResponse"));  

  7.     }  

  8.   

  9.     return (response.isAppCommitted());  

  10. }  

Java代碼  

  1. /** 

  2.  * Application commit flag accessor. 

  3.  */  

  4. public boolean isAppCommitted() {  

  5.     return (this.appCommitted || isCommitted() || isSuspended()  

  6.             || ((getContentLength() > 0)  

  7.                 && (getContentWritten() >= getContentLength())));  

  8. }  

所以設置響應頭的代碼要在設置響應的輸出流之前調用,否則無效

Java代碼  下載

  1. if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {  

  2.     String headerName;  

  3.     for (Header header : httpResponse.getAllHeaders()) {  

  4.         headerName = header.getName();  

  5.         switch (headerName){  

  6.             //這裏根據需要自己添加需要輸出的響應頭  

  7.             case "Content-Disposition":  

  8.             case "Content-Type":  

  9.                 response.setHeader(headerName, header.getValue());  

  10.                 break;  

  11.         }  

  12.     }  

  13.   

  14.     HttpEntity httpEntity = httpResponse.getEntity();  

  15.     httpEntity.writeTo(response.getOutputStream());  

  16. }  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章