这是我认为需要澄清的地方,因为这种说法可能被认为具有误导性,就其措辞而言:
1 WebRequest => 1 WebResponse。 WebRequest 初始化后,您将无法更改任何内容。
这在原则上仍然有效,但 initialized 术语可能会造成混淆。更好的定义是:
在发出请求并返回 WebResponse 后,您无法更改 WebRequest 的任何参数,直到 WebResponse 关闭(处置)之后。
在WebResponse 返回结果后,它可以被显式或隐式关闭(在Using 块中) - 您可以请求另一个,根据需要修改WebRequest 参数(例如,将方法从POST 到 GET)。
此外,必须在请求新的 WebResponse 之前重新初始化 WebRequest。如果你不这样做,它就会回到它的默认值。
我在下面发布的代码是一个经典上下文(Web 登录请求)的示例,当 WebRequest 必须在同一过程中重新初始化多次,以接收未确定数量的 WebResponses 直到目标地址(或着陆页)已到达。
这或多或少是架构:
--------------
(GET or POST) | WebRequest | (Method is POST)
|---------> | GET/(POST) | <-----------| <-------------- |
| -------------- | |
| | | |
-------------- --------------- ------------------ --------------
| New | | WebResponse |--> | LogIn Required |-->| LogIn |
| Location | --------------- ------------------ | Address |
| (Referer | | --------------
| Set) | |
-------------- (Set Cookies)
| |
| ---------------
| | LogIn |
Redirection <----| OK |---NO---|
--------------- |
| |
YES |
(Set Cookies) |
| Request
--------------- Denied
| Response | |
| URI | |
--------------- |
| |
EXIT <------------|
|
请注意,发出 WebRequest 时,如果访问请求的资源 URI 需要身份验证,则服务器可能不会以 StatusCode 302 (Found)、301 (Moved) 或 303 (Redirected) 回答,它可能只是将 StatusCode 设置为 @987654336 @。重定向是隐式的,因为设置了“位置”标头,或者,如果是 WebForm 登录,则检索到的 Html 页面包含重定向。
无论如何,在检测到重定向后,必须按照新的重定向位置到达目的地。重定向可能包含一个或多个Hops,通常必须手动处理(以验证我们是否被发送到我们真正想去的地方)。
关于keep-alive 标头。
keep-alive 标头由客户端和/或服务器设置,以提示已建立的连接应保持打开的对应方至少一段时间,因为与当前交易相关的其他资源被交换的机会很高。
这可以防止创建大量昂贵的连接。
这种设置在Http 和Ftp 请求中很常见,它是Http 1.1 中的标准。
Hypertext Transfer Protocol (HTTP) Keep-Alive Header (IETF)
Compatibility with HTTP/1.0 Persistent Connections (IETF)
需要记住,keep-alive 标头指的是通过 WebRequest 建立的 Connection,而不是 WebRequest 本身。当创建连接并指定它应保持打开时,后续请求应维护 connection: keep-alive 标头以符合协议。
在 .NET HttpWebRequest 中,将KeepAlive 属性设置为False,相当于设置connection: close Header。
但是,管理连接和进程被授予访问权限的连接池的逻辑由ServicePointManager 管理,使用ServicePoint 作为每个连接请求的参考。
因此,WebRequest 可以指定它要求创建的 Connection 应该保持打开状态(因为它需要重复使用更多次),但 Connection 背后的真正逻辑,它是如何建立、维护和管理的,在于其他地方。
在 HTTP 2.0(StackOverflow/StackExchange 使用此协议)中,keep-alive 设置被完全忽略,原因正是如此。连接逻辑在更高的独立级别进行管理。
(...) 当您每 3 秒调用 1 个新的 HttpWebRequest 时,每 10
秒,还是每 60 秒?当我发送那些有什么区别
True 或 False 的请求?
您将KeepAlive 属性设置为提示连接建立的连接管理器应该保持打开状态,因为您知道它将被重复使用。但是,如果使用的协议提供此设置并且在管理连接池复杂性的内部逻辑所施加的限制范围内,ServicePointManager 和远程服务器都将遵守请求。
应该在HTTP 1.0中设置,HTTP 1.1中默认设置,HTTP 2.0中忽略。
由于在建立连接之前您无法知道将使用哪种协议,因此通常将其设置为keep-alive,因为在到请求资源的路由中的某些设备(特别是代理)可能需要此设置明确的(阅读有关代理及其行为的 IETF 文档)。
在这个例子中,一个WebRequest在一个Loop中被重复初始化,并且每次都释放底层的WebResponse,直到收到一个StatusCode200 (OK)或者请求被拒绝或者我们被重定向太多次(一个Cancellation Token在这里也可能有用)。
在示例中,main 方法是这样调用的:
Public Async Sub SomeMethodAsync()
LoginParameters = New LoginObject() With {
.CookieJar = New CookieContainer,
.LogInUrl = "[Some IP Address]",
.Credentials = New Dictionary(Of String, String)
}
LoginParameters.Credentials.Add("UserName", "[Username]")
LoginParameters.Credentials.Add("Email", "[email]")
LoginParameters.Credentials.Add("Password", "[Password]")
LoginParameters = Await HttpLogIn(LoginParameters)
7End Sub
必须保留LoginParameters 对象,因为引用了一个CookieContainer,其中包含身份验证后收到的Cookie。当一个新的 WebRequest 被初始化时,这些 Cookies 被传递给服务器,作为请求的凭证已经被认证的“证明”。请注意,这些 Cookie 会在一段时间后过期(这些 Cookie 会在发出新的 WebRequest 时“刷新”,除非 Session 有时间限制)。如果是这种情况,登录过程会自动重复。
Imports System.Net
Imports System.Net.Security
Imports System.IO
Imports System.Security
Imports System.Security.Cryptography
Imports System.Security.Cryptography.X509Certificates
Imports System.Text
Public LoginParameters As LoginObject
Public Class LoginObject
Public Property LogInUrl As String
Public Property ResponseUrl As String
Public Property Credentials As Dictionary(Of String, String)
Public Property StatusCode As HttpStatusCode
Public Property CookieJar As New CookieContainer()
End Class
Public Async Function HttpLogIn(LogInParameters As LoginObject) As Task(Of LoginObject)
Dim httpRequest As HttpWebRequest
Dim StatusCode As HttpStatusCode
Dim MaxHops As Integer = 20
' Windows 7 (.Net 4.5.1+ required):
'ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
' Windows 10 (.Net 4.5.1+ required):
ServicePointManager.SecurityProtocol = SecurityProtocolType.SystemDefault
'If needed or for testing
'ServicePointManager.ServerCertificateValidationCallback = AddressOf CertificateValidation
httpRequest = WebRequest.CreateHttp(LogInParameters.LogInUrl)
Try
HTTP_RequestHeadersInit(httpRequest, String.Empty, LogInParameters.CookieJar)
Using httpResponse As HttpWebResponse = CType(Await httpRequest.GetResponseAsync(), HttpWebResponse)
StatusCode = httpResponse.StatusCode
End Using
If StatusCode = HttpStatusCode.OK OrElse StatusCode = HttpStatusCode.NoContent Then
'POST Parameters are URLEncoded and the encoded strings converted to a Byte array of UTF8 chars
Dim EncodedParameters As Byte() = HTTP_EncodePOSTParameters(LogInParameters.Credentials)
httpRequest = WebRequest.CreateHttp(LogInParameters.LogInUrl)
httpRequest.Method = WebRequestMethods.Http.Post
httpRequest.ContentType = "application/x-www-form-urlencoded"
httpRequest.ContentLength = EncodedParameters.Length
HTTP_RequestHeadersInit(httpRequest, String.Empty, LogInParameters.CookieJar)
Using stream As Stream = Await httpRequest.GetRequestStreamAsync()
stream.Write(EncodedParameters, 0, EncodedParameters.Length)
End Using
Dim Hops As Integer = 0
Dim Referer As String = LogInParameters.LogInUrl
Dim LastHttpMethod As String = httpRequest.Method
Do
'Evaluate Authentication redirect or page moved
Using httpResponse As HttpWebResponse = CType(Await httpRequest.GetResponseAsync(), HttpWebResponse)
StatusCode = httpResponse.StatusCode
LogInParameters.ResponseUrl = URIFromResponseLocation(httpResponse).ToString()
End Using
If (StatusCode = HttpStatusCode.Moved) OrElse
(StatusCode = HttpStatusCode.Found) OrElse
(StatusCode = HttpStatusCode.RedirectMethod) OrElse
(StatusCode = HttpStatusCode.RedirectKeepVerb) Then
httpRequest = WebRequest.CreateHttp(LogInParameters.ResponseUrl)
HTTP_RequestHeadersInit(httpRequest, Referer, LogInParameters.CookieJar)
If StatusCode = HttpStatusCode.RedirectKeepVerb Then
httpRequest.Method = LastHttpMethod
Else
LastHttpMethod = httpRequest.Method
End If
End If
If (CType(StatusCode, Integer) > 320) OrElse Hops >= MaxHops Then
Exit Do
End If
Hops += 1
Loop While (StatusCode <> HttpStatusCode.OK)
If StatusCode = HttpStatusCode.OK Then
LogInParameters.CookieJar = httpRequest.CookieContainer
End If
End If
Catch exW As WebException
StatusCode = If(exW.Response IsNot Nothing,
CType(exW.Response, HttpWebResponse).StatusCode,
CType(exW.Status, HttpStatusCode))
Catch exS As System.Exception
StatusCode = CType(WebExceptionStatus.RequestCanceled, HttpStatusCode)
Finally
ServicePointManager.ServerCertificateValidationCallback = Nothing
End Try
LogInParameters.StatusCode = StatusCode
Return LogInParameters
End Function
Private Sub HTTP_RequestHeadersInit(ByRef httpReq As HttpWebRequest, Referer As String, CookiesJar As CookieContainer)
httpReq.Date = DateTime.Now
httpReq.CookieContainer = CookiesJar
httpReq.KeepAlive = True
httpReq.ConnectionGroupName = Guid.NewGuid().ToString()
httpReq.AllowAutoRedirect = False
httpReq.AutomaticDecompression = DecompressionMethods.GZip Or DecompressionMethods.Deflate
httpReq.ServicePoint.Expect100Continue = False
httpReq.Referer = Referer
httpReq.UserAgent = "Mozilla/5.0 (Windows NT 10; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
httpReq.Accept = "ext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
httpReq.Headers.Add(HttpRequestHeader.AcceptLanguage, "en-US;q=0.9,en;q=0.5")
httpReq.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip, deflate;q=0.8")
httpReq.Headers.Add(HttpRequestHeader.CacheControl, "no-cache")
End Sub
Private Function HTTP_EncodePOSTParameters(PostParameters As Dictionary(Of String, String)) As Byte()
Dim Encoder As New System.Text.UTF8Encoding()
Dim CredentialValues As New StringBuilder()
Dim _first As Boolean = True
For Each CurrentKeyPair As KeyValuePair(Of String, String) In PostParameters
If _first = False Then CredentialValues.Append("&")
CredentialValues.AppendFormat("{0}={1}", WebUtility.UrlEncode(CurrentKeyPair.Key),
WebUtility.UrlEncode(CurrentKeyPair.Value))
_first = False
Next
Return Encoder.GetBytes(CredentialValues.ToString())
End Function
Private Function URIFromResponseLocation(Response As HttpWebResponse) As System.Uri
Dim uri As Uri
Dim Location As String = Response.Headers("Location")
Try
If uri.IsWellFormedUriString(Location, UriKind.Absolute) Then
uri = New Uri(Location, UriKind.Absolute)
Else
Dim HostUri As String = Response.ResponseUri.GetComponents(UriComponents.SchemeAndServer,
UriFormat.Unescaped) + Location
uri = If(uri.IsWellFormedUriString(HostUri, UriKind.Absolute),
New Uri(HostUri),
New Uri(Response.ResponseUri.GetComponents(UriComponents.Scheme, UriFormat.Unescaped) +
Response.ResponseUri.Host + Location))
End If
Catch ExceptionOnInvalidUri As Exception
uri = New Uri(Location, UriKind.Relative)
End Try
Return uri
End Function
Private Function CertificateValidation(sender As Object,
CACert As X509Certificate,
CAChain As X509Chain,
PolicyErrors As SslPolicyErrors) As Boolean
'This method, as it is, accepts a Server certificate in any case
'It could be eventually adapted to refuse a connection (returning false)
'if the certificate is invalid, expired or from a untrusted path
If (PolicyErrors = SslPolicyErrors.None) Then Return True
'If a Certificated must be added to the Chain, uncomment the code below,
'selecting a Certificate in the Local (or other) Storage
'Dim MyCert As X509Certificate2 = New X509Certificate2("[localstorage]/[ca.cert]")
'CAChain.ChainPolicy.ExtraStore.Add(MyCert)
'CAChain.Build(MyCert)
'For Each CACStatus As X509ChainStatus In CAChain.ChainStatus
' If (CACStatus.Status <> X509ChainStatusFlags.NoError) And
' (CACStatus.Status <> X509ChainStatusFlags.UntrustedRoot) Then
' Return False
' End If
'Next
Return True
End Function