Flutter http streamedResponse.stream and streamedResponse.sink - flutter

I'm learning the third example of Flutter http package, this is the base of the code: https://pub.dev/packages/http
When the request is sent via BaseClient.send, only the headers and whatever data has already been written to StreamedRequest.stream will be sent immediately. More data will be sent as soon as it's written to StreamedRequest.sink, and when the sink is closed the request will end.
https://pub.dev/documentation/http/latest/http/StreamedRequest-class.html
From the docs, I don't understand how we should write to StreamedRequest.stream? (To send data immediately)
Isn't StreamedResponse.sink basically where we add our HTTP POST's Request Body: Why does it only accept a List<int>? Shouldn't it be a Map<String, String>? If it's not then where should we add the request body? NEW: Even when I encode it with ut8.encode, it still doesn't show up on Fiddler's WebForms when I'm debugging, how do I send a x-www-form-urlencoded properly?:
Code:
userAgentClient = UserAgentClient(userAgent, client);
streamedRequest = http.StreamedRequest('POST', Uri(scheme: 'http', path: '/posts/', host: 'jsonplaceholder.typicode.com'));
streamedRequest.sink.add([123, 456]); // It has to be a List<int>
//NEW:
streamedRequest.sink.add(utf8.encode('username=123&password=456'));
streamedRequest.sink.add(utf8.encode('{"username":"123","password":"456"}'));
Why do I have to close the sink to be able to access StreamedResponse's properties?
streamedRequest.sink.close();
Update:
class UserAgentClient extends http.BaseClient {
final String userAgent;
final http.Client client;
UserAgentClient(this.userAgent, this.client);
Future<http.StreamedResponse> send(http.BaseRequest request){
request.headers['user-agent'] = userAgent;
return client.send(request);
}
}
dynamic _status = '';
dynamic _body = '';
dynamic _headers = '';
String _reason = '';
http.Client client = http.Client();
String userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36';
UserAgentClient userAgentClient;
http.StreamedRequest streamedRequest;
void _httpStreamed(){
userAgentClient = UserAgentClient(userAgent, client);
streamedRequest = http.StreamedRequest('POST', Uri(scheme: 'http', path: '/posts/', host: 'jsonplaceholder.typicode.com'));
streamedRequest.sink.add(utf8.encode('{"username":"123","password":"456"}'));
setState(() {
_status = streamedRequest.url;
_body = '';
_headers = '';
_reason = '';
});
}
void _httpSend() async{
http.StreamedResponse streamedResponse;
streamedResponse = await userAgentClient.send(streamedRequest);
streamedResponse.stream.listen(
(value) async{
_body = http.ByteStream.fromBytes(value);
_body = await _body.bytesToString();
},
onError: (e, sT) {
SnackBar sBar = SnackBar(content: Text('$e\n$sT',));
Scaffold.of(context).showSnackBar(sBar);
},
onDone: () {
SnackBar sBar = SnackBar(content: Text('Done lol'),);
Scaffold.of(context).showSnackBar(sBar);
},
);
setState(() {
_body;
_status = streamedResponse.statusCode;
_headers = streamedResponse.headers;
_reason = streamedResponse.reasonPhrase;
});
}
}
void _httpClose(){
if (streamedRequest != null){
streamedRequest.sink.close();
}
}
So I run the first 2 functions but the _body variable doesn't show up on my screen until I run the _httpClose() function.

yes
Map<String,String> can not be streamed.
Streamed means the data is sent in chunks every time a chunk of data is emitted by the stream and the server (or the browsers send buffer) is ready to receive more data.
List<int> can be chunked because it doesn't matter how many bytes are sent at once.
If you have all data readily available, you probably do not want to use a StreamedRequest, expecially if it is not a blob of data. https://en.wikipedia.org/wiki/Binary_large_object
utf8.encode can be used to encode chunks emitted by a stream, but it doesn't provide a stream by itself, so you can't add the result of utf8.encode to a sink.
I do not understand that question. What properties doe you want to access and what problems do you run into when you try?
To me it doesn't look like you don't need to use StreamedRequest for your use case.

Related

How to make a http post using form data in flutter

