Flutter send streamed multipart request not finishing and causing segfault - flutter

I'm trying to send a multipart request via http.StreamedRequest. I am aware of http.MultipartRequest, however, it does not fit my needs (the API I'm trying to send the request to encodes arrays by sending the same attribute multiple times, which is not possible using the fields parameter of http.MultipartRequest since it is a Map<String, String>, which does not allow the same key to be inserted multiple times).
The file (with form-data name document) I'm trying to send is provided in form of a Uint8List (a .pdf file), in addition to a filename (in the following "some_filename.pdf", a pdf title (in the following "some_document_title") and an array of numbers (which I'm trying to add to the multipart request) with the attribute tags.
For instance, I want the array tags=[1, 2, 3], title="some_document_title" and file to be encoded as follows:
--dart-http-boundary-rVI-TO32QXZlS.VAGKWpuhbt99woLKVVvjToGoT-MVOU1YGSpnQ
Content-Disposition: form-data; name="title"
Content-type: text/plain
some_document_title
--dart-http-boundary-rVI-TO32QXZlS.VAGKWpuhbt99woLKVVvjToGoT-MVOU1YGSpnQ
Content-Disposition: form-data; name="tags"
Content-type: text/plain
1
--dart-http-boundary-rVI-TO32QXZlS.VAGKWpuhbt99woLKVVvjToGoT-MVOU1YGSpnQ
Content-Disposition: form-data; name="tags"
Content-type: text/plain
2
--dart-http-boundary-rVI-TO32QXZlS.VAGKWpuhbt99woLKVVvjToGoT-MVOU1YGSpnQ
Content-Disposition: form-data; name="tags"
Content-type: text/plain
3
--dart-http-boundary-rVI-TO32QXZlS.VAGKWpuhbt99woLKVVvjToGoT-MVOU1YGSpnQ
Content-Disposition: form-data; name="document"; filename="some_filename.pdf"
Content-type: application/octet-stream
After this part, I want to stream the file data.
The rough implementation looks like this:
Future<void> createDocument(Uint8List file, String filename, String title, List<int> tags) {
final StreamedRequest request = StreamedRequest('POST', Uri.parse('some_url'));
String boundary = "..."; // Generate random boundary string
String contentType = "application/octet-stream";
String body = "";
Map<String, String> fields = {
"title": title,
// Some more fields left out for readability
};
for (final fieldKey in fields.keys) {
body += _buildMultipartField(fieldKey, fields[fieldKey], boundary);
}
for (final tag in tags) {
body += _buildMultipartField('tags', tag.toString(), boundary);
}
// Prepare file part
body += "--$boundary" +
'\r\nContent-Disposition: form-data; name="document"; filename="$filename"' +
"\r\nContent-type: $contentType" +
"\r\n\r\n";
final closing = "\r\n--" + boundary + "--\r\n";
request.headers.addAll({
"Content-Type": "multipart/form-data; boundary=$boundary",
"Content-Length": "${body.length + closing.length + file.lengthInBytes}"
});
// Add current body
request.sink.add(utf8.encode(body));
Stream.fromIterable(file).listen((chunk) => request.sink.add([chunk]))
.onDone(() {
// Add closing to multipart request
request.sink.add(utf8.encode(closing));
request.sink.close();
});
final StreamdResponse response = await IOClient().send(request);
if (response.statusCode != 200) {
throw Error();
}
}
String _buildMultipartField(String fieldName, String value, String boundary) {
return '--$boundary' +
'\r\nContent-Disposition: form-data; name="$fieldName"' +
'\r\nContent-type: text/plain' +
'\r\n\r\n' +
value +
'\r\n';
}
Now, my current implementation produces the expected body, which is good, however, the streamed request does not seem to finish, and according to the DevTools Network page, the request is never sent.
Is the content-length relevant for the request to finish? I.e. is it possible that the request is not finishing because the server is still expecting data because the content-length is calculated incorrectly?
Also, I'm getting a segfault when hot restarting the application when the request is still pending (it never returns).
Any help is appreciated, thanks!

Related

Mailparser ignores attachments' headers

