2017-06-21 7 views
1

F#にいくつかのC#コードを移植しようとしています。C#からF#に移植する際のSystem.Net.WebException

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つの引数があります。

私が取得エラーメッセージが最後の行に2番目にある:

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つのコードの実装は同一である必要があり、ここで失われたんですか?このC#コードで

+2

あなたは直接ポートに非常に具体的な理由C#のコードを持っているかもしれませんが、よりidimaticクライアントがラップHttpWebRequestの、例えば[Http.fs](https://github.com/haf/Httpこともあります。 fs)または[Http Utilities](http://fsharp.github.io/FSharp.Data/library/Http.html)を参照してください。 – s952163

+0

C#の方法は、HttpClientを使用することで、HttpWebRequestを直接使用することはありません。GetResponse、GetRresponseStreamへの呼び出しを連鎖させない - 応答が存在しない場合、例えば状態コードがエラーである場合、応答を読み取る*ポイント*は何ですか? –

+0

最後に、ここで行うように* random *エンコーディングを使用しないでください。Encoding.DefaultはシステムのANSIコードページです。ほとんどすべてのWebサービスは、UTF16を使用するものを除き、UTF8を使用します。 ANSIコードページは使用しないでください。いずれにしても、適切なヘッダーに符号化を指定する必要があります。 –

答えて

1

「C#の方法は、」HTTPのPOST呼び出しをしないようにということ。典型的な方法は、サポートされているすべての.NETバージョン(4.5.2以降など)でHttpClientを使用することです。クライアント証明書を使用するためには

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();  

、あなたが持っている

:でもHttpWebRequestので、デフォルトの資格(つまり、Windows認証)を使用してのような、あまりにも多くの冗長または矛盾した呼び出しは、

C#の方法がこれですありますカスタムHTTPハンドラを使用してクライアントのインスタンスを作成します:F#で同じコードを書く

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") 
      } 

はストレートフォワードです:

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 
    } 
4

:あなたはrequest.GetResponse()を呼び出す前に

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

writerstream1は、フラッシュとwriter.Write通話が終了した直後に閉鎖されています。 (この事実は多少により、コードの、うう... 面白いフォーマットに隠されている。)

をこの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) 

writerstream1req.GetResponse()生き続けるとunflushedままと閉じられていません呼び出されます。あなたは、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) 
関連する問題