要存取網站內容,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 讀取程式就完成囉~

Example of using PowerShell to open socket, send HTTP/HTTPS GET requqest and read response.


Comments

Be the first to post a comment

Post a comment