I'm trying to do a http post request and I need to specify the body as form-data, because the server don't take the request as raw or params.
here is the code I tried
** Future getApiResponse(url) async {
try {
// fetching data from the url
final response = await http.get(Uri.parse(url));
// checking status codes.
if (response.statusCode == 200 || response.statusCode == 201) {
responseJson = jsonDecode(response.body);
// log('$responseJson');
}
// debugPrint(response.body.toString());
} on SocketException {
throw FetchDataException(message: 'No internet connection');
}
return responseJson;
}
}
but its not working. here is the post man request
enter image description here
its not working on parms. only in body. its because this is in form data I guess.
how do I call form data in flutter using HTTP post?
First of all you can't send request body with GET request (you have to use POST/PUT etc.) and you can use Map for request body as form data because body in http package only has 3 types: String, List or Map. Try like this:
var formDataMap = Map<String, dynamic>();
formDataMap['username'] = 'username';
formDataMap['password'] = 'password';
final response = await http.post(
Uri.parse('http/url/of/your/api'),
body: formDataMap,
);
log(response.body);
For HTTP you can try this way
final uri = 'yourURL';
var map = new Map<String, dynamic>();
map['device-type'] = 'Android';
map['username'] = 'John';
map['password'] = '123456';
http.Response response = await http.post(
uri,
body: map,
);
I have use dio: ^4.0.6 to create FormData and API Calling.
//Create Formdata
formData = FormData.fromMap({
"username" : "John",
"password" : "123456",
"device-type" : "Android"
});
//API Call
final response = await (_dio.post(
yourURL,
data: formData,
cancelToken: cancelToken ?? _cancelToken,
options: options,
))

why flutter often occur return connection closed before full header was received

I use HTTP for connection to API, and I have tried some flutter sdk like 2.5, 2.10.5, 3 but still have same issue often occur return connection closed before full header was received. and it's can occur in random api and all apps I build in flutter.
it's example of my code
Future<dynamic> getGoodSolution() async {
final url = Uri.parse('$url');
final headers = {HttpHeaders.contentTypeHeader: 'application/json', HttpHeaders.authorizationHeader: 'Bearer mytoken123'};
var map = <String, dynamic>{};
map["xxx"] = "123";
// print(headers);
try {
final response = await client.post(url, headers: headers, body: json.encode(map));
final data = xxxFromJson(response.body);
return data;
} catch (e) {
print(e);
return null;
}
}
I solved the problem by using the send() method of the HTTP (More info) package
Future<dynamic> getGoodSolution() async {
final url = Uri.parse('$url');
final headers = {HttpHeaders.contentTypeHeader: 'application/json',HttpHeaders.authorizationHeader: 'Bearer mytoken123'};
var map = <String, dynamic>{};
map["xxx"] = "123";
try {
var request = http.Request('POST', url);
request.headers.addAll(headers);
request.body = json.encode(map);
var streamedResponse = await request.send();
var response = await http.Response.fromStream(streamedResponse);
final data = xxxFromJson(response.body);
return data;
} catch (e) {
print(e);
return null;
}
}
There is an issue ongoing on the Flutter repo which describes your problem.
One of the given workarounds is to use a HttpClient and to set the allowLegacyUnsafeRenegotiation property to true
void main() async {
final context = SecurityContext.defaultContext;
context.allowLegacyUnsafeRenegotiation = true;
final httpClient = HttpClient(context: context);
final client = IOClient(httpClient);
await client.get(Uri.parse('https://your_uri.net'));
}
This solution will only work on mobile though, the http package should not be used in Web mode.

Simple http request with basicauth stucks

I don’t really want to do anything other than a simple HTTP-request with a GET parameter and Basic Auth.
For this, I have written the following small class. However, not much seems to happen here. The request is started but it doesn’t seem to return any result. It runs and runs and should timeout or something like that.
class HttpService {
Future<List<Post>> getPosts() async {
final queryParameters = {
'data_type': 'temps_today',
};
String username = 'user';
String password = 'password';
String basicAuth =
'Basic ' + base64Encode(utf8.encode('$username:$password'));
print(basicAuth);
Response res = await get(Uri.https('mydomain.com', '/gartentemp/api/get_temp_data.php', queryParameters), headers: <String, String>{'authorization': basicAuth}); //stucks here
print(res.statusCode);
if (res.statusCode == 200) {
List<dynamic> body = jsonDecode(res.body);
List<Post> posts = body
.map(
(dynamic item) => Post.fromJson(item),
)
.toList();
return posts;
} else {
throw "Unable to retrieve posts.";
}
}
}
In the browser, a JSON file is delivered without any problems.
What's wrong with my try?
Thx niesel

