dart-lang / http

A composable API for making HTTP requests in Dart.
https://pub.dev/packages/http
BSD 3-Clause "New" or "Revised" License
1.01k stars 343 forks source link

Multipart file uploads to AWS S3 fail with cuppertino_http but work with HttpClient: Missing content length #1236

Closed escamoteur closed 1 week ago

escamoteur commented 2 weeks ago

While exited about the new cupertino_http package we found out, that we currently can't use it because it does not att a Content-Length header in Multipart file uploads and if I add this header manually I get an 400 from S3 with the message that the request isn't a well-formed multipart request.

See how they compare:

image

I also attach the two requests as raw text files ( I only replace our URLs with XXX) I also wonder why the Cupertino Client includes data from sentry in a Request that has nothing to do with sentry. this could be a privacy violation.

this is how we do the Upload:

  @override
  Future<MediaUploadPayload> uploadImage(File file) async {
    final api = S3Api(di<ApiClient>());
    final s3Response = await api.getS3PresignedData();
    final S3AccessData accessData = s3Response!.data!;

    final String fileExt = file.path.split('.').last;

    final contentType = lookupMimeType(file.path) ?? 'application/octet-stream';

    var fileLength = await file.length();
    final multipartFile = http.MultipartFile(
      'file',
      file.openRead(),
      fileLength,
      contentType: MediaType.parse(contentType),
    );

    var relativeImagePath = generateImagePath(fileExt);

    final multipartRequest =
        http.MultipartRequest('POST', Uri.parse(accessData.endpointUrl));

    multipartRequest.fields.addAll({
      for (final field in accessData.params.entries)
        field.key: field.value.toString(),
    });
    multipartRequest.fields.addAll({
      'Content-Type': contentType,
      'key': relativeImagePath,
    });
    multipartRequest.files.add(multipartFile);

    final multipartResponse = await multipartRequest.send();
    final response = await http.Response.fromStream(multipartResponse);

    if (response.statusCode != 201) {
      throw Exception('Failed to upload file to S3: ${response.body}');
    }

As we have customers who complain about uploading errors (Connection closed before...) on iOS we would really like to use the new client. If we can change something in the way we use it any pointer would be really helpful. As it works with the normal Dart HttpClient and in cronnet it seems to be a problem with cupertino_htttp. cuppertino_client.txt HttpClient.txt

cc @brianquinlan

escamoteur commented 2 weeks ago

ok, I now found out the malform was the result that I did not use the correct value for Content-Length. after adding this to my code:

    multipartRequest.headers.addAll({
      'Content-Length': multipartRequest.contentLength.toString(),
    });

it works. Still, this should probably be done automatically

escamoteur commented 1 week ago

just tested with S3 works perfectly