http長連接與短連接
HTTP協議與TCP/IP協議的關係
HTTP的長連接和短連接本質上是TCP長連接和短連接。HTTP屬於應用層協議,在傳輸層使用TCP協議,在網絡層使用IP協議。IP協議主要解決網絡路由和尋址問題,TCP協議主要解決如何在IP層之上可靠的傳遞數據包,使在網絡上的另一端收到發端發出的所有包,並且順序與發出順序一致。TCP有可靠,面向連接的特點。
如何理解HTTP協議是無狀態的
HTTP協議是無狀態的,指的是協議對於事務處理沒有記憶能力,服務器不知道客戶端是什麼狀態。也就是說,打開一個服務器上的網頁和你之前打開這個服務器上的網頁之間沒有任何聯繫。HTTP是一個無狀態的面向連接的協議,無狀態不代表HTTP不能保持TCP連接,更不能代表HTTP使用的是UDP協議(無連接)。
什麼是長連接、短連接?
在HTTP/1.0中,默認使用的是短連接。也就是說,瀏覽器和服務器每進行一次HTTP操作,就建立一次連接,但任務結束就中斷連接。如果客戶端瀏覽器訪問的某個HTML或其他類型的 Web頁中包含有其他的Web資源,如JavaScript文件、圖像文件、CSS文件等;當瀏覽器每遇到這樣一個Web資源,就會建立一個HTTP會話。
但從 HTTP/1.1起,默認使用長連接,用以保持連接特性。使用長連接的HTTP協議,會在響應頭有加入這行代碼:Connection:keep-alive
在使用長連接的情況下,當一個網頁打開完成後,客戶端和服務器之間用於傳輸HTTP數據的 TCP連接不會關閉,如果客戶端再次訪問這個服務器上的網頁,會繼續使用這一條已經建立的連接。Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件(如Apache)中設定這個時間。實現長連接要客戶端和服務端都支持長連接。
HTTP協議的長連接和短連接,實質上是TCP協議的長連接和短連接。
TCP連接
當網絡通信時採用TCP協議時,在真正的讀寫操作之前,server與client之間必須建立一個連接,當讀寫操作完成後,雙方不再需要這個連接 時它們可以釋放這個連接,連接的建立是需要三次握手的,而釋放則需要4次握手,所以說每個連接的建立都是需要資源消耗和時間消耗的
經典的三次握手示意圖:
經典的四次握手關閉圖:
TCP短連接
我們模擬一下TCP短連接的情況,client向server發起連接請求,server接到請求,然後雙方建立連接。client向server 發送消息,server迴應client,然後一次讀寫就完成了,這時候雙方任何一個都可以發起close操作,不過一般都是client先發起 close操作。爲什麼呢,一般的server不會回覆完client後立即關閉連接的,當然不排除有特殊的情況。從上面的描述看,短連接一般只會在 client/server間傳遞一次讀寫操作
短連接的優點是:管理起來比較簡單,存在的連接都是有用的連接,不需要額外的控制手段。
TCP長連接
接下來我們再模擬一下長連接的情況,client向server發起連接,server接受client連接,雙方建立連接。Client與server完成一次讀寫之後,它們之間的連接並不會主動關閉,後續的讀寫操作會繼續使用這個連接。
首先說一下TCP/IP詳解上講到的TCP保活功能,保活功能主要爲服務器應用提供,服務器應用希望知道客戶主機是否崩潰,從而可以代表客戶使用資源。如果客戶已經消失,使得服務器上保留一個半開放的連接,而服務器又在等待來自客戶端的數據,則服務器將應遠等待客戶端的數據,保活功能就是試圖在服務 器端檢測到這種半開放的連接。
如果一個給定的連接在兩小時內沒有任何的動作,則服務器就向客戶發一個探測報文段,客戶主機必須處於以下4個狀態之一:
客戶主機依然正常運行,並從服務器可達。客戶的TCP響應正常,而服務器也知道對方是正常的,服務器在兩小時後將保活定時器復位。
客戶主機已經崩潰,並且關閉或者正在重新啓動。在任何一種情況下,客戶的TCP都沒有響應。服務端將不能收到對探測的響應,並在75秒後超時。服務器總共發送10個這樣的探測 ,每個間隔75秒。如果服務器沒有收到一個響應,它就認爲客戶主機已經關閉並終止連接。
客戶主機崩潰並已經重新啓動。服務器將收到一個對其保活探測的響應,這個響應是一個復位,使得服務器終止這個連接。
客戶機正常運行,但是服務器不可達,這種情況與2類似,TCP能發現的就是沒有收到探查的響應。
長連接短連接操作過程
短連接的操作步驟是:
建立連接——數據傳輸——關閉連接…建立連接——數據傳輸——關閉連接
長連接的操作步驟是:
建立連接——數據傳輸…(保持連接)…數據傳輸——關閉連接
長連接是什麼時候關閉
- 響應頭Keep-Alive: timeout。這個值能夠讓一些瀏覽器主動關閉連接,這樣服務器就不必要去關閉連接了。
- tcp自動探測一次,發現對方關閉,則斷開連接
長連接和短連接的優點和缺點
由上可以看出,長連接可以省去較多的TCP建立和關閉的操作,減少浪費,節約時間。對於頻繁請求資源的客戶來說,較適用長連接。不過這裏存在一個問題,存活功能的探測週期太長,還有就是它只是探測TCP連接的存活,屬於比較斯文的做法,遇到惡意的連接時,保活功能就不夠使了。在長連接的應用場景下,client端一般不會主動關閉它們之間的連接,Client與server之間的連接如果一直不關閉的話,會存在一個問題,隨着客戶端連接越來越多,server早晚有扛不住的時候,這時候server端需要採取一些策略,如關閉一些長時間沒有讀寫事件發生的連接,這樣可 以避免一些惡意連接導致server端服務受損;如果條件再允許就可以以客戶端機器爲顆粒度,限制每個客戶端的最大長連接數,這樣可以完全避免某個蛋疼的客戶端連累後端服務。
短連接對於服務器來說管理較爲簡單,存在的連接都是有用的連接,不需要額外的控制手段。但如果客戶請求頻繁,將在TCP的建立和關閉操作上浪費時間和帶寬。
長連接和短連接的產生在於client和server採取的關閉策略,具體的應用場景採用具體的策略,沒有十全十美的選擇,只有合適的選擇。
什麼時候用長連接?什麼時候用短連接?
長連接多用於操作頻繁,點對點的通訊,而且連接數不能太多情況,。每個TCP連接都需要三步握手,這需要時間,如果每個操作都是先連接,再操作的話那麼處理速度會降低很多,所以每個操作完後都不斷開,次處理時直接發送數據包就OK了,不用建立TCP連接。例如:數據庫的連接用長連接, 如果用短連接頻繁的通信會造成socket錯誤,而且頻繁的socket 創建也是對資源的浪費。
而像WEB網站的http服務一般都用短鏈接,因爲長連接對於服務端來說會耗費一定的資源,而像WEB網站這麼頻繁的成千上萬甚至上億客戶端的連接用短連接會更省一些資源,如果用長連接,而且同時有成千上萬的用戶,如果每個用戶都佔用一個連接的話,那可想而知吧。所以併發量大,但每個用戶無需頻繁操作情況下需用短連好。
長連接與短連接應用場景
長連接應用於場景 客戶端(移動App)與服務器消息推送、RPC遠程調用
http請求工具
客戶端模擬http請求工具
Postmen(谷歌插件)、RestClient
服務器模擬http請求工具
httpclient、HttpURLConnection
httpCient請求代碼
/**
* 發送 post請求訪問本地應用並根據傳遞參數不同返回不同結果
*/
public void post() {
// 創建默認的httpClient實例.
CloseableHttpClient httpclient = HttpClients.createDefault();
// 創建httppost
HttpPost httppost = new HttpPost("http://localhost:8080/myDemo/Ajax/serivceJ.action");
// 創建參數隊列
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("type", "house"));
UrlEncodedFormEntity uefEntity;
try {
uefEntity = new UrlEncodedFormEntity(formparams, "UTF-8");
httppost.setEntity(uefEntity);
System.out.println("executing request " + httppost.getURI());
CloseableHttpResponse response = httpclient.execute(httppost);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
System.out.println("--------------------------------------");
System.out.println("Response content: "
+ EntityUtils.toString(entity, "UTF-8"));
System.out.println("--------------------------------------");
}
} finally {
response.close();
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 關閉連接,釋放資源
try {
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void get() {
CloseableHttpClient httpclient = HttpClients.createDefault();
//請求超時
httpclient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 60000);
//讀取超時
httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 60000);
try {
// 創建httpget.
HttpGet httpget = new HttpGet("http://www.baidu.com/");
System.out.println("executing request " + httpget.getURI());
// 執行get請求.
CloseableHttpResponse response = httpclient.execute(httpget);
try {
// 獲取響應實體
HttpEntity entity = response.getEntity();
System.out.println("--------------------------------------");
// 打印響應狀態
System.out.println(response.getStatusLine());
if (entity != null) {
// 打印響應內容長度
System.out.println("Response content length: "
+ entity.getContentLength());
// 打印響應內容
System.out.println("Response content: " + EntityUtils.toString(entity));
}
System.out.println("------------------------------------");
} finally {
response.close();
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 關閉連接,釋放資源
try {
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
前端ajax請求
$.ajax({
type : 'post',
dataType : "text",
url : "http://a.a.com/a/FromUserServlet",
data : "userName=吳高尚&userAge=19",
success : function(msg) {
alert(msg);
}
});
跨域實戰解決方案
跨域原因產生:在當前域名請求網站中,默認不允許通過ajax請求發送其他域名。
服務器端代碼
@WebServlet("/FromServlet")
public class FromServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("userName");
JSONObject jsonObject = new JSONObject();
jsonObject.put("userName", userName);
resp.getWriter().println(jsonObject.toJSONString());
}
}
前端代碼
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>B網站訪問</title> </head> <script type="text/javascript" src="http://www.iooicc.com/static/common/jquery-1.7.2.min.js?t=2019-04-21"></script> <script type="text/javascript"> $(document).ready(function() { $.ajax({ type : "GET", async : false, url : "http://a.a.com/a/FromServlet?userName=644064", dataType : "json", success : function(data) { alert(data["userName"]); }, error : function() { alert('fail'); } });
}); </script> <body> <img alt="" src="http://a.a.com/a/imgs/log.png"> </body> </html></script> <body> <img alt="" src="http://a.a.com/a/imgs/log.png"> </body> </html> |
XMLHttpRequest cannot load 跨域問題解決辦法
解決方案
使用後臺response添加header
後臺response添加header,response.setHeader("Access-Control-Allow-Origin", "*"); 支持所有網站
使用JSONP
前端代碼:
$.ajax({ type : "POST", async : false, url : "http://a.a.com/a/FromUserServlet?userName=張三", dataType : "jsonp",//數據類型爲jsonp jsonp : "jsonpCallback",//服務端用於接收callback調用的function名的參數 success : function(data) { alert(data.result); }, error : function() { alert('fail'); } }); |
在同源策略下,在某個服務器下的頁面是無法獲取到該服務器以外的數據的,即一般的ajax是不能進行跨域請求的。但 img、iframe 、script等標籤是個例外,這些標籤可以通過src屬性請求到其他服務器上的數據。利用 script標籤的開放策略,我們可以實現跨域請求數據,當然這需要服務器端的配合。 Jquery中ajax 的核心是通過 XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態添加 <script>標籤來調用服務器提供的 js腳本。 當我們正常地請求一個JSON數據的時候,服務端返回的是一串 JSON類型的數據,而我們使用 JSONP模式來請求數據的時候服務端返回的是一段可執行的 JavaScript代碼。因爲jsonp 跨域的原理就是用的動態加載 script的src ,所以我們只能把參數通過 url的方式傳遞,所以jsonp的 type類型只能是get ! 示例: $.ajax({ url: 'http://192.168.1.114/yii/demos/test.php', //不同的域 type: 'GET', // jsonp模式只有GET 是合法的 data: { 'action': 'aaron' }, dataType: 'jsonp', // 數據類型 jsonp: 'backfunc', // 指定回調函數名,與服務器端接收的一致,並回傳回來 }) 其實jquery 內部會轉化成 http://192.168.1.114/yii/demos/test.php?backfunc=jQuery2030038573939353227615_1402643146875&action=aaron 然後動態加載 <script type="text/javascript"src="http://192.168.1.114/yii/demos/test.php?backfunc= jQuery2030038573939353227615_1402643146875&action=aaron"></script> 然後後端就會執行backfunc(傳遞參數 ),把數據通過實參的形式發送出去。 使用JSONP 模式來請求數據的整個流程:客戶端發送一個請求,規定一個可執行的函數名(這裏就是 jQuery做了封裝的處理,自動幫你生成回調函數並把數據取出來供success屬性方法來調用,而不是傳遞的一個回調句柄),服務器端接受了這個 backfunc函數名,然後把數據通過實參的形式發送出去
(在jquery 源碼中, jsonp的實現方式是動態添加<script>標籤來調用服務器提供的 js腳本。jquery 會在window對象中加載一個全局的函數,當 <script>代碼插入時函數執行,執行完畢後就 <script>會被移除。同時jquery還對非跨域的請求進行了優化,如果這個請求是在同一個域名下那麼他就會像正常的 Ajax請求一樣工作。)
|
後端代碼:
@WebServlet("/FromUserServlet")
public class FromUserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
// resp.setHeader("Access-Control-Allow-Origin", "*");
String userName = req.getParameter("userName");
String userAge = req.getParameter("userAge");
System.out.println(userName + "----" + userAge+"---"+req.getMethod());
// JSONObject JSONObject1 = new JSONObject();
// JSONObject1.put("success", "添加成功!");
// resp.getWriter().write("callbackparam(" + JSONObject1.toJSONString()
// + ")");
try {
resp.setContentType("text/plain");
resp.setHeader("Pragma", "No-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);
PrintWriter out = resp.getWriter();
JSONObject resultJSON = new JSONObject(); // 根據需要拼裝json
resultJSON.put("result", "content");
String jsonpCallback = req.getParameter("jsonpCallback");// 客戶端請求參數
out.println(jsonpCallback + "(" + resultJSON.toJSONString() + ")");// 返回jsonp格式數據
out.flush();
out.close();
} catch (Exception e) {
// TODO: handle exception
}
}
}
JSONP的優缺點:
JSONP只支持get請求不支持psot請求
後臺Http請求轉發
使用HttpClinet轉發進行轉發
使用接口網關
使用nginx轉發。
使用SpringCloud網關
表單重複提交解決方案(防止Http重複提交
場景模擬
創建一個from.jsp頁面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Form表單</title> </head>
<body> <form action="${pageContext.request.contextPath}/DoFormServlet" method="post"> 用戶名:<input type="text" name="userName"> <input type="submit" value="提交" id="submit"> </form> </body> </html> |
DoFormServlet 代碼
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/DoFormServlet")
public class DoFormServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String userName = req.getParameter("userName");
try {
Thread.sleep(300);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("往數據庫插入數據...."+userName);
resp.getWriter().write("success");
}
}
網絡延時
在平時開發中,如果網速比較慢的情況下,用戶提交表單後,發現服務器半天都沒有響應,那麼用戶可能會以爲是自己沒有提交表單,就會再點擊提交按鈕重複提交表單,我們在開發中必須防止表單重複提交。
重新刷新
表單提交後用戶點擊【刷新】按鈕導致表單重複提交
點擊瀏覽器的【後退】按鈕回退到表單頁面後進行再次提交
用戶提交表單後,點擊瀏覽器的【後退】按鈕回退到表單頁面後進行再次提交
解決方案
使用javascript 解決
既然存在上述所說的表單重複提交問題,那麼我們就要想辦法解決,比較常用的方法是採用JavaScript來防止表單重複提交,具體做法如下:
修改form.jsp頁面,添加如下的JavaScript代碼來防止表單重複提交
代碼:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Form表單</title> <script type="text/javascript"> var isFlag = false; //表單是否已經提交標識,默認爲false
function submitFlag() {
if (!isFlag) { isFlag = true; return true; } else { return false; }
} </script> </head>
<body> <form action="${pageContext.request.contextPath}/DoFormServlet" method="post" onsubmit="return submitFlag()"> 用戶名:<input type="text" name="userName"> <input type="submit" value="提交" id="submit"> </form> </body> </html> |
除了用這種方式之外,經常見的另一種方式就是表單提交之後,將提交按鈕設置爲不可用,讓用戶沒有機會點擊第二次提交按鈕,代碼如下:
function dosubmit(){
//獲取表單提交按鈕
var btnSubmit = document.getElementById("submit");
//將表單提交按鈕設置爲不可用,這樣就可以避免用戶再次點擊提交按鈕
btnSubmit.disabled= "disabled";
//返回true讓表單可以正常提交
return true;
}
使用後端提交解決
對於【場景二】和【場景三】導致表單重複提交的問題,既然客戶端無法解決,那麼就在服務器端解決,在服務器端解決就需要用到session了。
具體的做法:在服務器端生成一個唯一的隨機標識號,專業術語稱爲Token(令牌),同時在當前用戶的Session域中保存這個Token。然後將Token發送到客戶端的Form表單中,在Form表單中使用隱藏域來存儲這個Token,表單提交的時候連同這個Token一起提交到服務器端,然後在服務器端判斷客戶端提交上來的Token與服務器端生成的Token是否一致,如果不一致,那就是重複提交了,此時服務器端就可以不處理重複提交的表單。如果相同則處理表單提交,處理完後清除當前用戶的Session域中存儲的標識號。
在下列情況下,服務器程序將拒絕處理用戶提交的表單請求:
- 存儲Session域中的Token(令牌)與表單提交的Token(令牌)不同。
- 當前用戶的Session中不存在Token(令牌)。
- 用戶提交的表單數據中沒有Token(令牌)。
轉發代碼:
@WebServlet("/ForwardServlet") public class ForwardServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getSession().setAttribute("sesionToken", TokenUtils.getToken()); req.getRequestDispatcher("form.jsp").forward(req, resp); } } |
轉發頁面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Form表單</title>
</head>
<body> <form action="${pageContext.request.contextPath}/DoFormServlet" method="post" onsubmit="return dosubmit()"> <input type="hidden" name="token" value="${sesionToken}"> 用戶名:<input type="text" name="userName"> <input type="submit" value="提交" id="submit"> </form> </body> </html> |
後端Java代碼:
@WebServlet("/DoFormServlet") public class DoFormServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); boolean flag = isFlag(req); if (!flag) { resp.getWriter().write("已經提交..."); System.out.println("數據已經提交了.."); return; } String userName = req.getParameter("userName"); try { Thread.sleep(300); } catch (Exception e) { // TODO: handle exception } System.out.println("往數據庫插入數據...." + userName); resp.getWriter().write("success"); }
public boolean isFlag(HttpServletRequest request) { HttpSession session = request.getSession(); String sesionToken = (String) session.getAttribute("sesionToken"); String token = request.getParameter("token"); if (!(token.equals(sesionToken))) { return false; } session.removeAttribute("sesionToken"); return true; } }
|
Filter 也稱之爲過濾器,它是 Servlet 技術中最實用的技術,Web 開發人員通過 Filter 技術,對 web 服務器管理的所有 web 資源:例如 Jsp, Servlet, 靜態圖片文件或靜態 html 文件等進行攔截,從而實現一些特殊的功能。例如實現 URL 級別的權限訪問控制、過濾敏感詞彙、壓縮響應信息等一些高級功能。
它主要用於對用戶請求進行預處理,也可以對 HttpServletResponse 進行後處理。使用 Filter 的完整流程:Filter 對用戶請求進行預處理,接着將請求交給 Servlet 進行處理並生成響應,最後 Filter 再對服務器響應進行後處理。
使用Fileter防止XSS攻擊
什麼是XSS攻擊?
XSS攻擊使用Javascript腳本注入進行攻擊
例如在表單中注入: <script>location.href='http://www.iooicc.com'</script>
注意:谷歌瀏覽器 已經防止了XSS攻擊,爲了演示效果,最好使用火狐瀏覽器
實例:
演示:
代碼: fromToXss.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <form action="XssDemo" method="post"> <input type="text" name="userName"> <input type="submit"> </form> </body> </html> |
代碼: XssDemo
import java.io.IOException;
import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
@WebServlet("/XssDemo") public class XssDemo extends HttpServlet {
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String userName = req.getParameter("userName"); req.setAttribute("userName", userName); req.getRequestDispatcher("showUserName.jsp").forward(req, resp); }
}
|
代碼: showUserName.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title>
</head> <body>userName:${userName}
</body> </html> |
解決方案
使用Fileter過濾器過濾器注入標籤
XSSFilter
public class XssFiter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; XssAndSqlHttpServletRequestWrapper xssRequestWrapper = new XssAndSqlHttpServletRequestWrapper(req); chain.doFilter(xssRequestWrapper, response); }
public void destroy() {
}
}
|
XssAndSqlHttpServletRequestWrapper
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils;
/** * 防止XSS攻擊 */ public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper { HttpServletRequest request; public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) { super(request); this.request = request; } @Override public String getParameter(String name) { String value = request.getParameter(name); System.out.println("name:" + name + "," + value); if (!StringUtils.isEmpty(value)) { // 轉換Html value = StringEscapeUtils.escapeHtml4(value); } return value; } }
|
本章節用到maven座標
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> |