如何仿造websocket請求?

之前兩次singnalr、 websocket實時推送相關:

tag: 瀏覽器--->nginx--> server

其中提到nginx默認不會爲客戶端轉發UpgradeConnection標頭, 因爲爲了讓被代理的後端服務器知道客戶端要升級協議,故要在nginx上顯式轉發標頭:

location /realtime/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

事情本該就就這麼簡單, 但devops總會有各種奇怪的姿勢。

小動作引起的頭腦風暴

但是運維在給nginx配置的時候,給/根路徑配置了webcoket協議升級標頭。

按照字面理解,導致所有的客戶端轉發請求都在要求切換到websocket協議,但是除了/chat路徑, 服務器其他http路徑並沒有做websocket協議的邏輯,其他http請求是不是都該報錯了。

是實際看,所有的請求(websocket、http)都沒有報錯,都按照指定預期返回。

刨一下

利用asp.netcore默認腳手架項目:

已知http://localhost:5000/WeatherForecast是http請求,返回一大坨json數據;
WeatherForecast添加斷言日誌:

模擬ops的錯配效果,我們給這個請求添加websocket協議升級標頭。

第一次:curl 'http://localhost:5000/WeatherForecast' -H 'Upgrade: websocket' -H 'Connection: Upgrade' --verbose,正常返回大坨json數據。

日誌記錄:

 該請求是不是webcocket請求:False,headers:[Accept, */*], [Connection, Upgrade], [Host, localhost:5000], [User-Agent, curl/7.79.1], [Upgrade, websocket]

以上說明,服務端並不認爲是websocket請求, 這也印證了ops雖然錯配,但對於常規的http請求沒造成影響。

那服務端到底是怎麼認定websocket請求?

服務端認定websocket請求的源碼

依次判斷;

  • HttpMethod: GET
  • Sec-WebSocket-Version標頭==13
  • Connection標頭==Upgrade
  • Upgrade標頭==websocket
  • 有效的Sec-WebSocket-Key標頭

這樣我們就明白了,雖然websocket協議基於http,添加了httpConnectionUpgrade標頭,但是瀏覽器實際會給我們帶上Sec-WebSocket-KeySec-WebSocket-Version標頭,以向服務器證明這是一個有效的websocket握手。

於是我們可以使用
curl 'http://localhost:5000/WeatherForecast' -H 'Upgrade: websocket' -H 'Connection: Upgrade' -H 'Sec-WebSocket-Version: 13' -H 'Sec-webSocket-Key: eeZn6lg/rOu8QbKwltqHDA==' --verbose 仿造客戶端websocket請求。

日誌記錄:

該請求是不是webcocket請求:True,headers:[Accept, */*], [Connection, Upgrade], [Host, localhost:5000], [User-Agent, curl/7.79.1], [Upgrade, websocket], [Sec-WebSocket-Version, 13], [Sec-WebSocket-Key, eeZn6lg/rOu8QbKwltqHDA==]

對於這個websocket請求,服務端還是按照http代碼邏輯返回200ok和JSON數據,從這個層面上看,http協議是兼容websocket的。

真正要讓服務端按照websocket姿勢, 要使用HttpContext.WebSockets.AcceptWebSocketAsync()告知客戶端開始切換協議,並在原tcp上發起全雙工通信。

前後對比, 困惑得解: 雖然nginx爲http請求轉發了ConnectionUpgrade標頭, 但是服務器並不認可這是websocket升級協議,認爲是攜帶了特殊標頭的http請求,走原來的http業務處理邏輯是沒有問題的。

就坡下驢

將腳手架項目改成一個同時支持http和websocket協議的Action 吧:

[HttpGet(Name = "GetWeatherForecast")]
    public async Task Get()
    {
        _logger.LogInformation("該請求是不是webcocket請求:"+ HttpContext.WebSockets.IsWebSocketRequest+",headers:{0}", Request.Headers);

        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            while(true)  // 服務端單向推送
            {
                var wf = Summaries[Random.Shared.Next(Summaries.Length)];
                var serverMsg = Encoding.UTF8.GetBytes(wf);
                await webSocket.SendAsync(new ArraySegment<byte>(serverMsg, 0, serverMsg.Length), WebSocketMessageType.Text, false, CancellationToken.None);
            }

        }
        else
        {
            var arr = Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
            await  Response.WriteAsJsonAsync(arr);
        }
    }
}

總結

  1. 本文記錄了nginx在轉發websocket請求時要添加的配置
  2. websocket以http協議爲藍本,添加了特定的htp標頭來要求切換協議;爲了與常規http區分,瀏覽器自動增加了Sec-websocket-key等標頭, 讓服務端認爲這是一個有效的websocket請求。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章