5

Starting Sept 13th the Google Tasks BatchRequest update workflow is triggering a 400 error return "Duplicate Request ID in Batch Request" within an application that has remained stable for years. I can't find anything in the request that indicates a duplicate request id. Anyone have any idea what's up? Did Google change something?

Here's a copy of the response I'm receiving when sending a simple batch task insert request ...

[{
  "error": {
    "code": 400,
    "message": "Duplicate Request ID in Batch Request: ",
    "errors": [
      {
        "message": "Duplicate Request ID in Batch Request: ",
        "domain": "global",
        "reason": "badRequest"
      }
    ],
    "status": "INVALID_ARGUMENT"
  }
}]

UPDATE

While investigating this issue I found that Google made a change that now requires the Content-ID header on each batch request item. This header is currently not set when using the .Net Google.Apis.Requests.BatchRequest class.

To work around this I was able to create a new local implementation of Google.Apis.Requests.BatchRequest and inject a "Content-ID" header then creating each request entry.

private static long _id = 0;

[VisibleForTestOnly]
internal static async Task<HttpContent> CreateIndividualRequest(IClientServiceRequest request)
{
    HttpRequestMessage requestMessage = request.CreateRequest(false);
    string requestContent = await CreateRequestContentString(requestMessage).ConfigureAwait(false);

    var content = new StringContent(requestContent);
    content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/http");
    content.Headers.Add("Content-ID", (_id++).ToString());
    return content;
}
Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164

3 Answers3

1

Thank you for sorting this out! The Content-ID header solves the problem.

But instead of creating a new implementation of Google.Apis.Requests.BatchRequest you can inject a HttpExecuteInterceptor into the underlying service which is then called by the batch request.

Create the interceptor:

public class GoogleBatchInterceptor : IHttpExecuteInterceptor
{
    public Task InterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request.Content is not MultipartContent multipartContent)
        {
            return Task.CompletedTask;
        }
        
        foreach (var content in multipartContent)
        {
            content.Headers.Add("Content-ID", Guid.NewGuid().ToString());
        }

        return Task.CompletedTask;
    }
}

Add the interceptor to the service:

CalendarService calendarService = CreateTheCalendarService();
calendarService.HttpClient.MessageHandler.AddExecuteInterceptor(new GoogleBatchInterceptor());
Roman Weis
  • 347
  • 3
  • 8
  • One user also reported it here: https://github.com/googleapis/google-api-dotnet-client/issues/1969 – Andrew Truckle Sep 26 '21 at 05:47
  • Should I added your code to a new CS file? Where is best place to put it? Will things still be ok using this code once Google fix the issue? – Andrew Truckle Sep 26 '21 at 05:49
  • 1
    the interceptor can be placed anywhere in your code base, it's certainly only a temporary work around, and can be removed once the github issue is resolved. – Roman Weis Sep 26 '21 at 06:17
  • For others benefit, you code has been ported to VB.Net here: https://stackoverflow.com/a/69335639/2287576 – Andrew Truckle Sep 26 '21 at 14:01
  • 1
    It doesn't make a whole lot of sense to `await` on `Task.CompletedTask`. I'd suggest making it not `async` and returning `Task.CompletedTask` instead. (I've submitted an edit updating the answer accordingly. Er, I will, the edit queue is full right now.) – Craig Sep 27 '21 at 17:16
  • yes, you are right! probably the guid is also overkill, too, but hey it's a workaround (that will stay in the code base for the next 6 months at least :)) – Roman Weis Sep 27 '21 at 17:35
1

Update 2021/09/28:

This issue has been marked as fixed and you shouldn't be experiencing this anymore:

Thank you for your patience, as of now this issue is fixed. If this issue persists let us know in a comment so we can assist you.

Original answer:

This doesn't seem to be limited to the library, nor to Tasks API, since it's happening at least in Calendar API too.

Now the batch endpoints seem to require Content-ID to be provided in the request, so it has to be added manually if the library doesn't do it.

It's still not clear whether this is an expected change of behavior or not.

It has been reported in the .NET library GitHub:

And also in Google Issue Tracker:

I'd suggest starring the referenced issue in Issue Tracker in order to help prioritizing it (if this is an expected behavior, at least it should be mentioned in the documentation that Content-ID has to be provided).

Iamblichus
  • 18,540
  • 2
  • 11
  • 27
0

I can confirm this was an unintended issue on the backend side which affected the Calendar and Tasks APIs batch endpoint.

A fix has been fully rolled out and both endpoints should be working as before, i.e. ContentID is not required for batched requests. For those of you who put patches or workarounds in place, you may remove them.

Also, you may follow https://github.com/googleapis/google-api-dotnet-client/issues/1969, specially if you have questions/related issues.

Amanda Tarafa Mas
  • 1,043
  • 8
  • 13