open System open System.IO open System.Net open System.Threading open System.Collections.Generic /// Type alias for the MailboxProcessor type type Agent<'T> = MailboxProcessor<'T> // System.Net Extensions type System.Net.HttpListener with /// Asynchronously retrieves the next incoming request member x.AsyncGetContext() = Async.FromBeginEnd(x.BeginGetContext, x.EndGetContext) type System.Net.WebClient with /// Asynchronously downloads data from the member x.AsyncDownloadData(uri) = Async.FromContinuations(fun (cont, econt, ccont) -> x.DownloadDataCompleted.Add(fun res -> if res.Error <> null then econt res.Error elif res.Cancelled then ccont (new OperationCanceledException()) else cont res.Result) x.DownloadDataAsync(uri) ) type System.Net.HttpListener with /// Starts an HTTP server on the specified URL with the /// specified asynchronous function for handling requests static member Start(url, f) = let tokenSource = new CancellationTokenSource() Async.Start ( ( async { use listener = new HttpListener() listener.Prefixes.Add(url) listener.Start() while true do let! context = listener.AsyncGetContext() Async.Start(f context, tokenSource.Token) }), cancellationToken = tokenSource.Token) tokenSource /// Starts an HTTP server on the specified URL with the /// specified synchronous function for handling requests static member StartSynchronous(url, f) = HttpListener.Start(url, f >> async.Return) // [snippet:Common functions and declarations] // NOTE: This snippet uses System.Net extensions from: http://fssnip.net/6d // (such as HttpListener.Start and WebClient.AsyncDownloadData) // Location where the proxy copies content from let root = "http://msdn.microsoft.com" // Maps requests from local URL to target URL let getProxyUrl (ctx:HttpListenerContext) = Uri(root + ctx.Request.Url.PathAndQuery) // Handle exception asynchronously - generate page with message let asyncHandleError (ctx:HttpListenerContext) (e:exn) = async { use wr = new StreamWriter(ctx.Response.OutputStream) wr.Write("
" + e.Message + "
") ctx.Response.Close() } // [/snippet] module Chunked = // [snippet:Extension #1: Chunked proxy server] /// Handles request using asynchronous workflows - The content /// is downloaded and sent to the caller in chunks, so the proxy /// is more efficient and doesn't read entire file in memory let asyncHandleRequest (ctx:HttpListenerContext) = async { // Initialize HTTP connection to the server let request = HttpWebRequest.Create(getProxyUrl(ctx)) use! response = request.AsyncGetResponse() use stream = response.GetResponseStream() ctx.Response.SendChunked <- true // Asynchronous loop to copy data let count = ref 1 let buffer = Array.zeroCreate 4096 while count.Value > 0 do let! read = stream.AsyncRead(buffer, 0, buffer.Length) do! ctx.Response.OutputStream.AsyncWrite(buffer, 0, read) count := read ctx.Response.Close() } // Start HTTP proxy that handles requests asynchronously using chunking let token = HttpListener.Start("http://localhost:8080/", asyncHandleRequest) token.Cancel() // [/snippet] module Cached = // [snippet:Extension #2: Agent-based in-memory cache (Part 1)] /// The cache supports messages for retrieving and adding content type CacheMessage = | TryGet of Uri * AsyncReplyChannel