I have a mono/.Net 4.5 app that compiles just fine. But whe I run it I get a Method missing Http.Request. The code in question is this
let private post url parser body =
let res = Http.Request (
url,
body = (body |> TextRequest),
silentHttpErrors = true,
headers = [
Accept HttpContentTypes.Json
ContentType HttpContentTypes.Json
]
)
let body =
match res.Body with
HttpResponseBody.Text str -> str
| _ -> failwith "Only text replies are supported"
if res.StatusCode >= 200 && res.StatusCode < 300 then
body |> parser
else
body |> errorParser
It doesn't seem to be related with the actual method because all method calls from FSharp.Data seems to fail.
I'm experiencing this both when running some standard nunit tests or when executing.
I would seem that the issue was that I had FSharp.Data.TypeProviders installed in the GAC. removing that
gacutil -u FSharp.Data.TypeProviders
solved it
Related
Looking for WebSocketClient example I only found simple example with a single request/response scenario.
Kind of:
type WSClientSimple (url) =
let ws = new ClientWebSocket()
let lockConnection = Object()
let connect() =
lock lockConnection ( fun () ->
if not (ws.State = WebSocketState.Open) then
ws.ConnectAsync(Uri(url), CancellationToken.None)
|> Async.AwaitTask |> Async.RunSynchronously // await
else ()
)
let receive () =
lock lockConnection ( fun () ->
let rec readStream finalText endOfMessage =
let buffer = ArraySegment(Array.zeroCreate<byte> 1024)
let result = ws.ReceiveAsync(buffer, CancellationToken.None) |> Async.AwaitTask |> Async.RunSynchronously
let text = finalText + Encoding.UTF8.GetString (buffer.Array |> Array.take result.Count)
if result.EndOfMessage then text
else readStream text true
readStream "" false
)
let sendRequest jsonMessage =
let bytes = Encoding.UTF8.GetBytes(jsonMessage:string)
let bytesMessage = ArraySegment(bytes, 0, bytes.Length)
if not (ws.State = WebSocketState.Open) then
connect()
// send request...
ws.SendAsync(bytesMessage, WebSocketMessageType.Text, true, CancellationToken.None) |> Async.AwaitTask |> Async.RunSynchronously
// ... read response
receive()
member this.SendRequest request = sendRequest request
Obviously it works with:
[<Test>]
member this.``Receive sequentially`` () =
let client = WSClientSimple("url")
for i in 1..100 do
client.SendRequest "aaa" |> ignore
and also (thanks to the orrible lock) with multiple thread using the same Client:
[<Test>]
member this.``Receive parallel on same client`` () =
let client = WSClientSimple("url")
for _ in 1..100 do
async {
client.SendRequest "aaa" |> ignore
} |> Async.Start
Now, if I really want to get the beast from WebSocket "duplex" cpmmunication I would continuosly read from the socket, send requests without any block, and distribute the received messages to the right call.
So, this is an ongoing receive function that collect all the inbound messages.
type WSClientTest2 (url:string) =
let onMessageReceived = new Event<string>()
let responseMessage = new Event<ResponseMessage>()
let receivedMesasages = System.Collections.Concurrent.ConcurrentQueue<ResponseMessage>()
let responseCallbacks = Map.empty<int, (string -> unit)>
let manageMessage (message:string) =
match message.Split(':') with
| [|id;message|] ->
responseMessage.Trigger {Id=int(id);Message=message}
receivedMesasages.Enqueue {Id=int(id);Message=message}
| _ -> ()
let startReceiving() =
let mutable counter = 1
async {
// simulate receiving from a WebSocket
while true do
System.Threading.Tasks.Task.Delay 100 |> Async.AwaitTask |> Async.RunSynchronously
onMessageReceived.Trigger (sprintf "message %d" counter)
manageMessage (sprintf "%d:message" counter)
counter <- counter + 1
} |> Async.Start
do
startReceiving()
How can I send a request and wait for the correlated response message?
This is my try:
let mutable requestId = 0
let sendRequest message: string =
let requestId = requestId+1
let received = new Event<string>()
let receivedCall = fun (msg:string) ->
received.Trigger msg
responseCallbacks.Add(requestId, receivedCall) |> ignore
let cancel = fun () -> failwith "Timeout"
async {
System.Threading.Thread.Sleep 500 // wait x seconds
cancel()
} |> Async.Start
// simulate send/receive messsage after some time
let generateRequest () =
System.Threading.Thread.Sleep 100 // wait x time for the response
responseMessage.Trigger {Id=requestId; Message=message}
generateRequest()
Async.AwaitEvent(received.Publish, cancel)
|> Async.RunSynchronously
Async.AwaitWaitHandle seems the right thing to use but I don't know how to create a WaitHandle.
I'm using Async.AwaitEvent but it seems not to work.
The cancel() is always called but it does not raise any Exception!
What could be a proper way to wait for an Event while executing a function and then check and return its content?
I also tried to use a Map<id, response> populatd with any inbound message but still I don't know how to "wait" for the proper message and also it probably requires a check for orphan response messages (add complexity).
More in general, if the resulting code is so crappy I would prefer to use a simple API for this Request/Response scenario and use the WebSocket only for a realtime update.
I'm looking for a nice solution, otherwise I think it is not really worth for the sake of performance, not for my needs.
Following version is calling all functions synchronously,
I'm looking to find out how to call asynchronous functions in parallel and return all results and errors to the caller.
Request
let requestAsync (url: string) : Async<Result<string, Error>> =
async {
Console.WriteLine ("Simulating request " + url)
try
do! Async.Sleep(1000)
return Ok (url + ": body...")
with :? WebException as e ->
return Error {code = 500; message = "Internal Server Error";}
}
Test
[<TestMethod>]
member this.TestrequestAsync() =
let urls = [|
"http://www.example.com/1";
"http://www.example.com/2";
"http://www.example.com/3";
"http://www.example.com/4";
"http://www.example.com/5";
"http://www.example.com/6";
"http://www.example.com/7";
"http://www.example.com/8";
"http://www.example.com/9";
"http://www.example.com/10";
|]
urls
|> Array.map (fun url -> requestAsync url |> Async.RunSynchronously) // Async.Parallel some mismatch
// Iterate results
Ideally to be able to match Ok and Error results while iterating through results
Edit based on the answer.
let result =
urls
|> Seq.map Entity.requestDetailAsync2
|> Async.Parallel
|> Async.RunSynchronously
result
|> Array.iter Console.WriteLine // match x with Ok and Error?
Attempt
result |> Array.iter (fun data -> match data with
| Ok result -> Console.WriteLine(result)
| Error error -> Console.WriteLine(error) )
Iteration using For in
for r in result do
match r with
| Ok re -> Console.WriteLine(re)
| Error error -> Console.WriteLine(error)
You can use Async.Parallel to run many async operations in parallel:
let results =
urls
|> Seq.map requestAsync // seq<Async<'T>>
|> Async.Parallel // async<T' []>
|> Async.RunSynchronously // T' []
Here's a very similar example on MSDN.
There may be an issue with your requestAsync function return type, or a missing type definition in your example. Here's what I used to verify the solution:
type RequestError = {
code : int
message : string
}
let requestAsync (url: string) =
async {
Console.WriteLine ("Simulating request " + url)
try
do! Async.Sleep(1000)
return Ok (url + ": body...")
with :? WebException as e ->
return Error {code = 500; message = "Internal Server Error";}
}
I'm trying to execute the following F# script code on my macbook pro using FSI in visual studio code and the ionide plugin.
#r "packages/Newtonsoft.Json.9.0.1/lib/net40/Newtonsoft.Json.dll"
#r "System.Net.Http"
open System
open System.Net.Http
open Newtonsoft.Json
let client = new HttpClient()
type AlbumInfo = { userId:int; id:int; title:string }
let url = "https://jsonplaceholder.typicode.com/albums/1"
async {
let! res = Async.AwaitTask <| client.GetAsync(url)
let! content = Async.AwaitTask <| res.Content.ReadAsStringAsync()
let x = JsonConvert.DeserializeObject<AlbumInfo>(content)
printfn "%s" x.title
} |> Async.Start
printfn "Please wait..."
But I don't get any output apart from Please wait.... However, when I put https://jsonplaceholder.typicode.com/albums/1 into the browser I get the expected Json response. So I know there's no problem reaching the API.
Also, when I run the same code in Visual Studio 2013 on my Windows 10 PC. The code produces the expected result. i.e. Please wait... and the title of the album.
Any ideas why it doesn't work correctly on my macbook?
In Visual Studio there is a process hosting FSI and keeping the thread (pool) for the async computation alive. In FSI on the command line or VS Code, FSI will just terminate as soon as the main thread has finished writing Please wait... (which typically is before the computation was even started on the thread pool).
If you want to observe the side effects of an async computation you have to await its result (in this example unit):
let computation = async {
printfn "Starting async"
let! res = Async.AwaitTask <| client.GetAsync(url)
let! content = Async.AwaitTask <| res.Content.ReadAsStringAsync()
let x = JsonConvert.DeserializeObject<AlbumInfo>(content)
printfn "Downloaded %s" x.title
}
async {
let! started = computation |> Async.StartChild
let! _ = Async.Sleep 1 // only here to get interleaved ouput
printfn "Please wait..."
let! res = started
printfn "Got result %A" res
} |> Async.RunSynchronously
will likely print:
Starting async
Please wait...
Downloaded quidem molestiae enim
Got result <null>
Sorry for newbie's question (and for my english) :)
I tries to write the following function:
the function downloads a content from URL1 (it's received as argument)
the function parses this content and extract URL2
the function downloads a content from URL2
the content from URL2 is a result of this function
if an error was occured, this function should return Nothing
I know how to execute the HTTP requests. I have a function to parse the request from URL1. But I don't know how:
to execute new request with extracted URL2
to ignore second request if URL2 isn't extracted (or error in URL1 is occured)
I principle you want something like this:
import Maybe
import Http
type Url = String
getContentFromUrl : Maybe Url -> Maybe String
getContentFromUrl url = --your implementation
extractUrlFromContent : Maybe String -> Maybe Url
extractUrlFromContent content = --your implementation
content = getContentFromUrl (Just "http://example.com")
|> extractUrlFromContent
|> getContentFromUrl
Sending an Http means talking to the outside world, which involves Signals in Elm. So the final result from URL2 will come packed in a Signal. As long as you're ok with that, you can use maybe to return the content of in a Maybe in a Signal. For example:
import Maybe
import Http
-- helper functions
isSuccess : Http.Response a -> Bool
isSuccess response = case response of
Http.Success _ -> True
_ -> False
responseToMaybe : Http.Response a -> Maybe.Maybe a
responseToMaybe response = case response of
Http.Success a -> Just a
_ -> Nothing
parseContentAndExtractUrl : String -> String
parseContentAndExtractUrl = identity -- this still requires your implementation
-- URL1
startUrl : String
startUrl = "www.example.com" -- replace this with your URL1
result1 : Signal (Http.Response String)
result1 = Http.sendGet <| constant startUrl
-- URL2
secondUrl : Signal String
secondUrl = result1
|> keepIf isSuccess (Http.Success "")
|> lift (\(Http.Success s) -> s)
|> lift parseContentAndExtractUrl
result2 : Signal (Maybe String)
result2 = secondUrl
|> Http.sendGet
|> lift responseToMaybe
Note that there are plans to make all of this easier to work with: https://groups.google.com/d/topic/elm-discuss/BI0D2b-9Fig/discussion
I've an application written in C# (.Net 3.5) with this code.
using System;
using System.Net;
string strURI = String.Format("ftp://x{0}ftp/%2F'{1}'", parm1, parm2);
FtpWebRequest ftp = (FtpWebRequest)FtpWebRequest.Create(strURI);
ftp.Proxy = null;
ftp.KeepAlive = true;
ftp.UsePassive = false;
ftp.UseBinary = false;
ftp.Credentials = new NetworkCredential("uid", "pass");
ftp.Method = WebRequestMethods.Ftp.DownloadFile;
FtpWebResponse response = (FtpWebResponse)ftp.GetResponse();
...
I've translated this into F# (.Net 4.0) to use in my application.
open System.Net
let uri = sprintf "ftp://x%sftp/%%2F'%s'" parm1 parm2
let ftp = FtpWebRequest.Create(uri) :?> FtpWebRequest
ftp.Credentials <- new NetworkCredential("uid", "pass")
ftp.Method <- WebRequestMethods.Ftp.DownloadFile
ftp.Proxy <- null
let response = ftp.GetResponse() :?> FtpWebResponse
...
At this point, FSI complains.
System.Net.WebException: The remote server returned an error: (550) File unavailable (e.g. file not found, no access).
Yet the C# application runs and successfully downloads the file. What am I missing in F# (besides the properties that aren't there in 4.0, i.e. KeepAlive, UsePassive, and UseBinary)?
The code may have errors, I don't have F# compiler right now.
open System
open System.Reflection
open System.Net
let switch_to_legacy_mode _ =
let wtype = typeof<FtpWebRequest>
let mfield = wtype.GetField("m_MethodInfo", BindingFlags.NonPublic ||| BindingFlags.Instance)
let mtype = mfield.FieldType
let knfield = mtype.GetField("KnownMethodInfo", BindingFlags.Static ||| BindingFlags.NonPublic)
let knarr = knfield.GetValue(null) :?> Array
let flags = mtype.GetField("Flags", BindingFlags.NonPublic ||| BindingFlags.Instance)
let flag_val = 0x100
for f in knarr do
let mutable ff = flags.GetValue(f) :?> int
ff <- ff ||| flag_val
flags.SetValue(f, ff)
let uri = sprintf "ftp://x%sftp/%%2F'%s'" parm1 parm2
do switch_to_legacy_mode () // Call it once before making first FTP request
let ftp = FtpWebRequest.Create(uri) :?> FtpWebRequest
ftp.Credentials <- new NetworkCredential("uid", "pass")
ftp.Method <- WebRequestMethods.Ftp.DownloadFile
ftp.Proxy <- null
let response = ftp.GetResponse() :?> FtpWebResponse
Source: http://support.microsoft.com/kb/2134299
The cause of this issue is due to a behavior change in the
System.Net.FtpWebRequest class in .Net Framework 4. There has been a
change made to the System.Net.FtpWebRequest class from .Net Framework
3.5 to .Net Framework 4 to streamline the use of the CWD protocol commands. The new implementation of the System.Net.FtpWebRequest class
prevents the send of extra CWD commands before issuing the actual
command which the user requested and instead directly sends the
requested command. For fully RFC compliant FTP servers, this should
not be an issue, however for non-fully RFC compliant servers, you will
see these types of errors.
Try this instead:
open System
open System.Net
let parm1 = "test"
let parm2 = "othertest"
let uri = String.Format("ftp://x{0}ftp/%2F'{1}'", parm1, parm2)
let ftp = FtpWebRequest.Create(uri) :?> FtpWebRequest;;
I had to add a dummy parm1 and parm2 but I'm assuming you've got parm1 and parm2 defined in your actual code. I'd guess that whatever the issue is, it lies in the uri string. That is, I think you'll find the problem by comparing uri to the known to be good uri string.