System.Net.WebException 从 C# 移植到 F# 时
System.Net.WebException when porting from C# to F#
我正在尝试将一些 C# 代码移植到 F#。
C# 代码取自此处(并略微剥离):https://github.com/joelpob/betfairng/blob/master/BetfairClient.cs
public bool Login(string p12CertificateLocation, string p12CertificatePassword, string username, string password)
{
var appKey = "APPKEY";
string postData = string.Format("username={0}&password={1}", username, password);
X509Certificate2 x509certificate = new X509Certificate2(p12CertificateLocation, p12CertificatePassword);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://identitysso.betfair.com/api/certlogin");
request.UseDefaultCredentials = true;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Headers.Add("X-Application", appKey);
request.ClientCertificates.Add(x509certificate);
request.Accept = "*/*";
using (Stream stream = request.GetRequestStream())
using (StreamWriter writer = new StreamWriter(stream, Encoding.Default))
writer.Write(postData);
using (Stream stream = ((HttpWebResponse)request.GetResponse()).GetResponseStream())
using (StreamReader reader = new StreamReader(stream, Encoding.Default))
上面的 C# 代码效果很好。但是,当尝试 运行(我认为是)F# 等效代码时,没有任何实际更改,我收到一条错误消息。
代码来自 运行 来自同一台计算机、相同的 VS 安装和完全相同的 4 个参数。
我收到的错误消息在倒数第二行:
member x.Login(username, password,p12CertificateLocation:string, p12CertificatePassword:string) =
let AppKey = "APPKEY"
let url = "https://identitysso.betfair.com/api/certlogin"
let postData = "username=" + username + "&password=" + password
let x509certificate = new X509Certificate2(p12CertificateLocation, p12CertificatePassword)
let req = HttpWebRequest.Create(url) :?> HttpWebRequest
req.ClientCertificates.Add(x509certificate)|>ignore
req.UseDefaultCredentials <- true
req.Method <- "POST"
req.ContentType <- "application/x-www-form-urlencoded"
req.Headers.Add("X-Application",AppKey)
req.Accept <-"*/*"
use stream = req.GetRequestStream()
use writer =new StreamWriter(stream,Encoding.Default)
writer.Write(postData)
// fails on this line:
use stream = (req.GetResponse() :?> HttpWebResponse ).GetResponseStream()
// with System.Net.WebException: 'The remote server returned an error: (400) Bad Request.'
use reader = new StreamReader(stream,Encoding.Default)
我有点迷路了,我觉得这两个代码实现应该是相同的?
在此 C# 代码中:
using (Stream stream1 = request.GetRequestStream())
using (StreamWriter writer = new StreamWriter(stream1, Encoding.Default))
writer.Write(postData);
using (Stream stream2 = ((HttpWebResponse)request.GetResponse()).GetResponseStream())
using (StreamReader reader = new StreamReader(stream2, Encoding.Default))
writer
和 stream1
在 writer.Write
调用完成后立即刷新并关闭,然后再调用 request.GetResponse()
。 (由于,呃.. 有趣 你的代码格式,这个事实有点模糊。)
在此 F# 代码中:
use stream1 = req.GetRequestStream()
use writer = new StreamWriter(stream1, Encoding.Default)
writer.Write(postData)
use stream2 = (req.GetResponse() :?> HttpWebResponse).GetResponseStream()
use reader = new StreamReader(stream2, Encoding.Default)
当调用 req.GetResponse()
时,writer
和 stream1
保持活动状态并保持未刷新和未关闭状态;您需要将它们放在人工范围内以获得与 C# 相同的行为:
do use stream1 = req.GetRequestStream()
use writer = new StreamWriter(stream1, Encoding.Default)
writer.Write(postData)
(* or
(use stream1 = req.GetRequestStream()
use writer = new StreamWriter(stream1, Encoding.Default)
writer.Write(postData))
*)
use stream2 = (req.GetResponse() :?> HttpWebResponse).GetResponseStream()
use reader = new StreamReader(stream2, Encoding.Default)
这不是 "the C# way" 进行 HTTP POST 调用。在所有受支持的 .NET 版本(即 4.5.2 及更高版本)中,典型的方法是使用 HttpClient。即使使用 HttpWebRequest,也有太多冗余或矛盾的调用,例如使用默认凭据(即 Windows 身份验证)
C#方式是这样的:
var client=new HttpClient("https://identitysso.betfair.com/api");
var values = new Dictionary<string, string>
{
{ "username", username },
{ "password", password }
};
var content = new FormUrlEncodedContent(values);
content.Headers.Add("X-Application",apiKey);
var response = await client.PostAsync("certlogin", content);
var responseString = await response.Content.ReadAsStringAsync();
为了使用客户端证书,您必须使用自定义 HTTP 处理程序创建客户端实例:
var handler = new WebRequestHandler();
var x509certificate = new X509Certificate2(certPath, certPassword);
handler.ClientCertificates.Add(certificate);
var client = new HttpClient(handler)
{
BaseAddress = new Uri("https://identitysso.betfair.com/api")
}
在 F# 中编写相同的代码非常简单:
let login username password (certPath:string) (certPassword:string) (apiKey:string) =
let handler = new WebRequestHandler()
let certificate = new X509Certificate2(certPath, certPassword)
handler.ClientCertificates.Add certificate |> ignore
let client = new HttpClient(handler,BaseAddress = Uri("https://identitysso.betfair.com"))
async {
let values = dict["username", username ; "password", password ]
let content = new FormUrlEncodedContent(values)
content.Headers.Add( "X-Application" ,apiKey)
let! response = client.PostAsync("api/certlogin",content) |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! responseString = response.Content.ReadAsStringAsync() |> Async.AwaitTask
return responseString
}
客户端、处理程序是线程安全的,可以重复使用,因此可以将它们存储在字段中。重复使用相同的客户端意味着 OS 不必每次都创建新的 TCP/IP 连接,从而提高性能。最好单独创建客户端。 :
let buildClient (certPath:string) (certPassword:string) =
let handler = new WebRequestHandler()
let certificate = new X509Certificate2(certPath, certPassword)
handler.ClientCertificates.Add certificate |> ignore
new HttpClient(handler,BaseAddress = Uri("https://identitysso.betfair.com"))
let login (client:HttpClient) username password (apiKey:string) =
async {
let values = dict["username", username ; "password", password ]
let content = new FormUrlEncodedContent(values)
content.Headers.Add( "X-Application" ,apiKey)
let! response = client.PostAsync("api/certlogin",content) |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! responseString = response.Content.ReadAsStringAsync() |> Async.AwaitTask
//Do whatever is needed here
return responseString
}
我正在尝试将一些 C# 代码移植到 F#。
C# 代码取自此处(并略微剥离):https://github.com/joelpob/betfairng/blob/master/BetfairClient.cs
public bool Login(string p12CertificateLocation, string p12CertificatePassword, string username, string password)
{
var appKey = "APPKEY";
string postData = string.Format("username={0}&password={1}", username, password);
X509Certificate2 x509certificate = new X509Certificate2(p12CertificateLocation, p12CertificatePassword);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://identitysso.betfair.com/api/certlogin");
request.UseDefaultCredentials = true;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Headers.Add("X-Application", appKey);
request.ClientCertificates.Add(x509certificate);
request.Accept = "*/*";
using (Stream stream = request.GetRequestStream())
using (StreamWriter writer = new StreamWriter(stream, Encoding.Default))
writer.Write(postData);
using (Stream stream = ((HttpWebResponse)request.GetResponse()).GetResponseStream())
using (StreamReader reader = new StreamReader(stream, Encoding.Default))
上面的 C# 代码效果很好。但是,当尝试 运行(我认为是)F# 等效代码时,没有任何实际更改,我收到一条错误消息。
代码来自 运行 来自同一台计算机、相同的 VS 安装和完全相同的 4 个参数。
我收到的错误消息在倒数第二行:
member x.Login(username, password,p12CertificateLocation:string, p12CertificatePassword:string) =
let AppKey = "APPKEY"
let url = "https://identitysso.betfair.com/api/certlogin"
let postData = "username=" + username + "&password=" + password
let x509certificate = new X509Certificate2(p12CertificateLocation, p12CertificatePassword)
let req = HttpWebRequest.Create(url) :?> HttpWebRequest
req.ClientCertificates.Add(x509certificate)|>ignore
req.UseDefaultCredentials <- true
req.Method <- "POST"
req.ContentType <- "application/x-www-form-urlencoded"
req.Headers.Add("X-Application",AppKey)
req.Accept <-"*/*"
use stream = req.GetRequestStream()
use writer =new StreamWriter(stream,Encoding.Default)
writer.Write(postData)
// fails on this line:
use stream = (req.GetResponse() :?> HttpWebResponse ).GetResponseStream()
// with System.Net.WebException: 'The remote server returned an error: (400) Bad Request.'
use reader = new StreamReader(stream,Encoding.Default)
我有点迷路了,我觉得这两个代码实现应该是相同的?
在此 C# 代码中:
using (Stream stream1 = request.GetRequestStream())
using (StreamWriter writer = new StreamWriter(stream1, Encoding.Default))
writer.Write(postData);
using (Stream stream2 = ((HttpWebResponse)request.GetResponse()).GetResponseStream())
using (StreamReader reader = new StreamReader(stream2, Encoding.Default))
writer
和 stream1
在 writer.Write
调用完成后立即刷新并关闭,然后再调用 request.GetResponse()
。 (由于,呃.. 有趣 你的代码格式,这个事实有点模糊。)
在此 F# 代码中:
use stream1 = req.GetRequestStream()
use writer = new StreamWriter(stream1, Encoding.Default)
writer.Write(postData)
use stream2 = (req.GetResponse() :?> HttpWebResponse).GetResponseStream()
use reader = new StreamReader(stream2, Encoding.Default)
当调用 req.GetResponse()
时,writer
和 stream1
保持活动状态并保持未刷新和未关闭状态;您需要将它们放在人工范围内以获得与 C# 相同的行为:
do use stream1 = req.GetRequestStream()
use writer = new StreamWriter(stream1, Encoding.Default)
writer.Write(postData)
(* or
(use stream1 = req.GetRequestStream()
use writer = new StreamWriter(stream1, Encoding.Default)
writer.Write(postData))
*)
use stream2 = (req.GetResponse() :?> HttpWebResponse).GetResponseStream()
use reader = new StreamReader(stream2, Encoding.Default)
这不是 "the C# way" 进行 HTTP POST 调用。在所有受支持的 .NET 版本(即 4.5.2 及更高版本)中,典型的方法是使用 HttpClient。即使使用 HttpWebRequest,也有太多冗余或矛盾的调用,例如使用默认凭据(即 Windows 身份验证)
C#方式是这样的:
var client=new HttpClient("https://identitysso.betfair.com/api");
var values = new Dictionary<string, string>
{
{ "username", username },
{ "password", password }
};
var content = new FormUrlEncodedContent(values);
content.Headers.Add("X-Application",apiKey);
var response = await client.PostAsync("certlogin", content);
var responseString = await response.Content.ReadAsStringAsync();
为了使用客户端证书,您必须使用自定义 HTTP 处理程序创建客户端实例:
var handler = new WebRequestHandler();
var x509certificate = new X509Certificate2(certPath, certPassword);
handler.ClientCertificates.Add(certificate);
var client = new HttpClient(handler)
{
BaseAddress = new Uri("https://identitysso.betfair.com/api")
}
在 F# 中编写相同的代码非常简单:
let login username password (certPath:string) (certPassword:string) (apiKey:string) =
let handler = new WebRequestHandler()
let certificate = new X509Certificate2(certPath, certPassword)
handler.ClientCertificates.Add certificate |> ignore
let client = new HttpClient(handler,BaseAddress = Uri("https://identitysso.betfair.com"))
async {
let values = dict["username", username ; "password", password ]
let content = new FormUrlEncodedContent(values)
content.Headers.Add( "X-Application" ,apiKey)
let! response = client.PostAsync("api/certlogin",content) |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! responseString = response.Content.ReadAsStringAsync() |> Async.AwaitTask
return responseString
}
客户端、处理程序是线程安全的,可以重复使用,因此可以将它们存储在字段中。重复使用相同的客户端意味着 OS 不必每次都创建新的 TCP/IP 连接,从而提高性能。最好单独创建客户端。 :
let buildClient (certPath:string) (certPassword:string) =
let handler = new WebRequestHandler()
let certificate = new X509Certificate2(certPath, certPassword)
handler.ClientCertificates.Add certificate |> ignore
new HttpClient(handler,BaseAddress = Uri("https://identitysso.betfair.com"))
let login (client:HttpClient) username password (apiKey:string) =
async {
let values = dict["username", username ; "password", password ]
let content = new FormUrlEncodedContent(values)
content.Headers.Add( "X-Application" ,apiKey)
let! response = client.PostAsync("api/certlogin",content) |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! responseString = response.Content.ReadAsStringAsync() |> Async.AwaitTask
//Do whatever is needed here
return responseString
}