Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
            ()
like image 830
J.K.J Avatar asked Jun 21 '10 14:06

J.K.J


2 Answers

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"
like image 171
Joel Mueller Avatar answered Sep 21 '22 19:09

Joel Mueller


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.

like image 30
Nathan Shively-Sanders Avatar answered Sep 17 '22 19:09

Nathan Shively-Sanders