简体   繁体   中英

How to Create Asynchronous HttpWebRequest in Silverlight( F#)

As I mentioned, because Silverlight HttpWebRequest.Create hangs inside async block , I just created a bundle of callback functions to implement the same async block.

The login process requires two steps :

1) Get request to a page that returns a cookie 2) Form Post to a second page that passes that cookie w/ it and performs the authentication

The following is the src. Any suggestions and discussions are welcome and appreciated no matter about Asynchronous HttpWebRequest or about the F# code style.

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

Normally when creating instances of a record, there's no need to fully-qualify each property as you're doing.

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

As long as the names and types of the properties you're using only match one record type, F# is generally smart enough to figure out what you meant.

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

Also, I might be inclined to use sprintf over String.Format here:

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"

But since the resulting string is being passed to a StreamWriter , which inherits from TextWriter another option would be to use fprintf which writes directly to a TextWriter .

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

I usually keep local state very local, hiding it inside a closure. So, unless I missed a reference to requestId , I would move it inside 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())

In the second version, getId is actually the fun at the bottom, after the let mutable... line. The fun captures requestId and then is given the name getId . Since requestId then goes out of scope, nobody else can change or even see it.

I answered to the orginal "Silverlight HttpWebRequest.Create hangs inside async block", check that ...

In your case you of course need the authentication, but this request.ContentType <- contentType may cause some problems.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM