Using node modules from fable - f#

I'm trying to import a node module in my fable code. Being new to fable I did expect so problems and understanding the import flow seems to be one of those. I have the below code which compiles fine but fails run time with Cannot read property 'request' of undefined on the line of the printfn statement
module Session =
let inline f (f: 'a->'b->'c->'d) = Func<_,_,_,_> f
[<Import("default","request")>]
type Http =
abstract request : string -> System.Func<obj,obj,obj,unit> -> unit
let http : Http = failwith "js only"
let start () =
http.request "http://dr.dk" (ff (fun error response body ->
printfn "%A" body
))
do
start()

I was able to get your example working in the Fable REPL:
open System
open Fable
open Fable.Core
open Fable.Core.JS
open Fable.Core.JsInterop
type RequestCallback = Func<obj, obj, obj, unit>
type Request = Func<string, RequestCallback, unit>
[<ImportDefault("request")>]
let request : Request = jsNative
let start () =
request.Invoke("http://dr.dk", (fun error response body ->
console.log("error", error)
console.log("response", response)
console.log("body", body)
))
start ()
And here is the JavaScript that it generated:
import request from "request";
import { some } from "fable-library/Option.js";
export function start() {
request("http://dr.dk", (error, response, body) => {
console.log(some("error"), error);
console.log(some("response"), response);
console.log(some("body"), body);
});
}
start();
Note that there are bindings for many modules already. For this particular task, I would suggest using Fable.Fetch. If you want a library that works in the browser and .NET, try Fable.SimpleHttp.

Related

How can I receive data by POST in Hyper?

What I want to do is really what the title says. I would like to know how I can receive data per post in hyper, for example, suppose I execute the following command (with a server in hyper running on port :8000):
curl -X POST -F "field=#/path/to/file.txt" -F "tool=curl" -F "other-file=#/path/to/other.jpg" http://localhost:8000
Now, I'm going to take parf of the code on the main page of hyper as an example:
use std::{convert::Infallible, net::SocketAddr};
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};
async fn handle(_: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new("Hello, World!".into()))
}
#[tokio::main]
async fn main() {
let addr = SocketAddr::from(([127, 0, 0, 1], 8000));
let make_svc = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(handle))
});
let server = Server::bind(&addr).serve(make_svc);
if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
}
So, now, with this basic code, how can I receive the data per post that my curl command above would send? How do I adapt my code to read the data? I've tried to search the internet, but what I found was that hyper doesn't actually split the request body depending on the HTTP method, it's all part of the same body. But I haven't been able to find a way to process data like the above with code like mine. Thanks in advance.
Edit
I tried the exact code that they left me in the answer. That is, this code:
async fn handle(req: Request<Body>) -> Result<Response<Body>, Infallible> {
let mut files = multipart::server::Multipart::from(req);
.....
}
But I get this error:
expected struct multipart::server::Multipart, found struct
hyper::Request
How can I solve that?
It is a single body, but the data is encoded in a way that contains the multiple files.
This is called multipart, and in order to parse the body correctly you need a multipart library such as https://crates.io/crates/multipart
To hyper integration you need to add the feature flag hyper in Cargo.toml
multipart = { version = "*", features = ["hyper"] }
Then
async fn handle(mut files: multipart::server::Multipart) -> Result<Response<Body>, Infallible> {
files.foreach_entry(|field| {
// contains name, filename, type ..
println!("Info: {:?}",field.headers);
// contains data
let mut bytes:Vec<u8> = Vec::new();
field.data.read_to_end(&mut bytes);
});
Ok(Response::new("Received the files!".into()))
}
You can also use it like this
async fn handle(req: Request<Body>) -> Result<Response<Body>, Infallible> {
let mut files = multipart::server::Multipart::from(req);
.....
}

PlayWright Code - Convert Async C# Code to F#

I'm having a bit of an issue converting some Microsoft playwright code from C# to F#
Specifically this code: https://playwright.dev/dotnet/docs/navigations#multiple-navigations
// Running action in the callback of waitForNavigation prevents a race
// condition between clicking and waiting for a navigation.
await page.RunAndWaitForNavigationAsync(async () =>
{
// Triggers a navigation with a script redirect.
await page.ClickAsync("a");
}, new PageWaitForNavigationOptions
{
UrlString = "**/login"
});
My F# code is a little separated and specific to my requirements - but here is the attempt so far ( which doesn't work )
let waitNavOptions = new PageWaitForNavigationOptions(UrlRegex=Regex("dashboard|login",RegexOptions.IgnoreCase))
do! Async.AwaitTask(page.Value.RunAndWaitForNavigationAsync(page.Value.ClickAsync("#xl-form-submit"),waitNavOptions))
let waitNavOptions = PageRunAndWaitForNavigationOptions(UrlRegex=Regex("dashboard|login",RegexOptions.IgnoreCase))
do!
page.Value.RunAndWaitForNavigationAsync(
(fun () -> page.Value.ClickAsync("#xl-form-submit")),
waitNavOptions)
|> Async.AwaitTask
|> Async.Ignore
There were a few things to fix here:
Changed PageWaitForNavigationOptions to PageRunAndWaitForNavigationOptions
Change the first method argument to a function returning a task, instead of just a task.
Ignore the Async result at the end so that do! is allowed

How to cancel a `ResponseSocket` server?

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?

Post to Restful api?

I'm writing the following code to post to a Web API. However, I got compiler error on the line of client.PostAsJsonAsync. The error message is
Error This expression was expected to have type
Async<'a>
but here has type
Tasks.Task<HttpResponseMessage>
code:
[<CLIMutable>]
type Model = { ..... }
let PostIt params = async {
use client = new HttpClient()
let content = { ..... } // a Model built from params
let! response = client.PostAsJsonAsync("http://...", content) // Error!
return response }
What's the best way to handle Restful API in F#? I'm using Fsharp.Data.
It seems like you need to use Async.AwaitTask:
let! response = Async.AwaitTask (client.PostAsJsonAsync("http://...", content))
Or using the |> operator:
let! response = client.PostAsJsonAsync("http://...", content) |> Async.AwaitTask
If you already have an F# Data reference, you can also do this using the F# Data HTTP utilities, which provides an F#-friendly API for making HTTP requests.
async {
let! response =
Http.AsyncRequest
( "http://httpbin.org/post", httpMethod = "POST",
headers = [ ContentType HttpContentTypes.Json ],
body = TextRequest """ {"test": 42} """)
return response }
F# Data will not automatically serialize data for you though, so the drawback of using these utilities is that you'll need to serialize the data explicitly before making the request.

async computation doesn't catch OperationCancelledException

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
}

Resources