Search code examples
flutterdartgmailgmail-api

Google APIs Batch Requests returning 411 Length-Required Error


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.


Solution

  • 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.