Flutter receives 422 response from Fastapi when posting a PNG file - flutter

I have created a working localhost API with FastAPI. The POST takes a PNG, does some image processing and returns a PNG as expected when I click the 'try it out' button in the FastAPI generated docs:
The curl post command shows as follows:
curl -X 'POST' \
'http://localhost:8345/api/predict' \
-H 'accept: application/json' \
-H 'Content-Type: multipart/form-data' \
-F 'file=#test_img.png;type=image/png'
The image File is successfully retrieved from the image picker library. (Where the image1 object has been initialized as File image1; in the app page's class.
Future getImage() async {
var imageTmp = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
image1 = imageTmp;
print('Image Path $image1');
});
}
I tried to emulate the API call with the below function in Flutter.
doUpload() {
/*
curl -X 'POST' \
'http://192.168.178.26:8345/api/predict' \
-H 'accept: application/json' \
-H 'Content-Type: multipart/form-data' \
-F 'file=#test_img.png;type=image/png'
*/
var request = http.MultipartRequest(
'POST',
Uri.parse("http://<my locally hosted ip>:8345/api/predict"),
);
Map<String, String> headers = {"Content-type": "multipart/form-data"};
request.files.add(
http.MultipartFile(
'image',
image1.readAsBytes().asStream(),
image1.lengthSync(),
filename: 'filename',
contentType: MediaType('image', 'png'),
),
);
request.headers.addAll(headers);
print("request: " + request.toString());
request.send().then((value) => print(value.statusCode));
}
When I run the doUpload() function, a POST is successfully sent to the localhost API, but it returns a 422 error 'unprocessable entity'.
What I tried:
I tried to set the image type in doUpload to jpg, jpeg, but I keep getting a 422 error.
I tried looking up where the image_picker is supposed to store the temporary file to see if it's stored correctly, but when I look at the generated filepath, I don't see the actual file and tmp folder:
filepath: File: '/data/user/0/<my package name>/cache/image_picker3300408791299772729jpg'
looking at my local UI filepath, I see:
It shows no folder named cache, so I can't inspect it like this. However, the image picker saves it with a jpg at the end (not .jpg, is this normal?)
I also tried adding this debugger function to my fastAPI server.py, but I'm not sure how I can inspect the resulting data in the current flutter code:
https://fastapi.tiangolo.com/tutorial/handling-errors/#use-the-requestvalidationerror-body
The resulting value has properties like statusCode and reason, but I don't see a full json output option.

To mimic that curl command exactly, use this: (I've used the convenience constructor for simplicity)
final request = http.MultipartRequest(
'POST',
Uri.parse('http://<my locally hosted ip>:8345/api/predict'),
);
request.files.add(
await http.MultipartFile.fromPath(
'file', // NOTE - this value must match the 'file=' at the start of -F
image1.path,
contentType: MediaType('image', 'png'),
),
);
final response = await http.Response.fromStream(await request.send());
print(response.body);

Related

Flutter http 400 error when sending an XFile Image

I want to send a jpg file from my flutter app to .Net backend. I'm using http package.
My code is as follows:
var uri = Uri.parse('$url/upload/$id');
var request = http.MultipartRequest('POST', uri);
var headers = {'accept': '*/*', 'Content-Type': 'multipart/form-data'};
request.headers.addAll(headers);
var x = await file.readAsBytes();
var mFile = http.MultipartFile.fromBytes('file', x);
request.files.add(mFile);
var response = await request.send();
Here file is an XFile file from package cross_file.
Unfortunately I get an error code - 400 "Bad request".
On the backend side code looks as follows
[HttpPost("/upload/{id}")]
public IActionResult UploadImage(IFormFile imageFormFile, [FromRoute] Guid id)
{
// program does not even enter the function
}
I've tested this using Swagger and it works, it generates following curl:
curl -X 'POST' \
'http://localhost:44383/apiname/f7765448-be93-4e72-b62e-04623b4ccdb1' \
-H 'accept: */*' \
-H 'Content-Type: multipart/form-data' \
-F 'imageFormFile=#sample.jpg;type=image/jpeg'
I've searched some forums and tutorials, but nothing works.
I've tried adding the file using fromBytes, fromPath and fromString, none worked.
I've tried experimenting with different combinations of headers and fields, this didn't work either. In particular I've tried to add fields "imageFormFile" and "type", as in Swagger curl, but it didn't work as well.
I've also tried to rewrite this using dio, but got the same result (also I'd rather stick to http, as the rest of my project uses it).
Future uploadRequest(String url, String filePath) async {
final dio = Dio();
dio.options.contentType = "multipart/form-data";
final multiPartFile = await MultipartFile.fromFile(
filePath,
filename: filePath.split('/').last,
);
FormData formData = FormData.fromMap({
"file": multiPartFile,
});
final response = await dio.post(
url,
data: formData,
);
return response.data;
}
This is 100% working solution but with dio package as I prefer it over http. But It doesn't mean that with http it is impossible.
IMPORTANT: formData could be different according to your API

Flutter data-raw in http PUT

I have this sample code
curl --request PUT \
--url someurl/test.png \
--header 'AccessKey: MY_API_AccessKey' \
--header 'Content-Type: application/octet-stream' \
--data-binary #myimage.png
How do I get this done in Flutter?
You did not mention where your image you are trying to upload does come from. So I supposed that is coming from local images. The code would then look the following (when using http package).
import 'package:http/http.dart';
void main() {
// example when loading image from assets
final image = Image.asset('graphics/background.png');
put(Uri.parse('someurl/test.png'), headers: {
'AccessKey': 'MY_API_AccessKey',
'Content-Type': 'application/octet-stream'
}, body: image.toByteData());
}

