module Main
open System
open System.Threading
open System.Threading.Tasks
open NetMQ
open NetMQ.Sockets
let uri = "ipc://hello-world"
let f (token : CancellationToken) =
use server = new ResponseSocket()
use poller = new NetMQPoller()
poller.Add(server)
printfn "Server is binding to: %s" uri
server.Bind(uri)
printfn <| "Done binding."
use __ = server.ReceiveReady.Subscribe(fun x ->
if token.CanBeCanceled then poller.Stop()
)
use __ = server.SendReady.Subscribe(fun x ->
if token.CanBeCanceled then poller.Stop()
)
poller.Run()
printfn "Server closing."
server.Unbind(uri)
let src = new CancellationTokenSource()
let token = src.Token
let task = Task.Run((fun () -> f token), token)
src.CancelAfter(100)
task.Wait() // Does not trigger.
My failed attempt looks something like this. The problem is that the poller will only check the cancellation token if it gets or sends a message. I guess one way to do it would be to send a special cancel message from the client rather than these tokens, but that would not work if the server gets into a send state.
What would be a reliable way of closing the server in NetMQ?
Related
I would like to upload files using Suave. I understand that small files will automatically get written to a /tmp folder, but my files are too large for that. What I would like to do is process them as a stream and send them to long term storage, via the Suave server.
Here is my server:
#r "nuget: Suave"
open System.IO
open Suave
open Suave.Sockets
open Suave.Sockets.Control
let config = defaultConfig
let socketTask (conn : Connection, httpResult : HttpResult) =
socket {
printfn "Started socket task"
use stream = new TransportStream (conn.transport)
use reader = new StreamReader (stream)
while not reader.EndOfStream do
printfn "Reading... "
let! line = SocketOp.ofAsync (async {
let! line = reader.ReadLineAsync ()
return line
})
printfn "%s" line
printfn "Finished socket task"
return conn
}
let app : WebPart =
(fun ctx -> async {
printfn "Received request"
return
Some
{
ctx with
response =
{
ctx.response with
content = SocketTask socketTask
}
}
})
startWebServer config app
But when I run this curl command, it just hangs:
$ curl -X POST --data-binary #big-file.csv localhost:8080
$ dotnet fsi ./Suave.fsx
[17:32:05 INF] Smooth! Suave listener started in 14.279ms with binding 127.0.0.1:8080
Received request
Started socket task
I would expect the uploaded file to be printed line-by-line.
What am I missing here?
I write this echo server:
let listener=new TcpListener(IPAddress.Parse("127.0.0.1"),2000)
let rec loop (client : TcpClient,sr : StreamReader, sw : StreamWriter) =
async {
let line=sr.ReadLine()
sw.WriteLine(line)
if line="quit" then
client.Close()
else
return! loop(client,sr,sw)
}
let private startLoop (listener:TcpListener) =
while true do
let client = listener.AcceptTcpClient()
let stream = client.GetStream()
let sr = new StreamReader(stream)
let sw = new StreamWriter(stream)
sw.AutoFlush <- true
sw.WriteLine("welcome")
Async.Start(loop (client,sr,sw))
[<EntryPoint>]
let main argv =
listener.Start()
startLoop(listener)
0
when I open one or two telnet window to test it,it works fine
but when I write this test program to test it:
static void Main(string[] args)
{
for (int a = 0; a < 5; a++)
{
var client = new TcpClient("localhost", 2000);
Console.WriteLine(client.Connected);
client.close();
}
}
the test program return one or two true,but the server raise an exception:
System.Net.Sockets.SocketException:An existing connection was forcibly closed by the remote host
in line 12:let line=sr.ReadLine()
and client raise the exception:System.Net.Sockets.SocketException:Because the target computer actively refused, unable to connect
at line 16:var client = new TcpClient("localhost", 2000);
I don't know why,please help me
Your problem is that the client opens a connection and then immediately closes it.
The server however expects a "quit" message from the client before it will terminate the connection. So the server sends a "welcome" to the client, then enters the loop. Inside the loop, sr.ReadLine() is called, which waits for the client to send something over the wire.
The client never sends anything. It closes the connection. Therefore, the server's call to ReadLine aborts with the a SocketException (forcibly closed...). And you do not handle this exception, so the server dies.
Then the client tries to connect once again, with no server listening anymore. The client can't connect and you see another SocketException (actively refused...).
You should guard your server code against clients that disconnect without saying "quit" first.
I am working on Suave 1.0 + Angular 2.0 sample app and very interesting to start Suave server in watch mode, so the server watch file changes (js,css,html) in root folder and sub-folders and automatically send refresh command to all open browser tabs with my application when any file is changed.
lite-server from Angular 2 5min Quckstark can do this and it is very handy.
I think that most of watch pieces can be found in latest Steffen Forkmann's post but it is not very clean how to send refresh to the open browser tabs.
Please provide complete code of similar implementation with Suave.
The code of Suave server should looks similar to this
#r "packages/Suave/lib/net40/suave.dll"
#r "packages/FAKE/tools/FakeLib.dll"
open Fake
open Suave
open Suave.Operators
open Suave.Sockets.Control
open Suave.WebSocket
open Suave.Utils
open Suave.Files
open Suave.RequestErrors
open Suave.Filters
open System
open System.Net
let port =
let rec findPort port =
let portIsTaken =
System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()
|> Seq.exists (fun x -> x.Port = int(port))
if portIsTaken then findPort (port + 1us) else port
findPort 8083us
let logger = Logging.Loggers.ConsoleWindowLogger Logging.LogLevel.Verbose
let refreshEvent = new Event<_>()
let handleWatcherEvents (events:FileChange seq) =
for e in events do
let fi = fileInfo e.FullPath
traceImportant <| sprintf "%s was changed." fi.Name
refreshEvent.Trigger()
let socketHandler (webSocket : WebSocket) =
fun cx -> socket {
while true do
let! refreshed =
Control.Async.AwaitEvent(refreshEvent.Publish)
|> Suave.Sockets.SocketOp.ofAsync
do! webSocket.send Text (ASCII.bytes "refreshed") true
}
let cfg =
{ defaultConfig with
homeFolder = Some (__SOURCE_DIRECTORY__)
bindings =
[ HttpBinding.mk HTTP IPAddress.Loopback port ]
listenTimeout = TimeSpan.FromMilliseconds 3000. }
let app : WebPart =
choose [
Filters.log logger logFormat >=> never
Filters.path "/websocket" >=> handShake socketHandler
Filters.GET >=> Filters.path "/" >=> file "index.html"
Writers.setHeader "Cache-Control" "no-cache, no-store, must-revalidate"
>=> Writers.setHeader "Pragma" "no-cache"
>=> Writers.setHeader "Expires" "0"
>=> browseHome
NOT_FOUND "Found no handlers."
]
let watcher =
!! ("app/*.js")
++ ("*.html")
|> WatchChanges handleWatcherEvents
try
System.Diagnostics.Process.Start(sprintf "http://localhost:%d/index.html" port) |> ignore
startWebServer cfg app
finally
watcher.Dispose()
So we setup watcher that handle changes in js(generated by TypeScript) and html files and send refresh command to the client, but in the same time we need to add following code to the head section of index.html to handle refresh on the client side
<!-- 3. Listen on refresh events from the server -->
<script language="javascript" type="text/javascript">
function init()
{
websocket = new WebSocket("ws://"+window.location.host+"/websocket");
websocket.onmessage = function(evt) { location.reload(); };
}
window.addEventListener("load", init, false);
</script>
The full demo app you can find here
I'm trying to make an asynchronous web request to a URL that will return if the request takes too long. I'm using the F# asynchronous workflow and the System.Net.Http library to do this.
However, I am unable to catch the Task/OperationCancelledExceptions that are raised by the System.Net.Http library in the async workflow. Instead, the exception is raised at the Async.RunSynchronously method, as you can see in this stack trace:
> System.OperationCanceledException: The operation was canceled. at
> Microsoft.FSharp.Control.AsyncBuilderImpl.commit[a](Result`1 res)
> at
> Microsoft.FSharp.Control.CancellationTokenOps.RunSynchronously[a](CancellationToken
> token, FSharpAsync`1 computation, FSharpOption`1 timeout) at
> Microsoft.FSharp.Control.FSharpAsync.RunSynchronously[T](FSharpAsync`1
> computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken)
> at <StartupCode$FSI_0004>.$FSI_0004.main#()
The code:
#r "System.Net.Http"
open System.Net.Http
open System
let readGoogle () = async {
try
let request = new HttpRequestMessage(HttpMethod.Get, "https://google.co.uk")
let client = new HttpClient()
client.Timeout <- TimeSpan.FromSeconds(0.01) //intentionally low to always fail in this example
let! response = client.SendAsync(request, HttpCompletionOption.ResponseContentRead) |> Async.AwaitTask
return Some response
with
| ex ->
//is never called
printfn "TIMED OUT"
return None
}
//exception is raised here
readGoogle ()
|> Async.RunSynchronously
|> ignore
Cancellation was always different from the error. In your case you can override default behavior of AwaitTask that invokes "cancel continuation" if task is cancelled and handle it differently:
let readGoogle () = async {
try
let request = new HttpRequestMessage(HttpMethod.Get, "https://google.co.uk")
let client = new HttpClient()
client.Timeout <- TimeSpan.FromSeconds(0.01) //intentionally low to always fail in this example
return! (
let t = client.SendAsync(request, HttpCompletionOption.ResponseContentRead)
Async.FromContinuations(fun (s, e, _) ->
t.ContinueWith(fun (t: Task<_>) ->
// if task is cancelled treat it as timeout and process on success path
if t.IsCanceled then s(None)
elif t.IsFaulted then e(t.Exception)
else s(Some t.Result)
)
|> ignore
)
)
with
| ex ->
//is never called
printfn "TIMED OUT"
return None
}
EDIT for moderators
I had this issue this morning, but the problem has been somehow solved on its own. If it were to come back and I could exactly tell what is happening I would reopen another question with more details.
Thx
I have the following code to start a http listener (I have so far copied and pasted a lot from this series of article )
httpAgent.fs :
namespace Server.Core
open System.Net
open System.Threading
type Agent<'T> = MailboxProcessor<'T>
/// HttpAgent that listens for HTTP requests and handles
/// them using the function provided to the Start method
type HttpAgent private (url, f) as this =
let tokenSource = new CancellationTokenSource()
let agent = Agent.Start((fun _ -> f this), tokenSource.Token)
let server = async {
use listener = new HttpListener()
listener.Prefixes.Add(url)
listener.Start()
while true do
let! context = listener.AsyncGetContext()
agent.Post(context) }
do Async.Start(server, cancellationToken = tokenSource.Token)
/// Asynchronously waits for the next incomming HTTP request
/// The method should only be used from the body of the agent
member x.Receive(?timeout) = agent.Receive(?timeout = timeout)
/// Stops the HTTP server and releases the TCP connection
member x.Stop() = tokenSource.Cancel()
/// Starts new HTTP server on the specified URL. The specified
/// function represents computation running inside the agent.
static member Start(url, f) =
new HttpAgent(url, f)
httpServer.fs :
module httpServer
open Server.Core
let execute = fun ( server : HttpAgent) -> async {
while true do
let! ctx = server.Receive()
ctx.Response.Reply(ctx.Request.InputString) }
This code runs well in a console project (ie: I can access it with a browser, it does find it) :
[<EntryPoint>]
let main argv =
let siteRoot = #"D:\Projects\flaming-octo-spice\src\Site"
let url = "http://localhost:8082/"
let server = HttpAgent.Start(url, httpServer.execute)
printfn "%A" argv
let s = Console.ReadLine()
// Stop the HTTP server and release the port 8082
server.Stop()
0 // return an integer exit code
whereas in my test, I cannot access the server. I have even put some breakpoint in order to check with my browser if the server was up and running , but chrome tells me no host exists with ths url.
namespace UnitTestProject1
open System
open Microsoft.VisualStudio.TestTools.UnitTesting
open Server.Core
open System.Net.Http
[<TestClass>]
type HttpServerTests() =
[<TestMethod>]
member x.Should_start_a_web_site_with_host_address () =
let host = "http://localhost:8082/"
let server = HttpAgent.Start(host, httpServer.execute)
let url = "http://localhost:8082/test/url"
let client = new HttpClient()
let response = client.GetAsync(url)
Assert.IsTrue(response.Result.IsSuccessStatusCode )
Thanks for any enlightment...
You're starting server at port 8092, but client tries to access it at 8082.