ServletRequest 是咱們搞 Java Web 常常接觸的 Servlet Api 。有些時候咱們要常常對其進行一些操做。這裏列舉一些常常的難點操做。java
先後端交互咱們會在 body 中傳遞數據。咱們如何從 body 中提取數據。一般咱們會經過 IO 操做:spring
/**
* 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 你能夠這樣簡化這種操做:後端
String body = request.getReader().lines().collect(Collectors.joining());
複製代碼
BufferedReader 提供了獲取 Java 8 Stream 流的方法 lines() ,咱們能夠經過以上方法很是方便的獲取 ServletRequest 中的 bodyapp
不要覺得上面的讀取 body 操做是完美無瑕的,這裏有一個坑。若是按照上面的操做 ServletRequest 中的 body 只能讀取一次。 咱們傳輸的數據都是經過流來傳輸的。ServletRequest 中咱們實際上都是經過:ide
ServletInputStream inputStream = request.getInputStream() 來獲取輸入流,而後經過 read 系列方法來讀取。Java 中的 InputStream read 方法內部有一個postion, **它的做用是標誌當前流讀取到的位置,每讀取一次,位置就會移動一次,若是讀到最後,read 方法會返回 -1,標誌已經讀取完了,若是想再次讀取,能夠調用 reset 方法,position 就會移動到上次調用 mark 的位置,mark 默認是 0,因此就能從頭再讀了。 可否 reset 是有條件的,它取決於 markSupported(),markSupported() 方法返回是否能夠進行 mark/reset 。post
咱們再回頭看 ServletInputStream ,其實現並無重寫 reset 方法並不支持 mark/reset 。因此ServletRequest 中的 IO流 只能讀取一次 。ui
若是咱們使用了個多個 Servlet Filter 進行鏈式調用並屢次操做 ServletRequest 中的流應該怎麼作? 咱們能夠經過 Servlet Api 提供的 javax.servlet.http.HttpServletRequestWrapper 來對其進行包裝。 經過繼承 HttpServletRequestWrapper :spa
public class ReaderRequest extends HttpServletRequestWrapper { private String body;code
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 中的標準範例:繼承
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 實戰乾貨: 玩轉自定義登陸 就遇到了這個問題
首先說一下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 的一些經常使用的操做進行了講解。也是咱們常常在實際開發中遇到的一些問題。固然你也可使用一些第三方包來解決這些問題。
原創做者:碼農小胖哥 轉載地址:gper.club/articles/7e…