The argument type 'Set<String>' can't be assigned to the parameter type 'Map<String, String>'

I'm trying to create an user authentication system using Auth0 in my Flutter app. The Auth0 REST documentation gave an example of cURL but I didn't find any flutter package which does the job of cURL. So, I used http.
Here's the code:
Future<String> getToken(String userId) async {
final response = await http.post(
Uri.parse('https://my-auth0-subdomain.auth0.com/oauth/token'), // I used my real subdomain
body: jsonEncode({
'grant_type=client_credentials',
'client_id=my_project_client_id', // I used my real client id
'client_secret=my_project_client_secret', // I used my real client secret
'audience=https://my-auth0-subdomain.auth0.com/api/v2/' // I used my real subdomain
}),
headers: {
'content-type: application/x-www-form-urlencoded'
},
);
final token = jsonDecode(response.body)["access_token"];
return token;
}
This gives me an error that The argument type 'Set<String>' can't be assigned to the parameter type 'Map<String, String>'. on line 10 (headers: {...}). I can resolve this error by using headers: {'content-type': 'application/x-www-form-urlencoded'},.
But this then gives the error from Auth0 {"error":"access_denied","error_description":"Unauthorized"}. The API is set up properly, because on running
curl --request POST \
--url 'https://my-auth0-subdomain.auth0.com/oauth/token' \
--header "content-type: application/x-www-form-urlencoded" \
--data grant_type=client_credentials \
--data 'client_id=my_project_client_id' \
--data client_secret=my_project_client_secret \
--data 'audience=https://my-auth0-subdomain.auth0.com/api/v2/'
it returns a "access_token", "scope", "expires_in" and "token_type".
Please help. It's very important.
Thanks in advance :)
Try to send data as url encoded using :
Map<String, String> myBody= {
'grant_type' : 'client_credentials',
'client_id' : 'my_project_client_id', // I used my real client id
'client_secret' : 'my_project_client_secret', // I used my real client secret
'audience ' : 'https://my-auth0-subdomain.auth0.com/api/v2/' // I used my real subdomain
};
Future<String> getToken(String userId) async {
final response = await http.post(
Uri.parse('https://my-auth0-subdomain.auth0.com/oauth/token'), // I used my real subdomain
body: myBody,
headers: {
'content-type: application/x-www-form-urlencoded'
},
);
final token = jsonDecode(response.body)["access_token"];
return token;
}

Flutter: MultipartRequest returns 403 Forbidden... CURL works fine

I try the following CURL command:
-X PUT \
-H 'Content-Type: application/octet-stream' \
--upload-file ${file_name} \
${url}
To upload a file into Google Storage (https://storage.googleapis.com/...?signature=...&...). This works just fine.
However, when I try to do it using the following Flutter code - I get a response with Error 403 (Forbidden):
Future<http.Response?> uploadVideo(
{required String uploadURL, required filePath}) async {
try {
ByteData bytes = await rootBundle.load(filePath);
var data =
http.MultipartFile.fromBytes('file', bytes.buffer.asInt64List());
Uri uri = Uri.parse(uploadURL);
var request = http.MultipartRequest(
'PUT',
uri,
);
request.files.add(data);
request.headers.addAll({
HttpHeaders.contentTypeHeader: 'Content-Type: application/octet-stream',
});
http.StreamedResponse response = await request.send();
return await http.Response.fromStream(response);
} on SocketException catch (e) {
showNoInternetError(e);
}
Any idea why this happens? The uploadURL is the same as the one I sent through CURL (very long string, with signature and ampersand signs etc).
Thanks a lot!!! <3

How to put raw data in a http get request in Flutter(Dart)?

I'm trying to execute the following curl in dart but I can't find a way to achieve that:
curl --location --request GET 'https://someurl.com/query' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer xxxx' \
--data-raw '{
"query":"test_value",
"size":10
}'
The only way I've found to achieve this is to use POST and put the raw data inside the body but I was wondering if there is a real way to achieve that since the POST request with a body seems to be about 220ms slower than the GET one(I know that they should be almost equal, it may be something from the server when recieving the request).
The default get() method of the http package doesn't allow you to add data since that isn't a common thing to do. You can get around this by using the Request object directly for more fine-grained control, as stated in the docs:
Request req = Request('GET', Uri.parse('https://someurl.com/query'))
..body = json.encode(data)
..headers.addAll({
"Content-type": "application/json",
"Authorization": "Bearer xxxx"
});
var response await req.send();
if (response.statusCode == 200) {
// do something with valid response
}
I'd look at getting the POST variant working properly, since a GET method semantically shouldn't do anything with the provided body. But that's a different discussion of course.
import 'package:http/http.dart' as http;
// Define the raw data to be sent in the request
String rawData = '{ "key": "value" }';
// Send the GET request with the raw data as the body
http.Response response = await http.get(
'https://example.com/endpoint',
headers: {'Content-Type': 'application/json'},
body: rawData,
);
// Check the status code of the response to see if the request was successful
if (response.statusCode == 200) {
// The request was successful, process the response
} else {
// The request was not successful, handle the error
}