Как создать асинхронный запрос HttpWebRequest в Silverlight (F #) - PullRequest
6 голосов
/ 21 июня 2010

Как я уже говорил, поскольку Silverlight HttpWebRequest.Create висит внутри асинхронного блока , я просто создал пакет функций обратного вызова для реализации того же асинхронного блока.

Процесс входа в систему требует двух шагов:

1) Получить запрос на страницу, которая возвращает куки 2) Форма Публикация на второй странице, которая передает этот cookie и выполняет аутентификацию

Ниже приведен источник. Любые предложения и обсуждения приветствуются и приветствуются независимо от асинхронного запроса 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
            ()

Ответы [ 3 ]

1 голос
/ 22 июня 2010

Я обычно держу местное государство очень локально, пряча его в закрытии.Итак, если я не пропустил ссылку на 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 затем выходит из области видимости, никто другой не может изменить или даже увидеть его.

1 голос
/ 21 июня 2010

Обычно при создании экземпляров записи нет необходимости полностью квалифицировать каждое свойство, как вы делаете.

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
}

Кроме того, я могу склоняться к использованию sprintf сверх String.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 голосов
/ 01 июня 2011

Я ответил на оригинал «Silverlight HttpWebRequest. Создание зависает внутри асинхронного блока», проверьте, что ...

В вашем случае вам, конечно, нужна аутентификация, но это request.ContentType <- contentType может вызвать некоторые проблемы.

...