从C#移植到F#时出现System.Net.WebException

时间:2017-06-21 21:59:59

标签: http f# httpwebresponse 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)

我有点迷失在这里,至于我的想法,两个代码实现应该是相同的吗?

2 个答案:

答案 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电话完成之后,

stream1writer.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)

writerstream1保持活跃状态​​,并在调用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
    }