這兩天做好的功能要上線了。但是測試的時候忽然發現當POST提交數據量多大時,會導致后端報400錯誤。最開始以為瓶頸存在於tomcat,因為tomcat默認能接受的POST請求大小為2M,所以手動修改tomcat server.xml 文件,將接受POST大小修改為不限制。 經測試,發現依舊不起作用。 今天上網查詢了相關資料,解決了這個問題。資料比較零散,在這里整理一下,為同樣遇到該問題的伙伴們提供一個解決思路。也為自己以后再遇到這種問題做個筆記。
首先看了一下前端提交的Content-type 發現是application/x-www-form-urlencoded 然后就根據這個去查找相關問題,找到如下文章:
http://hongjiang.info/http-application-x-www-form-urlencoded/#comments
里面介紹的有幾句話起到了很大的作用:
同事遇到在servlet端通過request對象getInputStream讀取POST過來的數據,卻讀不到的問題,懷疑是tomcat的問題。查了一下Content-type
是application/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轉換。這樣問題就解決了。