how to simply fetch an url body as a string synchronously - dart

How would you simply implement this function:
String fetchUrlBodyAsString(String url) {
...
}
Usage:
String schema = fetchUrlBodyAsString("http://json-schema.org/draft-04/schema#");
This thread Using dart to download a file explains a good way to get to the data from a main function. But if you try it you see that the real work happens after leaving main. I think that the synchronous function that I want to create is difficult using HttpClient because it is trying to get an async api to work synchronously. According to this thread that may not be possible: https://groups.google.com/a/dartlang.org/d/msg/misc/kAgayQyaPhQ/wonJ776_FGIJ
What is a Dart way to implement this in a non-browser/console setting?

The using of asynchronous methods is really infectious. Once you start using Future inside a function you have to return a Future as result. So your fetchUrlBodyAsString function can look like :
import 'dart:io';
import 'dart:async';
Future<String> fetchUrlBodyAsString(String url) =>
new HttpClient().getUrl(Uri.parse(url))
.then((HttpClientRequest request) => request.close())
.then((HttpClientResponse response) =>
response.transform(new StringDecoder()).join());
main() {
final url = "http://json-schema.org/draft-04/schema#";
Future<String> schema = fetchUrlBodyAsString(url);
schema.then(handleContent);
}
handleContent(String content) {
print(content); // or do what you want with content.
}
or with async/await:
import 'dart:io';
import 'dart:async';
Future<String> fetchUrlBodyAsString(String url) async {
var request = await new HttpClient().getUrl(Uri.parse(url));
var response = await request.close();
return response.transform(new StringDecoder()).join();
}
main() async {
final url = "http://json-schema.org/draft-04/schema#";
handleContent(await fetchUrlBodyAsString(url));
}
handleContent(String content) {
print(content); // or do what you want with content.
}

You can make synchronous http requests by using HttpRequest.open and setting async to false.
https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart-dom-html.HttpRequest#id_open
import 'dart:html';
String fetchUrlBodyAsString(String url) {
var request = new HttpRequest()
..open('GET', url, async: false)
..send();
return request.responseText;
}
String schema = fetchUrlBodyAsString("http://json-schema.org/draft-04/schema#");

There is no way to turn an async API into a sync API; once you have a Future as a result, that is what you will have to deal with.
For your specific example, the only way to achieve what you want would be to build your own synchronous HTTP library from the ground up. Using asynchronous APIs in a synchronous manner is not possible.

Related

Flutter Jaguar HTTP cookie

I am using jaguar http library to perform network queries.
I am trying to get a cookie which should be returned from an http request.
I would like to save the cookie and send it on later queries.
Can anyone help with this?
I've figure it out!
you can use their interceptors to get the cookies from the response and add them to requests:
Here is a suggestion of how your api class can manage cookies.
import 'package:jaguar_retrofit/jaguar_retrofit.dart';
import 'package:jaguar_serializer/src/repo/repo.dart';
import 'package:jaguar_resty/jaguar_resty.dart' as resty;
import 'package:jaguar_resty/jaguar_resty.dart';
import 'package:http/http.dart';
import 'package:client_cookie/client_cookie.dart';
#GenApiClient(path: "apiPath")
class MyApi extends _$MyApiClient implements ApiClient{
#override
resty.Route base;
#override
SerializerRepo serializers;
MyApi({this.base, this.serializers}){ //Prepare the API in its constructor
globalClient = IOClient();
List<ClientCookie> cookies = List<ClientCookie>(); //Prepare alist of cookies
this.base.after((resty.StringResponse response){
Map map = response.headers.map((key, value){
return MapEntry(key, value);
});
String cookieStr = map["set-cookie"]; //Extract cookies from response header
print("Cookies Str: $cookieStr");
if(cookieStr != null){
cookies.clear();
cookies.addAll(Utils.cookies(cookieStr)); //Build your cookies and add them to the list
}
}
);
this.base.before((base1){ //This is 'before 'interceptor
base1.cookies(cookies); //add the cookies list to queries here
}
);
}//ApiConstructor
#GetReq(path: "something")
Future<dynamic> getSomething();
}

Making a Future block until it's done

