I'm attempting to make a batch request to the Gmail API, as outlined here: https://developers.google.com/gmail/api/guides/batch
I've got to the point at which I have the URL, headers, and body of my request, and the headers include an (accurate) Content-Length
attribute. However, when I make the POST
request to the Gmail batch request endpoint, the response is a [411 Length-Required][1] response, returning in the body an HTML document stating:
411. That’s an error.
POST requests require a Content-length header. That’s all we know.
My code looks something like the following:
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:googleapis/gmail/v1.dart';
import 'package:http/http.dart' as http;
class GoogleClient {
static const _SCOPES = const [GmailApi.GmailReadonlyScope];
static GoogleSignIn _googleSignIn = GoogleSignIn(scopes: _SCOPES);
static String _gmailUrl = 'https://www.googleapis.com/gmail/v1/users/';
static String _batchGmailUrl = 'https://www.googleapis.com/batch/gmail/v1';
final FirebaseAuth _auth = FirebaseAuth.instance;
GoogleSignInAccount _user;
Future<List<Message>> fetchEmailsBatch({List<String> gmailMessageIds}) async {
await _ensureUserIsAuthenticated();
final boundary = 'batch_email';
var body = '--' + boundary + '\r\n';
for (String id in gmailMessageIds) {
final messageUrl = '/gmail/v1/users/' + _user.email + '/messages/' + id;
body += 'Content-Type: application/http\r\n';
body += 'Content-ID: email:' + id + '\r\n\r\n';
body += 'GET ' + messageUrl + '\r\n\r\n';
body += '--' + boundary + '\r\n';
}
final bodyAsBytes = utf8.encode(body);
final headers = await _batchAuthHeaders(bodyAsBytes.length);
final response = await http.post(_batchGmailUrl,
headers: await _batchAuthHeaders(bodyAsBytes.length),
body: bodyAsBytes);
}
Future<Map<String, String>> _batchAuthHeaders(int length) async {
var headers = await _user.authHeaders;
headers[HttpHeaders.contentLengthHeader] = length.toString();
headers[HttpHeaders.contentTypeHeader] =
'multipart/mixed; boundary=batch_email';
headers[HttpHeaders.hostHeader] = 'www.googleapis.com';
return headers;
}
Future<void> _ensureUserIsAuthenticated() async {
if (_user == null) {
await signIn();
}
}
}
As you can see, all of the sub-requests being made in the body of the batch request are GET
requests, and therefore are not covered by the error message I'm receiving. I can't seem to find any more expansive documentation that that already linked above, nor any other similar issues already reported on GitHub or StackOverflow.
Any help would be greatly appreciated - thanks in advance!
[1]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/411#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,a%20defined%20Content%2DLength%20header.
This is a reported bug in Flutter that has been fixed since. As of writing this, it's not yet visible in a release but it will appear soon, hopefully.
As a temporary workaround, you can use the lower level dart:io
calls instead.