I'm sending an email with attachments, which look like
const attachment = { filename, path, size, headers: { uid: 'someId' } };
According to the Nodemailer's docs I can set attachment's headers in the same format as message headers.
At the receiver's side the email is parsed by simpleParser (from mailparser). Parsed email looks great, it has all attachment's info but the headers are empty {}. But the raw email source has the following:
----_NmP-30615c8ac620489d-Part_1
Content-Type: image/jpeg; name=attachment.jpg
Uid: someId
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=attachment.jpg
The uid header is there, but it is lost after parsing.
Also tried headers in the following format: headers: [{ header: 'uid', value: 'someId' }]. But it doesn't help.
How can I get that headers correctly? Or this can be the mailparser's bug?
After few hours of mailparser's source code researching I discovered that mailparser returns headers as Map, that's why it looks like Object {} which seems to be empty, but it's not.

Sending int and boolean at body of http post method in flutter

Hi I have a http post as
final http.Response response = await client.post(
'http://someurl/',
headers: {
HttpHeaders.contentTypeHeader: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": token
},
body: {
"isItTake": false,
"servisID": 1
}
);
But when I try this post method I get "Unhandled Exception: type 'bool' is not a subtype of type 'String' in type cast". I can change the API to expect string but I wonder if there is a work around to send int or boolean.
Note that When I send a similar request on postman everything is fine.
Edit:
Postman:
POST /someendpoint/ HTTP/1.1
Host: somehost
Authorization: Token sometoken
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 20582fd0-c980-2d0d-fb2f-3bdd87d767f5 \
{
"isItTake": false,
"servisID": 1
}
Try sending the request body values as Strings and see if that works. I've faced this issue before with type mismatch with the request bodies of http requests and I'm not quite sure as to why it throws exceptions like that even though the documentation for the api clearly specifies the type for each value in the request body. Try this:
final http.Response response = await client.post(
'http://someurl/',
headers: {
HttpHeaders.contentTypeHeader: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": token
},
body: {
"isItTake": 'false',
"servisID": '1'
}
);
Or in case if you have your values in some bool and int variables:
final http.Response response = await client.post(
'http://someurl/',
headers: {
HttpHeaders.contentTypeHeader: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": token
},
body: {
"isItTake": isItTake.toString(),
"servisID": servisID.toString()
}
);
Use String encoded = json.encode(theMap); then post encoded. If you need a particular character encoding (e.g. utf-8) then further encode the string using utf8.encode(encoded) and post the resulting byte array. (The second step should be unnecessary for utf-8 as I think that is the default.)
It's worth considering what the 3 variants do:
List<int> - sends an opaque byte array
String encodes the string into bytes using a character encoding - and sends the byte array
Map<String, String> - encodes the string key/value pairs in
x-www-form-urlencoded and sends that.
If you want to send more complex data then you need to convert it into one of the above (and the server needs to know how to decode it). That's where the content-type header is useful. Ultimately, the server receives a byte array and converts it back into, for example, a string, or some json, or a set of form fields, or an image. It knows how to do this based on the header and any specified encoding.
Complete Credit: Source

Why is RestSharp posting form name/value pairs instead of JSON?