Flutter print any http request automatically - abstract HTTP class

in short words I want to print in my console any Http request that my app is requesting without putting print command after each call I'm making for example :
let's say I have service with http.Client.get and I have another 100 service like that.
what I'm doing now is I'm waiting for the response in each service and then I'm printing it like this print('response is ' + response.body);.
what I want to achieve is that will be automatically be printed out for me without me writing print 100 times in after each request I'm making, any good architect would you recommend to follow ?
hope I cleared the idea well.
You can try the http_interceptor package which allows you to catch all the requests & responses from your http requests (change headers, params etc..)
If you add LogInterceptor, Request and Response URLs and request body are printed. Try ...
final logInterceptor = LogInterceptor(
requestBody: true,
responseBody: true,
error: false,
requestHeader: true,
responseHeader: true);
..interceptors.add(logInterceptor)
well here is my last approach for this.
for every one is seeking for making it with abstraction or let's say wrapping;
first what I did is kind if wrapping for the HTTP class and used my class everywhere instead of the original Http Class.
so the code would go like this
class MHttpClient {
final http.Client client;
final SharedPreferences sharedPreferences;
MHttpClient(this.client, this.sharedPreferences);
Future<http.Response> get(
{String path = "", Map<String, String> extraHeders}) async {
printWrapped('get Path: $path');
final response = await client.get(
Uri.parse(getBaseURL() + Version + path),
headers: getHeaders(extraHeaders: extraHeders),
);
printWrapped("get response : \n" + utf8.decode(response.bodyBytes));
return response;
}
Future<http.Response> post(
{String body = "",
String path = "",
Map<String, String> extraHeders}) async {
printWrapped('sended body: \n');
printWrapped(' ${json.decode(body)}');
final response = await client.post(
Uri.parse(getBaseURL() + Version + path),
body: body,
headers: getHeaders(extraHeaders: extraHeders),
);
printWrapped("post response : \n" + utf8.decode(response.bodyBytes));
return response;
}
Future<http.Response> put({String body = "", String path = ""}) async {
printWrapped('put body: \n ${json.decode(body)}');
final response = await client.put(
Uri.parse(getBaseURL() + Version + path),
body: body,
headers: getHeaders(),
);
printWrapped(utf8.decode(response.bodyBytes));
return response;
}
Future<http.Response> putImage({File image, String path = ""}) async {
printWrapped('Image Path: $path');
final response = await http.put(
Uri.parse(path),
headers: getImageHeaders(),
body: image.readAsBytesSync(),
);
return response;
}
String getBaseURL() {
if (Foundation.kDebugMode)
return BaseURLSTAGING;
else
return BaseURL;
}
String getApiKey() {
if (Foundation.kDebugMode)
return ApiKeyStaging;
else
return ApiKey;
}
String getToken() {
String cashedToken = sharedPreferences.getString(CACHED_TOKEN);
if (cashedToken == null) cashedToken = "";
return cashedToken;
}
Map<String, String> getHeaders({Map extraHeaders}) {
Map<String, String> headers = {
'Content-Type': 'application/json; charset=UTF-8',
'x-api-key': getApiKey(),
HttpHeaders.authorizationHeader: 'Bearer ' + getToken(),
};
if (extraHeaders == null || extraHeaders.isEmpty)
return headers;
else {
headers.addAll(extraHeaders);
return headers;
}
}
Map<String, String> getImageHeaders() {
return <String, String>{'Content-Type': 'image/png'};
}
void printWrapped(String text) {
final pattern = RegExp('.{400}'); // 800 is the size of each chunk
pattern.allMatches(text).forEach((match) => developer.log(match.group(0)));
}
}
and then I used MHttpClient else where
final MHttpClient client;
final response = await client.get(path: path);
and in this case I don't have to warry about anything else ,
and when you need to change one thing you will change it in one place only, and every thing will stay the same and work as you want without braking changes you have to do for all you requested.

