我正在尝试将一些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)
我有点迷失在这里,至于我的想法,两个代码实现应该是相同的吗?
答案 0 :(得分:4)
在这个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)
writer
和stream1
保持活跃状态,并在调用req.GetResponse()
时保持畅通无阻;你需要把它们放在一个人工范围内以获得与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)
答案 1 :(得分:1)
那不是&#34; C#方式&#34;进行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
}
客户端,处理程序是线程安全的,可以重用,因此可以存储在字段中。重用相同的客户端意味着操作系统不必每次都创建新的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
}