【问题标题】:Flutter Resumable Upload to Google Drive Through HTTPFlutter 可通过 HTTP 恢复上传到 Google Drive
【发布时间】:2020-02-26 22:32:28
【问题描述】:

根据Google Drive API 上的文档,我正在尝试将文件上传到 Google 云端硬盘的根文件夹。我通过 Google 登录对用户进行了身份验证,这不是问题。我不断收到从服务器返回的 411 错误,上面写着

“POST 请求需要 Content-length 标头。这就是我们所知道的。”。

我有一个 Content-length 标头,但它似乎不被接受。这是我的代码,

Uri uri = Uri.parse('https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable');

http.MultipartRequest request = new http.MultipartRequest('POST', uri);
request.headers["Authorization"] = header['Authorization'];
request.headers['content-type'] = "application/json; charset=UTF-8";
request.headers['X-Upload-Content-Type'] ='video/mp4';
request.headers['X-Upload-Content-Length'] = lengthInBytes.toString();
request.headers['name'] = fileName;
request.headers['content-length'] = (request.contentLength).toString();
//request.files.add(await http.MultipartFile.fromPath('$fileName', file.path,));
print("request.toString: " + request.toString());
http.StreamedResponse response = await request.send();
print('ok: ' + response.statusCode.toString());
response.stream.transform(utf8.decoder).listen((value) {
  print(value);
});

我知道的唯一一行是文件名,因为 API 站点上的文档略有不同,我不确定我是否正确编码。这是 Google 网站上的 API 示例,

POST https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable HTTP/1.1
Authorization: Bearer [YOUR_AUTH_TOKEN]
Content-Length: 38
Content-Type: application/json; charset=UTF-8
X-Upload-Content-Type: image/jpeg
X-Upload-Content-Length: 2000000

{
  "name": "myObject"
}

我可以对大小约为 5MB 的文件进行分段上传,但我需要能够上传更大的文件,并且可恢复是唯一的选择。如果需要,我可以发布有效的多部分代码。

【问题讨论】:

  • 也许尝试不要将request.headers['content-length'] = (request.contentLength).toString(); 转换为toString(),因为内容长度必须是整数。让我知道这是否有效。 :)

标签: rest http flutter dart google-drive-api


【解决方案1】:

我通过使用 http StreamedRequest 类解决了这个问题。下面发布的代码适用于 Google Drive V3 上传 mp4 视频。

Future handleUploadData(Map headers, String filename, String path) async {
    final file = new File(path);
    final fileLength = file.lengthSync().toString();
    String sessionUri;

    Uri uri = Uri.parse('https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable');

    String body = json.encode({ 'name' : filename });

    final initialStreamedRequest =
    new http.StreamedRequest('POST', uri)
      ..headers.addAll({
        'Authorization': headers['Authorization'],
        'Content-Length' : utf8.encode(body).length.toString(),
        'Content-Type' : 'application/json; charset=UTF-8',
        'X-Upload-Content-Type' : 'video/mp4',
        'X-Upload-Content-Length' : fileLength
      });

    initialStreamedRequest.sink.add(utf8.encode(body));
    initialStreamedRequest.sink.close();

    http.StreamedResponse response = await initialStreamedRequest.send();
    print("response: " + response.statusCode.toString());
    response.stream.transform(utf8.decoder).listen((value) {
      print(value);
    });

    if (response.statusCode == 200) {
      sessionUri = response.headers['location'];
      print(sessionUri);
    }

    Uri sessionURI = Uri.parse(sessionUri);
    final fileStreamedRequest =
    new http.StreamedRequest('PUT', sessionURI)
      ..headers.addAll({
        'Content-Length' : fileLength,
        'Content-Type' : 'video/mp4',
      });
    fileStreamedRequest.sink.add(file.readAsBytesSync());
    fileStreamedRequest.sink.close();

    http.StreamedResponse fileResponse = await fileStreamedRequest.send();
    print("file response: " + fileResponse.statusCode.toString());
    fileResponse.stream.transform(utf8.decoder).listen((value) {
      print(value);
    });
  }

初始 StreamRequest 向 GDrive 发送一个请求,其中包含有关将要上传的文件的元数据,并接收在第二个文件 StreamRequest 中用于上传实际文件数据的位置 URI。目前这是在一个上传操作中完成的,但它可以分成块。

【讨论】:

    【解决方案2】:

    我遇到了大致相同的问题,除了我试图上传一个文本文件并且我想要一个原子请求以便能够将“If-Match”标头与文件 etag 一起使用(当我写“更新”代码,我正在同步,如果文件在同步期间被其他地方更改,我不想覆盖该文件)。

    我确实在使用 http.post 函数时遇到了困难,即使我正确设置了“Content-Length”标头,我也收到了“需要 411 长度”错误。

    Sean Coutinho 使用 http.StreamedRequest 的解决方案为我提供了工作代码,我可以使用它来让我的请求正常工作,所以谢谢!

    我会在这里发布我的代码以防它帮助其他人:

    import 'dart:convert';
    
    import 'package:http/http.dart' as http;
    import 'package:google_sign_in/google_sign_in.dart';
    
    enum RemoteFileType {
      FOLDER,
      FILE,
    }
    
    class RemoteFile {
      final RemoteFileType fileType;
      final String fileId;
      final String fileName;
    
      RemoteFile(
        this.fileType,
        this.fileId,
        this.fileName,
      );
    }
    
    // The boundary string
    const String MULTIPART_REQUESTS_BOUNDARY_STRING = 'foo_bar_baz';
    
    Map<String, String> _authHeaders;
    
    String _createMultiPartRequestBodyString(
      final Map<String, dynamic> requestMetaData,
      final String fileContentString,
    ) {
      return '\r\n--$MULTIPART_REQUESTS_BOUNDARY_STRING\r\n' +
          'Content-Type: application/json; charset=UTF-8\r\n\r\n' +
          jsonEncode(requestMetaData) +
          '\r\n--$MULTIPART_REQUESTS_BOUNDARY_STRING\r\nContent-Type: text/plain\r\n\r\n' +
          fileContentString +
          '\r\n--$MULTIPART_REQUESTS_BOUNDARY_STRING--';
    }
    
    Future<RemoteFile> createNewTextFile(
      final RemoteFile parentFolder,
      final String fileName,
      final String fileTextContent,
    ) async {
      final Map<String, dynamic> requestMetaData = {
        'mimeType': 'application/json',
        'title': fileName,
        'parents': [
          {'id': parentFolder.fileId}
        ],
      };
      final String multiPartRequestBodyString = _createMultiPartRequestBodyString(requestMetaData, fileTextContent);
      final http.StreamedRequest fileStreamedRequest = http.StreamedRequest(
        'POST',
        Uri.parse('https://www.googleapis.com/upload/drive/v2/files?uploadType=multipart'),
      );
      fileStreamedRequest.headers.addAll({
        'Authorization': _authHeaders['Authorization'],
        'Accept': 'application/json',
        'Content-Type': 'multipart/related; boundary=$MULTIPART_REQUESTS_BOUNDARY_STRING',
        'Content-Length': multiPartRequestBodyString.length.toString(),
        //'If-Match': 'my_etag_here_when_updating_existing_file_with_put',
      });
      fileStreamedRequest.sink.add(utf8.encode(multiPartRequestBodyString));
      fileStreamedRequest.sink.close();
    
      final http.StreamedResponse httpPostResponse = await fileStreamedRequest.send();
      // Do what you want with the response too
      //...
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-28
      • 1970-01-01
      相关资源
      最近更新 更多