ServletRequest
是咱們搞 Java Web
常常接觸的 Servlet Api
。有些時候咱們要常常對其進行一些操做。這裏列舉一些常常的難點操做。html
先後端交互咱們會在 body
中傳遞數據。咱們如何從 body
中提取數據。一般咱們會經過 IO
操做:java
/** * obtain request body * * @param request the ServletRequest * @return body string it maybe is null */
public static String obtainBody(ServletRequest request) {
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
try {
br = request.getReader();
String str;
while ((str = br.readLine()) != null) {
sb.append(str);
}
br.close();
} catch (IOException e) {
log.error(" requestBody read error");
} finally {
if (null != br) {
try {
br.close();
} catch (IOException e) {
log.error(" close io error");
}
}
}
return sb.toString();
}
複製代碼
看起來比較凌亂,各類異常處理,IO
開關操做,很不優雅。 若是你使用了 Java 8 你能夠這樣簡化這種操做:spring
String body = request.getReader().lines().collect(Collectors.joining());
複製代碼
BufferedReader
提供了獲取 Java 8 Stream 流的方法 lines()
,咱們能夠經過以上方法很是方便的獲取 ServletRequest
中的 body
後端
不要覺得上面的讀取 body
操做是完美無瑕的,這裏有一個坑。若是按照上面的操做 ServletRequest
中的 body
只能讀取一次。 咱們傳輸的數據都是經過流來傳輸的。ServletRequest
中咱們實際上都是經過:api
ServletInputStream inputStream = request.getInputStream()
複製代碼
來獲取輸入流,而後經過 read
系列方法來讀取。Java
中的 InputStream
read
方法內部有一個postion
, **它的做用是標誌當前流讀取到的位置,每讀取一次,位置就會移動一次,若是讀到最後,read
方法會返回 -1
,標誌已經讀取完了,若是想再次讀取,能夠調用 reset
方法,position
就會移動到上次調用 mark
的位置,mark
默認是 0
,因此就能從頭再讀了。 可否 reset
是有條件的,它取決於 markSupported()
,markSupported()
方法返回是否能夠進行 mark/reset
。app
咱們再回頭看 ServletInputStream
,其實現並無重寫 reset
方法並不支持 mark/reset
。因此ServletRequest
中的 IO流
只能讀取一次 。ide
若是咱們使用了個多個 Servlet Filter
進行鏈式調用並屢次操做 ServletRequest
中的流應該怎麼作? 咱們能夠經過 Servlet Api
提供的 javax.servlet.http.HttpServletRequestWrapper
來對其進行包裝。 經過繼承 HttpServletRequestWrapper
:post
public class ReaderRequest extends HttpServletRequestWrapper {
private String body;
public ReaderRequest(HttpServletRequest request) throws IOException {
super(request);
body = request.getReader().lines().collect(Collectors.joining());
}
@Override
public BufferedReader getReader() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
InputStreamReader inputStreamReader = new InputStreamReader(byteArrayInputStream);
return new BufferedReader(inputStreamReader);
}
}
複製代碼
如下是在一個 Servlet Filter
中的標準範例:ui
public class TestFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 包裝
ReaderRequest cachingRequestWrapper=new ReaderRequest((HttpServletRequest) servletRequest);
// 直接從包裝讀取
String collect = cachingRequestWrapper.getReader().lines().collect(Collectors.joining());
// 傳遞包裝
filterChain.doFilter(cachingRequestWrapper, servletResponse);
}
}
複製代碼
從前臺傳入數據的時候、後臺經過 HttpServletRequest
中的 getParameter(String name)
方法對數據進行獲取。 若是後臺想將數據放進去,下次請求或者其餘請求時使用,只能經過setAttribute(String name, Object o)
放入而後從 getAttribute(String name)
獲取, 沒法經過 getParameter(String name)
獲取。我在 Spring Security 實戰乾貨: 玩轉自定義登陸 就遇到了這個問題spa
首先說一下getParameter(String name)
是在數據從客戶端到服務端以後纔有效的,而 則是服務端內部的事情,只有在服務端調用了 setAttribute(String name, Object o)
以後,而且沒有重定向(redirect
),在沒有到客戶端以前 getAttribute(String name)
纔有效。
若是但願在服務端中轉過程當中使用 setParameter() ,咱們能夠經過 getParameter(String name)
委託給 getAttribute(String name)
來執行。相關實現依然經過 javax.servlet.http.HttpServletRequestWrapper
來實現。
package cn.felord.spring.security.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/** * @author Felordcn * @since 2019/10/17 22:09 */
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
public ParameterRequestWrapper(HttpServletRequest request ) {
super(request);
}
@Override
public String getParameter(String name) {
return (String) super.getAttribute(name);
}
}
複製代碼
你也可借鑑思路實現其它你須要的功能。
今天咱們對 ServletRequest
的一些經常使用的操做進行了講解。也是咱們常常在實際開發中遇到的一些問題。固然你也能夠使用一些第三方包來解決這些問題。
關注公衆號:Felordcn獲取更多資訊