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.
Related
There's lower level approach to writing and reading stream for web requests, where you read or write as data comes in, please help with a snippet.
let request = WebRequest.CreateHttp url
request.Method <- "PUT"
async {
request.ContentLength <- (int64) schema.Length
use! requestStream = request.GetRequestStreamAsync() |> Async.AwaitTask
requestStream.Write(Encoding.UTF8.GetBytes(schema), 0, schema.Length)
requestStream.Close()
use! response = request.AsyncGetResponse()
use stream = response.GetResponseStream()
use streamReader = new StreamReader(stream)
let! data = streamReader.ReadToEndAsync() |> Async.AwaitTask
return Ok(data)
}
References used for the above code.
http://www.fssnip.net/7PK/title/Send-async-HTTP-POST-request
Instead of using ReadToEndAsync, use ReadAsync in a while loop. There are plenty of examples in C# about how to use these api's here is a simple one: ReadAsync get data from buffer
Also there are Async based stream apis in FSharp.Core here a code sample: http://www.fssnip.net/nP/title/Async-demo
I am new in Spring 5 and Reactive Programming. My problem is creating the export feature for the database by a rest API.
User hits GET request -> Server reads data and returns data as a zip file. Because zip file is large, so I need to stream these data.
My code as below:
#GetMapping(
value = "/export",
produces = ["application/octet-stream"],
headers = [
"Content-Disposition: attachment; filename=\"result.zip\"",
"Content-Type: application/zip"])
fun streamData(): Flux<Resource> = service.export()
I use curl as below:
curl http://localhost/export -H "Accept: application/octet-stream"
But it always returns 406 Not Acceptable.
Anyone helps?
Thank you so much
The headers attribute of the #GetMapping annotation are not headers that should be written to the HTTP response, but mapping headers. This means that your #GetMapping annotation requires the HTTP request to contain the headers you've listed. This is why the request is actually not mapped to your controller handler.
Now your handler return type does not look right - Flux<Resource> means that you intend to return 0..* Resource instances and that they should be serialized. In this case, a return type like ResponseEntity<Resource> is probably a better choice since you'll be able to set response headers on the ResponseEntity and set its body with a Resource.
Is it right, man? I still feel it's not good with this solution at the last line when using blockLast.
#GetMapping("/vehicle/gpsevent", produces = ["application/octet-stream"])
fun streamToZip(): ResponseEntity<FileSystemResource> {
val zipFile = FileSystemResource("result.zip")
val out = ZipOutputStream(FileOutputStream(zipFile.file))
return ResponseEntity
.ok().cacheControl(CacheControl.noCache())
.header("Content-Type", "application/octet-stream")
.header("Content-Disposition", "attachment; filename=result.zip")
.body(ieService.export()
.doOnNext { print(it.key.vehicleId) }
.doOnNext { it -> out.putNextEntry(ZipEntry(it.key.vehicleId.toString() + ".json")) }
.doOnNext { out.write(it.toJsonString().toByteArray(charset("UTF-8"))) }
.doOnNext { out.flush() }
.doOnNext { out.closeEntry() }
.map { zipFile }
.doOnComplete { out.close() }
.log()
.blockLast()
)
}
I have an F# funciton that uses a static instance of HttpClient:
let executeRequest request =
async {
let! response = StaticHttpClient.Instance.SendAsync(request) |> Async.AwaitTask
let! stream = response.Content.ReadAsStreamAsync() |> Async.AwaitTask
return (stream, response.StatusCode)
}
|> Async.RunSynchronously
When the request body is large, the function often throws an AggregateException with inner exception "NotSupportedException: The stream does not support concurrent IO read or write operations."
I wonder why this happens. Looks like there is an attempt to use response stream before the request is stream is fully processed. But why?
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.
I'm currently developing a Firefox add-on(using https://addons.mozilla.org/en-US/developers/docs/sdk/1.0/ ) that consumes an API where the return data is in xml.
My problem is that I need to parse the returned data, and would like to do that using a xml object.
Since the request module only supports JSON and Text ( https://addons.mozilla.org/en-US/developers/docs/sdk/1.0/packages/addon-kit/docs/request.html#Response ) I need to convert the response.text to XML.
The code looks like this:
var Request = require('request').Request
.......
var req = Request({
url: https://to-the-api.com,
content: {
op: 'get-the-data-op',
password: "super-sec",
user: "username"
},
onComplete: function (response) {
dataAsText = response.text;
console.log("output: " + dataAsText);
}
});
req.post();
I have tried to user (new DOMParser).parseFromString(response.text, 'text/xml') but unfortunately it just fails with a error like ReferenceError: DOMParser is not defined
The question is if anyone of you guys have been able to create a Xml object inside a Firefox add-on, and if so, how?
Looks like the capability to parse response as xml was present, but has been removed. check out this bugzilla reference
Can't you use a normal XMLHttpRequest if you want to process the response as XML?
If DOMParser is unavailable you can try E4X:
var xml = new XML(response.text);
alert(xml.children().length());
You want to use the XMLHttpRequest object to handle your xhr request. Then when you get a response back access the responseXML object of the request variable. In the responseXML you'll have the documentElement and can use the querySelectorAll or querySelector to find elements you want. In each element you want just grab the textContent you need.
Here's an example to get you going (this looks for the 'xmls' element in the response):
var request = new require("xhr").XMLHttpRequest();
request.open('GET', 'https://to-the-api.com', true);
request.onreadystatechange = function (aEvt) {
if (request.readyState == 4) {
if(request.status == 200) {
var xmls = request.responseXML.documentElement.querySelectorAll("xmls");
for (var i = 0; i < xmls.length; i++) {
console.log("xml", i, xmls[i], xmls[i].textContent);
}
}
else {
console.log('Error', request.responseText);
}
}
};
request.send(null);