Why is RestSharp posting form name/value pairs instead of JSON when I have this line: `request.RequestFormat = DataFormat.Json;
var request = new RestRequest($"api/Users/{userId}/UpdateProperty", Method.PUT);
request.RequestFormat = DataFormat.Json;
request.AddObject(new { key = key, value = value });
Execute(request);
This results in the following http request (checked using Fiddler):
PUT /api/Users/c8c946f9-e1dd-49c6-9c7f-23572017058a/UpdateProperty HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
Accept-Encoding: gzip, deflate
key=Gender&value=Female
I was expecting the body to be JSON:
{
key: "Gender",
value: "Female"
}
What am I doing wrong?
Instead of the AddObject method, you want to use the AddJsonBody method. You probably also want to add the "Content-type" header with "application/json" value.
Basically something like this:
var request = new RestRequest($"api/Users/{userId}/UpdateProperty", Method.PUT);
request.AddHeader("Content-type", "application/json");
request.RequestFormat = DataFormat.Json;
request.AddJsonBody(new { key = key, value = value });
Execute(request);

ebay api error Uploaded picture has unsupported file format

I am trying to upload an image as a binary MIME attachment to ebays api UploadSiteHostedPictures. I keep receiving the following error: Uploaded picture has an unsupported file format. The image is a jpg but i have tried png, and that didn't work either.
I have gone through the ebay knowledgebase java and c# examples and i cannot understand where i am going wrong.
Here is where i am passing the image file.
List<int> imageBytes = imageFile.readAsBytesSync();
String base64Image = base64Encode(imageBytes);
Header
Map<String, String> uploadPictureHeader = {'X-EBAY-API-CALL-NAME': 'UploadSiteHostedPictures', 'X-EBAY-API-SITEID': '0', 'X-EBAY-API-RESPONSE-ENCODING': 'XML',
'X-EBAY-API-COMPATIBILITY-LEVEL': '967', 'X-EBAY-API-DETAIL-LEVEL': '0', 'Cache-Control': 'no-cache',
'Content-Type': 'multipart/form-data; boundary=FormBoundary7MA4YWxkTrZu0gW'};
Body
String requestUploadedSiteHostedPicture = '''--FormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="XML Payload"
<?xml version="1.0" encoding="utf-8"?>
<UploadSiteHostedPicturesRequest xmlns="urn:ebay:apis:eBLBaseComponents">
<RequesterCredentials>
<eBayAuthToken >$userTokenOAuth</eBayAuthToken>
</RequesterCredentials>
<PictureName>Vase</PictureName>
<PictureSet>Standard</PictureSet>
<ExtensionInDays>20</ExtensionInDays>
</UploadSiteHostedPicturesRequest>
--FormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="Vase"; filename="Vase.jpg"
Content-Transfer-Encoding: base64
$base64Image
--FormBoundary7MA4YWxkTrZu0gW--''';
Making the call
final response = await http.post(
url,
headers: uploadPictureHeader,
body: requestUploadedSiteHostedPicture,
encoding: Encoding.getByName("UTF-8")
);
I have used online binary converters to convert the image as well, but that didn't work either. Any help is much appreciated, thank you.

Gmail Api resumable upload Rest( attachment larger than 5MB)

I am trying to send via Gmail Api Rest a mail with attachment larger than 5MB. To accomplish that I am trying to sent it with resumable upload. This is my code.
byte[] ba = System.IO.File.ReadAllBytes(uploadFromPath);
String base64String = Convert.ToBase64String(ba);
string url = "https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=resumable"
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.Headers.Add("Authorization", "Bearer " + token);
request.Headers.Add("X-Upload-Content-Type", "message/rfc822");
request.Headers["X-Upload-Content-Length"]= base64String.Length.ToString();
request.Method = "POST";
request.ContentType = "application/json";
request.ContentLength = body.Length;
After I make the request I am getting the location
location = res.Headers["Location"];
and after that I make the PUT request with the location.
I would like to know what should I insert inside first request Body and what should be inside second request.
I have seen this post Attaching a file using Resumable upload w/ Gmail API
but the code worked only for files smaller than 5MB. Is there anything else I should do to accomplish attchment larger than 5MB?
There's actually samples on the Upload Attachment docs.
Step 1: Start a resumable session
Resumable session initiation request
POST /upload/gmail/v1/users/userId/messages/send?uploadType=resumable HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer your_auth_token
Content-Length: 38
Content-Type: application/json; charset=UTF-8
X-Upload-Content-Type: message/rfc822
X-Upload-Content-Length: 2000000
{
"id": string,
"threadId": string,
"labelIds": [
string
],
"snippet": string,
"historyId": unsigned long,
"payload": {
"partId": string,
"mimeType": string,
"filename": string,
"headers": [
{
"name": string,
"value": string
}
],
"body": users.messages.attachments Resource,
"parts": [
(MessagePart)
]
},
"sizeEstimate": integer,
"raw": bytes
}
Step 2: Save the resumable session URI
HTTP/1.1 200 OK
Location: https://www.googleapis.com/upload/gmail/v1/users/userId/messages/send?uploadType=resumable&upload_id=xa298sd_sdlkj2
Content-Length: 0
Step 3: Upload the file
PUT https://www.googleapis.com/upload/gmail/v1/users/userId/messages/send?uploadType=resumable&upload_id=xa298sd_sdlkj2 HTTP/1.1
Content-Length: 2000000
Content-Type: message/rfc822
bytes 0-1999999