1. 程式人生 > >前後端分離。前端POST請求引數過長,導致400錯誤解決辦法及分析

前後端分離。前端POST請求引數過長,導致400錯誤解決辦法及分析

這兩天做好的功能要上線了。但是測試的時候忽然發現當POST提交資料量多大時,會導致後端報400錯誤。最開始以為瓶頸存在於tomcat,因為tomcat預設能接受的POST請求大小為2M,所以手動修改tomcat server.xml 檔案,將接受POST大小修改為不限制。 經測試,發現依舊不起作用。 今天上網查詢了相關資料,解決了這個問題。資料比較零散,在這裡整理一下,為同樣遇到該問題的夥伴們提供一個解決思路。也為自己以後再遇到這種問題做個筆記。

首先看了一下前端提交的Content-type 發現是application/x-www-form-urlencoded  然後就根據這個去查詢相關問題,找到如下文章:

裡面介紹的有幾句話起到了很大的作用:

同事遇到在servlet端通過request物件getInputStream讀取POST過來的資料,卻讀不到的問題,懷疑是tomcat的問題。查了一下Content-typeapplication/x-www-form-urlencoded,估計是被解析成了parameters,果然在他獲取流之前,有過request.getParameter的操作。

熟悉servlet的話,這個問題應該算常識了。它其實跟容器無關,所有的servlet容器都是這樣的行為。幾年前在實現一個閘道器代理的時候就遇到過這個問題,當時使用的是jetty,發現POST過來的資料讀不到,也是application/x-www-form-urlencoded

編碼,斷點跟蹤發現是在獲取流之前有過request.getParameter,資料會被解析,並且後續資料流不可再被讀取。

在servlet規範3.1.1節裡,對POST資料何時會被當做parameters有描述:

1. The request is an HTTP or HTTPS request.
2. The HTTP method is POST.
3. The content type is application/x-www-form-urlencoded.
4. The servlet has made an initial call of any of the getParameter family of methods on the request object.

If the conditions are met, post form data will no longer be available for reading directly from the request object’s input stream.

規範裡已經明確的聲明當請求滿足: 1) http/https, 2) POST, 3) Content-type 是application/x-www-form-urlencoded, 4) 呼叫過getParameter方法;則資料會被當做請求的paramaters,而不能再通過 request 的 inputstream 直接讀取。

這裡給了我很大的思路,說是POST提交,實則在我spring mvc中是這麼寫的:

/**
	 * 儲存盤點記錄
	 * 
	 * @param data
	 * @return
	 */
	@ResponseBody
	@RequestMapping(value = "/saveStoreCheck", method = RequestMethod.POST)
	public AjaxResult saveStoreCheck(String data) {
		GoodsJson materials = new Gson().fromJson(data, InventoryCheckIn.class);
		inventoryCheckIn.setCheckId(ShopUUID.getUUID(ShopUUID.UUID_CHECK));
		inventoryCheckIn.setKtvId(ShopConstUtil.getShopId());
		try {
			String checkId = inventoryCheckService.saveStoreCheck(inventoryCheckIn);
			return AjaxResult.getOK(checkId);
		} catch (InventoryCheckException | AccessToLibraryException | AllotException e) {
			return AjaxResult.getLogicError(e.getMessage(), "");
		}
	}

 在我看來,這樣的寫法實際上傳過來的引數依舊是會拼成類似於URL引數一樣過來。所以當長度過長時,會導致出錯。所以就將提交方式 Content-type修改為了application/json 這種JSON格式,不會限制傳過來的大小。同時後臺接收也就需要修改。程式碼修改為:
/**
	 * 儲存盤點記錄
	 * 
	 * @param data
	 * @return
	 */
	@ResponseBody
	@RequestMapping(value = "/saveStoreCheck", method = RequestMethod.POST,produces="application/json")
	public AjaxResult saveStoreCheck(@RequestBody InventoryCheck inventoryCheckIn) {
		inventoryCheckIn.setCheckId(ShopUUID.getUUID(ShopUUID.UUID_CHECK));
		inventoryCheckIn.setKtvId(ShopConstUtil.getShopId());
		try {
			String checkId = inventoryCheckService.saveStoreCheck(inventoryCheckIn);
			return AjaxResult.getOK(checkId);
		} catch (InventoryCheckException | AccessToLibraryException | AllotException e) {
			return AjaxResult.getLogicError(e.getMessage(), "");
		}
	}
直接使用物件接收,不需要再進行JSON轉換。這樣問題就解決了。