2010-06-21 9 views
6

Como mencioné, porque Silverlight HttpWebRequest.Create hangs inside async block, acabo de crear un conjunto de funciones de devolución de llamada para implementar el mismo bloque asincrónico.Cómo crear una HttpWebRequest asíncrona en Silverlight (F #)

El proceso de inicio de sesión requiere dos pasos:

1) Obtener solicitud a una página que devuelve una cookie 2) el envío de formularios a una segunda página que pasa esa galleta w/ella y realiza la autenticación

El siguiente es el src. Cualquier sugerencia y discusión son bienvenidas y apreciadas sin importar la Asynchronous HttpWebRequest o el estilo de código F #.

module File1 

open System 
open System.IO 
open System.Net 
open System.Text 
open System.Security 
open System.Runtime.Serialization 
open System.Collections.Generic 
open JsonData 
open System.Net.Browser 
open System.Threading 


module rpc = 
    let mutable BASE_DNS = "" 

    let mutable requestId : int = 0 
    let getId() = 
     requestId <- requestId + 1 
     requestId.ToString() 

    module internal Helper = 
     ///<Summary> 
     ///Transfer data from Security.loginToRpc to Helper.FetchCookieCallback 
     ///</Summary> 
     type LoginRequestRecord = { 
       Request : HttpWebRequest; 
       UserName : string; 
       Password : string; 
       AuthenticationUrl : string; 
       CallbackUI : (bool -> unit) 
       } 

     ///<Summary> 
     ///Transfer data from Helper.FetchCookieCallback to Helper.requestAuthenticationCallback 
     ///</Summary> 
     type AuthenticationRecord = { 
       Request : HttpWebRequest; 
       UserName : string; 
       Password : string; 
       CallbackUI : (bool -> unit) 
       } 

     ///<Summary> 
     ///Transfer data from Helper.requestAuthenticationCallback to Helper.responseAuthenticationCallback 
     ///</Summary> 
     type ResponseAuthenticationRecord = { 
       Request : HttpWebRequest; 
       CallbackUI : (bool -> unit) 
       } 

     ///<Summary> 
     ///The cookieContainer for all the requests in the session 
     ///</Summary> 
     let mutable cookieJar = new CookieContainer() 

     ///<summary> 
     ///Function: Create HttpRequest 
     ///Param: string 
     ///Return: HttpWebRequest 
     ///</summary> 
     let internal createHttpRequest (queryUrl : string) = 
      let uri = new Uri(queryUrl) 
      let request : HttpWebRequest = 
       downcast WebRequestCreator.ClientHttp.Create(
        new Uri(queryUrl, UriKind.Absolute)) 
      request 

     ///<summary> 
     ///Function: set request whose method is "GET". 
     ///Attention: no contentType for "GET" request~!!!!!!!!!!!!!!!! 
     ///Param: HttpWebRequest 
     ///Return: unit 
     ///</summary> 
     let internal requestGetSet (request : HttpWebRequest) = 
      request.Method <- "GET" 

     ///<summary> 
     ///Function: set request whose method is "POST" and its contentType 
     ///Param: HttpWebRequest and contentType string 
     ///Return: unit 
     ///</summary> 
     let internal requestPostSet (request : HttpWebRequest) contentType = 
      request.Method <- "POST" 
      request.ContentType <- contentType 

     ///<summary> 
     ///Function: Callback function inluding EndGetResponse method of request 
     ///Param: IAsyncResult includes the information of HttpWebRequest 
     ///Return: unit 
     ///</summary> 
     let internal responseAuthenticationCallback (ar : IAsyncResult) = 
      let responseAuthentication : ResponseAuthenticationRecord 
        = downcast ar.AsyncState 
      try 
       let response = responseAuthentication.Request.EndGetResponse(ar) 
       //check whether the authentication is successful, 
       //which may be changed later into other methods 
       match response.ContentLength with 
        | -1L -> responseAuthentication.CallbackUI true 
        | _ -> responseAuthentication.CallbackUI false 
       () 
      with 
       | Ex -> responseAuthentication.CallbackUI false 

     ///<summary> 
     ///Function: Callback function for user to log into the website 
     ///Param: IAsyncResult includes the information of 
     ///HttpWebRequest and user's identity 
     ///Return: unit 
     ///</summary> 
     let internal requestAuthenticationCallback (ar : IAsyncResult) = 
      let authentication : AuthenticationRecord = downcast ar.AsyncState 
      try 
       let requestStream = authentication.Request.EndGetRequestStream(ar) 
       let streamWriter = new StreamWriter(requestStream) 
       streamWriter.Write(
        String.Format(
         "j_username={0}&j_password={1}&login={2}", 
         authentication.UserName, 
         authentication.Password, 
         "Login")) 
       streamWriter.Close() 
       let responseAuthentication = { 
        ResponseAuthenticationRecord.Request = authentication.Request 
        ResponseAuthenticationRecord.CallbackUI = authentication.CallbackUI 
        } 
       authentication.Request.BeginGetResponse(
        new AsyncCallback(responseAuthenticationCallback), 
        responseAuthentication) 
        |> ignore 
      with 
       | Ex -> authentication.CallbackUI false 
      () 

     ///<summary> 
     ///This is a magic number to check 
     ///whether the first request have got the cookie from the server-side, 
     ///which should be changed later 
     ///</summary> 
     let countHeadersAfterGetCookie = 8 

     ///<summary> 
     ///Function: Callback function to get the cookie and 
     ///Param: IAsyncResult includes the information of 
     ///login request, username, password and callbackUI 
     ///Return: 
     ///</summary> 
     let internal FetchCookieCallback (ar : IAsyncResult) = 
      let loginRequest : LoginRequestRecord = downcast ar.AsyncState 
      try 
       let response = loginRequest.Request.EndGetResponse(ar) 
       let request : HttpWebRequest 
        = createHttpRequest loginRequest.AuthenticationUrl 
       requestPostSet request "application/x-www-form-urlencoded" 
       request.CookieContainer <- cookieJar 

       //if the cookie is got, call the callback function; or else, return to UI 
       match response.Headers.Count with 
       | countHeadersAfterGetCookie -> 
        let authentication = { 
         AuthenticationRecord.Request = request; 
         AuthenticationRecord.UserName = loginRequest.UserName; 
         AuthenticationRecord.Password = loginRequest.Password; 
         AuthenticationRecord.CallbackUI = loginRequest.CallbackUI 
         } 
        request.BeginGetRequestStream(
          new AsyncCallback(requestAuthenticationCallback), 
          authentication) 
        |> ignore 
        () 
       | _ -> 
        loginRequest.CallbackUI false 
        () 
      with 
       | Ex -> loginRequest.CallbackUI false 

    module Security = 
     ///<summary> 
     ///Function: Use the async workflow around 2 we calls: 
     ///   1. get the cookie; 2. log into the website 
     ///Param: UserName and password 
     ///Return: unit 
     ///</summary> 
     let loginToRpc (userName : string) 
         (password : string) 
         (callbackUI : (bool-> unit)) = 
      let sessionIdUrl = BASE_DNS 
      let authenticationUrl = BASE_DNS + "..................." 
      let request : HttpWebRequest = Helper.createHttpRequest sessionIdUrl 
      Helper.requestGetSet(request) 
      request.CookieContainer <- Helper.cookieJar 
      let loginRequest = { 
       Helper.LoginRequestRecord.Request   = request 
       Helper.LoginRequestRecord.UserName   = userName 
       Helper.LoginRequestRecord.Password   = password 
       Helper.LoginRequestRecord.AuthenticationUrl = authenticationUrl 
       Helper.LoginRequestRecord.CallbackUI  = callbackUI 
       } 
      request.BeginGetResponse(new 
        AsyncCallback(Helper.FetchCookieCallback), 
        loginRequest) 
        |> ignore 
      () 

Respuesta

1

Normalmente, al crear instancias de un registro, no hay necesidad de calificar completamente cada propiedad como lo hace.

let authentication = { 
    AuthenticationRecord.Request = request; 
    AuthenticationRecord.UserName = loginRequest.UserName; 
    AuthenticationRecord.Password = loginRequest.Password; 
    AuthenticationRecord.CallbackUI = loginRequest.CallbackUI 
    } 

Mientras los nombres y tipos de las propiedades que están usando solamente un tipo de registro que coincida, F # es generalmente lo suficientemente inteligente como para saber lo que quería decir.

let authentication = { 
    Request = request; 
    UserName = loginRequest.UserName; 
    Password = loginRequest.Password; 
    CallbackUI = loginRequest.CallbackUI 
} 

Además, yo podría estar inclinado a utilizar sprintf sobre String.Format aquí:

String.Format(
    "j_username={0}&j_password={1}&login={2}", 
    authentication.UserName, 
    authentication.Password, 
    "Login")) 

sprintf "j_username=%s&j_password=%s&login=%s" 
    authentication.UserName authentication.Password "Login" 

Pero dado que la cadena resultante se pasa a un StreamWriter, que hereda de TextWriter otra opción sería utilizar fprintf que escribe directamente en TextWriter.

fprintf streamWriter "j_username=%s&j_password=%s&login=%s" 
    authentication.UserName authentication.Password "Login" 
+0

Para crear una instancia de registro, he intentado su método. Sin embargo, no funcionó solo en mi código, porque hay otro tipo de registro (genérico) que tiene el mismo campo que este. "let authentication: AuthenticationRecord = {...}" puede funcionar y también aplicarse al tipo de registro definido en otro módulo. La línea fprintf es muy ordenada. muy agradable. Muchas gracias ~! –

1

Suelo mantener el estado local muy local, ocultándolo dentro de un cierre. Por lo tanto, a menos que echaba de menos una referencia a requestId, me muevo dentro getId:

let mutable requestId : int = 0 
let getId() = 
    requestId <- requestId + 1 
    requestId.ToString() 

// changes to: 
let getId = 
let mutable requestId : int = 0 
(fun() -> 
    requestId <- requestId + 1 
    requestId.ToString()) 

En la segunda versión, getId es en realidad el fun en la parte inferior, después de la línea let mutable.... El fun captura requestId y luego recibe el nombre getId. Dado que requestId se sale del alcance, nadie más puede cambiarlo o incluso verlo.

0

que respondieron a la original "Silverlight HttpWebRequest.Create cuelga dentro del bloque asíncrono", check that ...

En el caso de que, por supuesto, necesita la autenticación, pero esto request.ContentType <- contentType puede causar algunos problemas.

Cuestiones relacionadas