简体   繁体   English

如何在Silverlight中创建异步HttpWebRequest(F#)

[英]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. 正如我所提到的,因为Silverlight HttpWebRequest.Create在异步块内部挂起 ,我只是创建了一组回调函数来实现相同的异步块。

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 1)获取返回cookie的页面的请求2)表单发布到第二页,通过该cookie并执行身份验证

The following is the src. 以下是src。 Any suggestions and discussions are welcome and appreciated no matter about Asynchronous HttpWebRequest or about the F# code style. 无论是关于异步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
            ()

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. 只要您使用的属性的名称和类型仅匹配一种记录类型,F#通常足够聪明,可以弄清楚您的意思。

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上使用sprintf

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 . 但是由于生成的字符串被传递给StreamWriter ,后者继承自TextWriter另一个选项是使用fprintf直接写入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 : 所以,除非我错过了对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())

In the second version, getId is actually the fun at the bottom, after the let mutable... line. 在第二个版本中, getId实际上是底部的fun ,在let mutable...之后。 The fun captures requestId and then is given the name getId . fun捕获requestId ,然后给出名称getId Since requestId then goes out of scope, nobody else can change or even see it. 由于requestId超出了范围,因此没有其他人可以更改甚至看到它。

I answered to the orginal "Silverlight HttpWebRequest.Create hangs inside async block", check that ... 我回答原始的“Silverlight HttpWebRequest.Create挂在异步块内”, 检查一下 ......

In your case you of course need the authentication, but this request.ContentType <- contentType may cause some problems. 在您的情况下,您当然需要身份验证,但此request.ContentType <- contentType可能会导致一些问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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