2017-06-21 3 views
1

J'essaie de porter du code C# sur F #.System.Net.WebException lors du portage de C# à F #

Le code C# a été prise d'ici (et légèrement dénudés): 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)) 

Le code C# ci-dessus fonctionne très bien. Cependant, lorsque j'essaie d'exécuter (ce que je pense être) un code équivalent à F #, sans réel changement, je reçois un message d'erreur.

Le code est exécuté à partir du même ordinateur, de la même installation VS et avec exactement les mêmes 4 arguments.

Le message d'erreur que je reçois est le deuxième avant-dernière ligne:

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) 

Je suis un peu perdu, à mon esprit les deux implémentations de code doivent être identiques?

+2

Vous pourriez avoir des raisons très spécifiques au port le code C# directement, mais il y a aussi des clients plus idimatic qui enveloppent HttpWebRequest, par exemple [Http.fs] (https://github.com/haf/Http. fs) ou [Http Utilities] (http://fsharp.github.io/FSharp.Data/library/Http.html) dans FSharp.Data. – s952163

+0

La méthode C# consiste à utiliser HttpClient, pas HttpWebRequest directement.Les gens n'enchaînent pas les appels à GetResponse, GetRresponseStream non plus - quel est le * point * de lecture d'une réponse s'il n'y en a pas, par exemple si le code d'état est une erreur? –

+0

Enfin, n'utilisez pas un encodage * random * comme vous le faites ici - Encoding.Default est la page de code ANSI du système. Presque tous les services Web utilisent UTF8, à l'exception de certains qui peuvent utiliser UTF16. Aucun n'utilise les pages de code ANSI. Dans tous les cas, le codage doit être spécifié dans les en-têtes appropriés –

Répondre

1

Ce n'est pas « la voie C# » pour faire un appel HTTP POST. La manière typique, dans toutes les versions .NET supportées (c.-à-d. 4.5.2 et plus tard) est d'utiliser HttpClient. Même avec HttpWebRequest, il y a trop d'appels redondants ou contradictoires, comme l'utilisation des informations d'identification par défaut (authentification Windows)

La voie C# est la suivante:

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

Pour utiliser un certificat client, vous devez créer l'instance de client à l'aide d'un gestionnaire HTTP personnalisé:

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

Écrire le même code dans F # est directe:

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 
    } 

Le client, handler, est thread-safe et peut être réutilisé pour pouvoir être stocké dans des champs. Réutiliser le même client signifie que le système d'exploitation n'a pas besoin de créer une nouvelle connexion TCP/IP à chaque fois, ce qui améliore les performances. Il est préférable de créer le client séparément. :

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

Dans ce code 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 et stream1 sont vidées et fermé immédiatement après l'writer.Write appel est terminé, avant d'appeler request.GetResponse(). (Ce fait est quelque peu obscurci en raison de la, uhh .. intéressante mise en forme de votre code.)

Dans ce code 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 et stream1 rester en vie et restent unflushed et non fermée lorsque req.GetResponse() est appelé; vous devez les mettre dans un cadre artificiel pour obtenir le même comportement que 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)