Is it possible to block a function call that returns a future?
I was under the impression calling .then() does it, but that's not what I'm seeing in my output.
print("1");
HttpRequest.getString(url).then((json) {
print("2");
});
print("3");
What I'm seeing in my output is:
1
3
2
The getString method doesn't have an async that would allow me to await it and then executes asynchronously in any case.
static Future<String> getString(String url,
{bool withCredentials, void onProgress(ProgressEvent e)}) {
return request(url, withCredentials: withCredentials,
onProgress: onProgress).then((HttpRequest xhr) => xhr.responseText);
}
How do I make it blocking without placing an infinite while loop before step 3 waiting for step 2 to be completed (not that it would work anyways due to the single thread nature of Dart)?
The above HttpRequest loads a config.json file that determines how everything works in the app, if the request for a field in the config is done before the config.json file is done loading, it causes errors, so I need to wait until the file is done loading before I allow calling getters on the fields of the class or getters needs to wait for the once-off loading of the config.json file.
Update, this is what I eventually did to make it work after Günter suggested I use a Completer:
#Injectable()
class ConfigService {
Completer _api = new Completer();
Completer _version = new Completer();
ConfigService() {
String jsonURI =
"json/config-" + Uri.base.host.replaceAll("\.", "-") + ".json";
HttpRequest.getString(jsonURI).then((json) {
var config = JSON.decode(json);
this._api.complete(config["api"]);
this._version.complete(config["version"]);
});
}
Future<String> get api {
return this._api.future;
}
Future<String> get version {
return this._version.future;
}
}
And where I use the ConfigService:
#override
ngAfterContentInit() async {
var api = await config.api;
var version = await config.version;
print(api);
print(version);
}
Now I get blocking-like functionality without it actually blocking.
There is no way to block execution until asynchronous code completes. What you can do is to chain successive code so that it is not executed before the async code is completed.
One way to chain is then
print("1");
HttpRequest.getString(url) // async call that returns a `Future`
.then((json) { // uses the `Future` to chain `(json) { print("2"); }`
print("2");
});
print("3"); // not chained and therefore executed before the `Future` of `getString()` completes.
An async call is just scheduling code for later execution. It will be added to the event queue and when the tasks before it are processed it itself will be executed. After an async call is scheduled the sync code `print("3") is continued.
In your case HttpRequest.getString() schedules a call to your server and registers (json) { print("2") as callback to be called when the response from the server arrives. Further execution of the application doesn't stall until the response arrives and there is no way to make that happen. What instead happens is that sync code is continued to be executed (print("3")).
If your currently executed sync code reaches its end, then the next scheduled task is processed the same way.
then() schedules the code (json) { print("2"); } to be executed after getString() completed.
await
async and await just make async code look more like sync code but otherwise it is quite the same and will be translated under the hood to xxx.then((y) { ... }).
I'm not sure I understand what you're trying to achieve, but it looks to me you want to do this:
myFunction() async {
print("1");
final json = await HttpRequest.getString(url);
print("2");
print("3");
}
async statement is only needed in the consumer function. In other words, producer functions doesn't need to have async, they only need to return a Future.
you should be able to do this:
Future consumerFunc() async {
print("1");
var response = await HttpRequest.getString(url);
print("2");
print("3");
}
and it should result:
1
2
3
Note: await replaces then methods

encapsulate repeated send/responses to the same Dart isolate within a single asyncronous function

Is it possible to encapsulate repeated send/responses to the same dart isolate within a single asynchronous function?
Background:
In order to design a convenient API, I would like to have a function asynchronously return the result generated by an isolate, e.g.
var ans = await askIsolate(isolateArgs);
This works fine if I directly use the response generated by a spawnUri call, e.g
Future<String> askIsolate(Map<String,dynamic> isolateArgs) {
ReceivePort response = new ReceivePort();
var uri = Uri.parse(ISOLATE_URI);
Future<Isolate> remote = Isolate.spawnUri(uri, [JSON.encode(isolateArgs)], response.sendPort);
return remote.then((i) => response.first)
.catchError((e) { print("Failed to spawn isolate"); })
.then((msg) => msg.toString());
}
The downside of the above approach, however, is that if I need to repeatedly call askIsolate, the isolate must be spawned each time.
I would instead like to communicate with a running isolate, which is certainly possible by having the isolate return a sendPort to the caller. But I believe since the 2013 Isolate refactoring , this requires the caller to listen to subsequent messages on the receivePort, making encapsulation within a single async function impossible.
Is there some mechanism to accomplish this that I'm missing?
The answer depend on how you intend to use the isolate
Do you intend to keep it running indefinitely, sending it inputs and expecting to receive answers asynchronously?
Do you want to send the isolate many (but finite) inputs at once, expect to receive answers asynchronously, then close the isolate?
I'm guessing the latter, and your askIsolate() function needs to immediately return a Future than completes when it receives all the answers.
The await for loop can be used to listen to a stream and consume events until it closes.
I'm not familiar with isolates, so I hope this is OK, I have not tested it. I've assumed that the isolate terminates and response closes.
String askIsolate(Map<String,dynamic> isolateArgs) async {
ReceivePort response = new ReceivePort();
var uri = Uri.parse(ISOLATE_URI);
Isolate.spawnUri(uri, [JSON.encode(isolateArgs)], response.sendPort)
.catchError((e)) {
throw ...;
});
List<String> answers = new List<String>;
await for(var answer in response) {
out.add(answer.toString());
}
return answers;
}
Note:
response is the stream you are listening to for answers. It's created before spawning the isolate so you don't need to (and probably should not) wait for the isolate future to complete before listening to it.
I made askIsolate() async because that makes it very easy to immediately return a future which completes when the function returns - without all that tedious mucking about with .then(...) chains, which I personally find confusing and hard to read.
BTW, your original then(...).catchError(...) style code would be better written like this:
Isolate.spawnUri(uri, [JSON.encode(isolateArgs)], response.sendPort)
.catchError((e) { ... });
return response.first)
.then((msg) => msg.toString());
I believe that delaying attaching a catchError handler to the line after the isolate's creation might allow the future to complete with an error before the handler is in place.
See: https://www.dartlang.org/articles/futures-and-error-handling/#potential-problem-failing-to-register-error-handlers-early .
I also recommend looking at IsolateRunner in package:isolate, it is intended to solve problems like this - calling a function in the same isolate several times instead of just once when the isolate is created.
If you don't want that, there are other, more primitive, options
Async functions can wait on futures or streams and a ReceivePort is a stream.
For a quick hack, you can probably do something with an await for on the response stream, but it won't be very convenient.
Wrapping the ReceivePort in a StreamQueue from package:async is a better choice. That allows you to convert the individual events into futures. Something like:
myFunc() async {
var responses = new ReceivePort();
var queue = new StreamQueue(responses);
// queryFunction sends its own SendPort on the port you pass to it.
var isolate = await isolate.spawn(queryFunction, [], responses.sendPort);
var queryPort = await queue.next();
for (var something in somethingToDo) {
queryPort.send(something);
var response = await queue.next();
doSomethingWithIt(response);
}
queryPort.send("shutdown command");
// or isolate.kill(), but it's better to shut down cleanly.
responses.close(); // Don't forget to close the receive port.
}
A quick working example based on lrn's comment above follows. The example initializes an isolate via spawnURI, and then communicates with the isolate by passing a new ReceivePort upon which a reply is expected. This allows the askIsolate to directly return a response from a running spawnURI isolate.
Note error handling has been omitted for clarity.
Isolate code:
import 'dart:isolate';
import 'dart:convert' show JSON;
main(List<String> initArgs, SendPort replyTo) async {
ReceivePort receivePort = new ReceivePort();
replyTo.send(receivePort.sendPort);
receivePort.listen((List<dynamic> callArgs) async {
SendPort thisResponsePort = callArgs.removeLast(); //last arg must be the offered sendport
thisResponsePort.send("Map values: " + JSON.decode(callArgs[0]).values.join(","));
});
}
Calling code:
import 'dart:async';
import 'dart:isolate';
import 'dart:convert';
const String ISOLATE_URI = "http://localhost/isolates/test_iso.dart";
SendPort isolateSendPort = null;
Future<SendPort> initIsolate(Uri uri) async {
ReceivePort response = new ReceivePort();
await Isolate.spawnUri(uri, [], response.sendPort, errorsAreFatal: true);
print("Isolate spawned from $ISOLATE_URI");
return await response.first;
}
Future<dynamic> askIsolate(Map<String,String> args) async {
if (isolateSendPort == null) {
print("ERROR: Isolate has not yet been spawned");
isolateSendPort = await initIsolate(Uri.parse(ISOLATE_URI)); //try again
}
//Send args to the isolate, along with a receiveport upon which we listen for first response
ReceivePort response = new ReceivePort();
isolateSendPort.send([JSON.encode(args), response.sendPort]);
return await response.first;
}
main() async {
isolateSendPort = await initIsolate(Uri.parse(ISOLATE_URI));
askIsolate({ 'foo':'bar', 'biz':'baz'}).then(print);
askIsolate({ 'zab':'zib', 'rab':'oof'}).then(print);
askIsolate({ 'One':'Thanks', 'Two':'lrn'}).then(print);
}
Output
Isolate spawned from http://localhost/isolates/test_iso.dart
Map values: bar,baz
Map values: zib,oof
Map values: Thanks,lrn

Retrieving the response body from an HttpClientResponse

I'm trying to write some tests for my Dart server application, and I've been using the HttpClient class (along with the related HttpClientRequest and HttpClientResponse classes to make test requests to the server (note that I'm using these classes because I need the dart:io package for running the server, so I can't also import dart:html). This has been going fairly well so far, and I've been able to write tests to check that the server is returning responses with the correct HTTP Status code. The base of the code I've been using to make these test calls is as follows:
Future<HttpClientResponse> makeServerRequest(String method, Uri uri, [String jsonData]) async {
HttpClient client = new HttpClient();
HttpClientRequest request = await client.openUrl(method, uri);
request.write(jsonData);
return request.close();
}
Now I need to write a test that makes sure that the body of the response, not just the status code, is correct. The problem is that I can't seem to find anything that allows me to actually access the response body in the HttpClient* classes. The closest I've been able to find so far is the HttpClientResponse.contentLength property, but that only tells me how big the response body is, and isn't the actual content.
How do I retrieve the response body from these requests? Or, if you aren't able to, is there some other way I can make the requests on a server side application so I can read the responses?
The HttpClientResponse object is a Stream, so you can just read it with the listen() method:
response.listen((List<int> data) {
//data as bytes
});
You can also use the codecs from dart:convert to parse the data. The following example reads the response contents to a String:
import 'dart:io';
import 'dart:convert';
import 'dart:async';
Future<String> readResponse(HttpClientResponse response) {
final completer = Completer<String>();
final contents = StringBuffer();
response.transform(utf8.decoder).listen((data) {
contents.write(data);
}, onDone: () => completer.complete(contents.toString()));
return completer.future;
}
Low level
Here is the await for version of collecting the response stream. It's a little more compact than using a completer.
Future<String> readResponse(HttpClientResponse response) async {
final contents = StringBuffer();
await for (var data in response.transform(utf8.decoder)) {
contents.write(data);
}
return contents.toString();
}
You should wrap it in a try catch block to handle errors.
High level
Most of the time from the client side you would use the http library instead:
// import 'package:http/http.dart';
Response response = await get(url);
String content = response.body;
See this article for more details.
A short way of getting the body from an HttpClientResponse is:
Future<String> readResponse(HttpClientResponse response) async {
return response.transform(utf8.decoder).join();
}

Use Future when retrieving data using HttpRequest in Dart

At the moment I'm using a simple HttpRequest to retrieve a JSON file:
void getConfigData(String url) {
var request = new HttpRequest.get(url, this.onSuccess);
}
void onSuccess(HttpRequest req) {
JsonObject conf = new JsonObject.fromJsonString(req.responseText);
MenuItemCollection top_bar = new MenuItemCollection();
// and parse the JSON data ...
}
What I would like to know is if I should be using Futures instead of the callback?
You don't really have the choice between Futures or callbacks, this choice is made by the API you are using. Sometimes you have to give a callback like with HttpRequest.get and sometimes you get a Future like File.create.

Resources