# How to Allow Concurrent Requests PerSession ## 研究起因 發送 request A 後端進行等待(await等方法),此時相同使用者發送request B 後端應要直接response,但卻發現需要等A進行回應才會回應B。 ## 如何發現跟Session有關 在原有專案撰寫簡單sample code,測試可以正常response不需等await結束,但相同程式碼更新至測試站從測試入口跳轉該頁面卻發現需要等await。 ![](https://i.imgur.com/5L967uU.png) 程式相同卻有不同結果所以首先先懷疑了 1. **機器狀態**: 是否因為機器較為忙碌導致response 速度被影響,架了一個乾淨的站台並在==只有一個client的只發送兩個request的情況依舊為必發==。 2. **瀏覽器問題**: 是否瀏覽器偷偷調整了[maximum TCP 數量](https://stackoverflow.com/questions/29206067/understanding-chrome-network-log-stalled-state/29564247),導致發送的請求超過上限需等待,架了一個乾淨的站台並發送數個不須等待的request以chrome為例可以發送到6個。 3. **IIS 設置**: 檢查了下發現測試站及RD機設置皆相同,但最底層的OS不同 一個是 window server 2012、一個是2016..,查找了兩者差異後決定先暫時放棄往這邊探索 4. **操作步驟是否影響**: 反覆測試了下操作步驟後忽然發現若RD機的網頁從測試入口登入一樣會有不支援併發request的狀況,比較了下兩者差異程式碼一步步刪減後發現是Session!! 當TestLogin ==有對 session進行寫入的狀況就會導致無法進行併發request== ## 參考解法 ### 調整設定檔 1. 設定 webconfig: ``` <appSettings> <add key="aspnet:AllowConcurrentRequestsPerSession" value="true" /> <add key="aspnet:RequestQueueLimitPerSession" value="2147483647"/> </appSettings> ``` 2. 發送 request 的頁面設定參數 ``` <%@ Page EnableSessionState="False"%> ``` 但以上方法測試後發現皆沒有效果... ### 清除session(需將cookie內的ASP.NET_SessionId 也清除) 從前面的測試發現只要曾經寫入到Session內就會造成request無法進行併發的狀態,原以為清空Session即是最暴力但一定可行的方式,程式碼如下: ```C# //清除 剛剛新增的session Session.Remove("剛剛新增的Session"); ``` 上述操作無效,於是那在更暴力直接Abandon一樣不行,程式碼如下: ```C# //清除 session Session.Abandon(); ``` 最後測試結果發現==需將cookie內的ASP.NET_SessionId 進行清空==才可以不被之前執行寫入Session的動作影響,程式碼如下: ```C# //清除 session Session.Abandon(); //清除 cookie內的sessionId Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", "")); ``` 不過此行為會造成跳轉頁面的時候產生新的SessionId,所以正常應該是無法這樣解除非網頁驗證登入狀態都不靠SessionId了。 另外若是想用JS直接改cookie內的ASP.NET_SessionId也是不行的,因為是預設HttpOnly所以Cookie只限被伺服端存取,無法在用戶端讀取。 ### Session的讀寫改使用WebAPI進行 [詳細架設方式請參考此連結](https://hackmd.io/zK04Jjg7QSCTXGWelg5cxA) 此作法是想藉由WEB頁面本身都不寫入Session的方式,統一將Session值寫在WEBAPI內當需要相對應的資料就呼叫API,但此作法需考量 ## 結論 目前測是唯一有效的方式就是,專案不進行Session的寫入處理,或是在乾脆一點有await的處理都不再WEB端做,不過這應該沒什麼機會QQ ## 補充 ### sample code #### 前端: 兩顆按鈕一個發送會進行await的request,一個不等待。 ```htmlmixed <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestAwait.aspx.cs" Inherits="BB_v3.bbView.TestAwait"%> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> </head> <body> <h1>簡易測試</h1> <h2>請開F12 看console 有 response 會直接印</h2> <div id="resultsEasy"></div> <div> <button type="button" style="width:30%;height:10%;font-size:20px;" onclick="AjaxAwait()">發送ajax請求至await</button> <button type="button" style="width:30%;height:10%;font-size:20px;" onclick="AjaxNotAwait()">發送ajax請求至notAwait</button> </div> <script src="/js/jquery-2.1.4.min.js" type="text/javascript"></script> <script type="text/javascript"> function AjaxAwait() { $.ajax({ type: 'POST', url: '/ajax/TestAwait.aspx', data: { method: 'NeedAwait'}, dataType: 'json', error: function (e) { console.log(e.responseText); }, success: function (data) { console.log(data) } }); } function AjaxNotAwait() { $.ajax({ type: 'POST', url: '/ajax/TestAwait.aspx', data: { method: 'NotAwait' }, dataType: 'json', error: function (e) { console.log(e.responseText); }, success: function (data) { console.log(data) } }); } </script> </body> </html> ``` #### 後端 ```C# using System; using System.Threading.Tasks; public partial class TestAwait : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { string method = Request.Form["method"]; switch (method) { case "NeedAwait": NeedAwait(); break; case "NotAwait": NotAwait(); break; default: break; } } public async void NeedAwait() { //進行延遲 await Task.Delay(5 * 1000); Response.Write("NeedAwait 運作完成"); } public void NotAwait() { Response.Write("NotAwait 運作完成"); } } ``` ###### tags: `Session` `併發處理`