Reputation: 21
Based off the documentation on Google Drive API I'm trying to upload a file to the root folder of a Google Drive. I have authentication of the user through Google Sign In, and that hasn't been an issue. I keep getting a 411 Error returned from the server that says
"POST requests require a
Content-length
header. That’s all we know.".
I have a Content-length header but it seems to not be accepted. Here's the code I have,
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);
});
The only line that I know looks off to me is the fileName, as the documentation on the API site is slightly different and I'm not sure if I'm encoding it correctly. Here's the API example on the Google site,
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"
}
I can do a multipart upload for a file about 5MB in size, but I need to be able to upload larger ones and resumable is the only option. I can post the multipart code that works if needed.
Upvotes: 0
Views: 1167
Reputation: 1277
I had roughly the same problem except I was trying to upload a text file and I wanted a single atomic request in order to be able to use the "If-Match" header with the file etag (When I'll write "update" code, I'm doing sync and I don't want to overwrite the file if it was changed by somewhere else during my sync).
I was really struggling with the http.post function and I was getting the "411 length required" error even though I was properly setting the "Content-Length" header.
The solution from Sean Coutinho using http.StreamedRequest gave me working code I could work from to get my request working, so thank you!
I'll post my code here in case it helps other people:
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
//...
}
Upvotes: 0
Reputation: 21
I solved the issue by using the http StreamedRequest class. The code posted below works with Google Drive V3 to upload a mp4 video.
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);
});
}
The initial StreamRequest sends a request to GDrive with meta data about the file that will be uploaded, and receives a location URI that is used in the second file StreamRequest to upload the actual file data. Currently this is done in one upload action, but it could be split up into chunks.
Upvotes: 2