Flutter: HttpClient post contentLength -- exception

Very weird...
In order to post some JSON data to my server, I define the contentLength to the length of the JSON encoded data but I then receive an exception that says "Content size exceeds specified contentLength". Difference is 1 byte.
Here is the source code:
Future<Map> ajaxPost(String serviceName, Map data) async {
var responseBody = json.decode('{"data": "", "status": "NOK"}');
try {
var httpClient = new HttpClient();
var uri = mid.serverHttps ? new Uri.https(mid.serverUrl, _serverApi + serviceName)
: new Uri.http(mid.serverUrl, _serverApi + serviceName);
var request = await httpClient.postUrl(uri);
var body = json.encode(data);
request.headers
..add('X-mobile-uuid', await _getDeviceIdentity())
..add('X-mobile-token', await mid.getMobileToken());
request.headers.contentLength = body.length;
request.headers.set('Content-Type', 'application/json; charset=utf-8');
request.write(body);
var response = await request.close();
if (response.statusCode == 200){
responseBody = json.decode(await response.transform(utf8.decoder).join());
//
// If we receive a new token, let's save it
//
if (responseBody["status"] == "TOKEN"){
await mid.setMobileToken(responseBody["data"]);
// Let's change the status to "OK", to make it easier to handle
responseBody["status"] = "OK";
}
}
} catch(e){
// An error was received
throw new Exception("AJAX ERROR");
}
return responseBody;
}
Some other times, it works fine...
Am I doing anything wrong with this code?
Many thanks for your help.
EDITED WITH SOLUTION:
Many thanks for your help. The simply fact of using utf8.encode(json.encode(data)) did not fully work. So, I turned to the http library and it now works like a charm. The code is even lighter!
Here is the new version of the code:
Future<Map> ajaxPut(String serviceName, Map data) async {
var responseBody = json.decode('{"data": "", "status": "NOK"}');
try {
var response = await http.put(mid.urlBase + '/$_serverApi$serviceName',
body: json.encode(data),
headers: {
'X-mobile-uuid': await _getDeviceIdentity(),
'X-mobile-token': await mid.getMobileToken(),
'Content-Type': 'application/json; charset=utf-8'
});
if (response.statusCode == 200) {
responseBody = json.decode(response.body);
//
// If we receive a new token, let's save it
//
if (responseBody["status"] == "TOKEN") {
await mid.setMobileToken(responseBody["data"]);
// Let's change the status to "OK", to make it easier to handle
responseBody["status"] = "OK";
}
}
} catch (e) {
// An error was received
throw new Exception("AJAX ERROR");
}
return responseBody;
}
I got it working with
req.headers.contentLength = utf8.encode(body).length;
From an indirect tip of the Utf8Codec documentation which states
decode(List codeUnits, { bool allowMalformed }) → String
Decodes the UTF-8 codeUnits (a list of unsigned 8-bit integers) to the corresponding string.
That means thatutf8.encode() returns codeUnits which actually means List<uint8>.
Encoding a String payload would in theory return a list which length is the length of the payload in bytes.
So using httpClient means to always measure the length of the payload in bytes, not the length of a String which may differ.
Günter is right. Content-Length has to be the length of the byte array after encoding from a String to bytes in whatever encoding you server requires.
There's a package called http which provides a slightly higher level api (it uses dart.io httpClient under the hood) which takes care of encoding the post body and length for you. For example, when you need to send application/x-www-form-urlencoded form it will even take a Map and do all the encoding for you (you still need to encode to json yourself). It's equally happy to send just a String or List<int>. Here's an example:
Map<String, String> body = {
'name': 'doodle',
'color': 'blue',
'teamJson': json.encode({
'homeTeam': {'team': 'Team A'},
'awayTeam': {'team': 'Team B'},
}),
};
Response r = await post(
url,
body: body,
);
Seems your string contains multibyte characters.
UTF8-encode the string to get the correct length:
var body = utf8.encode(json.encode(data));