11

PowerShell 練習 - 使用 Socket 取得 HTTP/HTTPS 回應

 3 years ago
source link: https://blog.darkthread.net/blog/ps-sslstream-over-socket/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
使用 Socket 取得 HTTP/HTTPS 回應-黑暗執行緒

要存取網站內容,PowerShell 有個 Invoke-WebRequest 滿足各式需求(延伸閱讀:野外求生系列- 無工具WebApi 徒手測試-黑暗執行緒),甚至呼叫 WCF 也難不倒它。

我有個需求是要偵測位於負載平衡設備(Network Load Balancer,以下簡稱 NLB)後方的 Web Farm,平時 NLB 平均分配 HTTP 請求給 A、B、C 三台網站伺服器處理,我想做到 1) 驗證流量會平均送到三台主機上 2) 測試三台主機都在正常提供服務。

直覺做法是連續發送多個 HTTP,統計落在各台主機的比例,順便確認三台主機都有提供服務。

一開始用 Invoke-WebRequest 標準寫法,發現它會引用 HttpWebRequest 的連線共用機制,無論跑幾次都是連同一台。依據在 HttpWebRequest 如何重複使用 TCP 連線?所做的研究,關掉 KeepAlive 可以迴避連線共用,而強大的 Invoke-WebRequest 也真有個 -DisableKeepAlive 參數,實測停用後如預期每次建立新的 TCP 連線,也會被 NLB 平均分配到各伺服器,滿足了要求。

雖然有現成工具可以解決問題,但手癢問題無法解決,我還是想造顆輪子伸展一下 - 便試著用 PowerShell 自己建 Socket 連上網站取回 HTTP/HTTPS 回應。

程式邏輯如下:

  1. 輸入 URL,用 System.Uri 解析出 Scheme、Host、Port、PathAndQuery
  2. 建立 Socket 連向 Host 的 80/443 Port
  3. 取得 Stream 以寫入及讀取資料
  4. 若走 HTTPS,則建立 SslStream 把 Stream 包起來 (裝飾者模式(Decorator Pattern))
  5. 以 StreamWriter 依 HTTP 規範寫入 GET /PathAndQuery HTTP/1.1... 請求
  6. 宣告 byte[] 呼叫 Stream.Read(byteArray, startIndex, dataLength) 分次讀取資料直到讀取長度為 0,讀取的資料寫入 MemoryStream 累積起來
  7. 以 Encoding.UTF8.GetString() 將 MemoryString.ToArray() 轉為文字
Param (
    [string]$url
)
$ErrorActionPreference = "STOP"
[System.Uri]$uri = New-Object System.Uri($url)
$isSsl = $uri.Scheme -eq 'https'
$hostName = $uri.Host
$path = $uri.PathAndQuery
$req = @"
GET $path HTTP/1.1
Host: $hostName
Connection: close
User-Agent: Mozilla/5.0
Accept: */*

"@
$port = 80
if ($isSsl) { $port = 443 }
if (!$uri.IsDefaultPort) { $port = $uri.Port }
$socket = New-Object System.Net.Sockets.TcpClient($hostName, $port)
$stream = $socket.GetStream()
if ($isSsl) {
    $sslStream = New-Object System.Net.Security.SslStream($stream, $false)
    $sslStream.AuthenticateAsClient($hostName)
    $stream = $sslStream
}
$sw = New-Object System.IO.StreamWriter($stream)
$sw.WriteLine($req)
$sw.Flush()
$buff = New-Object System.Byte[] 1024
$ms = New-Object System.IO.MemoryStream
do {
    Start-Sleep -Milliseconds 100
    $stream.ReadTimeout = 1000
    $more = $false
    $readBytes = 0
    do {
        try {
            $readBytes = $stream.Read($buff, 0, 1024)
            if ($readBytes -gt 0) {
                $more = $true
                $ms.Write($buff, 0, $readBytes)
            }
        }
        catch {
            $more = $false
            $readBytes = 0
        }
    } while ($readBytes -gt 0)
} while ($more)
# TODO Support encoding besides UTF8
$enc = [System.Text.Encoding]::UTF8
$resp = $enc.GetString($ms.ToArray())
$ms.Dispose()
$stream.Dispose()
$socket.Dispose()
Write-Output $resp

如此,一個簡單的 HTTP/HTTPS 讀取程式就完成囉~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK