2010-06-21 34 views
6

正如我所提到的,因爲Silverlight HttpWebRequest.Create hangs inside async block,我只是創建了一組回調函數來實現相同的異步塊。如何在Silverlight中創建異步HttpWebRequest(F#)

登錄過程需要兩個步驟:

1)獲取請求以返回一個cookie 2)形式發佈到該傳遞該cookie瓦特/它的第二頁和執行認證

一個頁以下是src。無論是關於異步HttpWebRequest還是關於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 
      () 

回答

1

正常情況下,當創建記錄實例時,您不需要完全限定每個屬性。

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

只要屬性的名稱和類型您只使用匹配一個記錄類型,F#是一般足夠聰明弄清楚你是什麼意思。

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

另外,我可能會傾向於使用sprintfString.Format這裏:

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" 

但由於得到的字符串被傳遞到StreamWriter,從TextWriter繼承了另一種選擇是使用fprintf它直接寫入TextWriter

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

爲了創建記錄實例,我試過了你的方法。但是,它不能在我的代碼中工作,因爲還有另一種記錄類型(泛型)具有與此相同的字段。 「讓身份驗證:AuthenticationRecord = {...}」可以工作,也可以應用於其他模塊中定義的記錄類型。 fprintf行非常整齊。非常好。非常感謝〜! – 2010-06-21 17:39:15

1

我通常保持本地狀態非常地方,隱藏在關閉。所以,除非我錯過了一個參考requestId,我會提出這裏面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()) 

在第二個版本,getId實際上是在fun底部,let mutable...行之後。 fun捕獲requestId,然後被命名爲getId。由於requestId超出範圍,因此沒有其他人可以更改或甚至看到它。

0

我回答的原單「的Silverlight HttpWebRequest.Create異步塊內掛起」,check that ...

在你的情況,你當然需要驗證,但這種request.ContentType <- contentType可能會引起一些問題。