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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With