electron: net.request response is missing content-type header in 304 responses

Preflight Checklist

  • [x ] I have read the Contributing Guidelines for this project.
  • [ x] I agree to follow the Code of Conduct that this project adheres to.
  • [ x] I have searched the issue tracker for an issue that matches the one I want to file, without success.

Issue Details

  • Electron Version: 10.1.7
  • Operating System: Windows 10 Enterprise 1909

When making a request with net.request, I can see that my response has a content-type header of application/json; charset=utf-8 inside of my webRequest.onHeadersReceived handler:

electron.session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
  // details.responseHeaders['content-type'] === 'application/json; charset=utf-8'
});

But by the time the net.request request.on('response') handler receives the headers, the content-type is gone.

request.on('response', (response) => {
  /* 
  response.headers === {
    // no content-type
  };
  */
});

I haven’t tested if this is an issue in all types of responses, but I can tell you this is occurring without fail for the case when the response.statusCode === 200, but the response.headers.status === 304.

Expected Behavior

For net.request response handler to receive the content-type that clearly should exist on the response (proved by webRequest.onHeadersReceived).

Actual Behavior

net.request response handler is receiving what seems to be a cached set of headers (or something?), which do not match what is seen in webRequest.onHeadersReceived.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 8
  • Comments: 19 (6 by maintainers)

Most upvoted comments

This is still an issue in 18.2.2 (and 19.0.0-beta.5). Any news or plans to fix this?

This looks like a regression caused by https://github.com/electron/electron/pull/21552. As an experiment, I tried adding the non-“raw” headers to the internal response object:

diff --git a/shell/browser/api/electron_api_url_loader.cc b/shell/browser/api/electron_api_url_loader.cc
index afe48e40c..6a836f7af 100644
--- a/shell/browser/api/electron_api_url_loader.cc
+++ b/shell/browser/api/electron_api_url_loader.cc
@@ -68,6 +68,22 @@ struct Converter<network::mojom::CredentialsMode> {
   }
 };  // namespace gin

+template <>
+struct Converter<net::HttpResponseHeaders> {
+  static v8::Local<v8::Value> ToV8(
+      v8::Isolate* isolate,
+      const net::HttpResponseHeaders& headers) {
+    gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
+    size_t i = 0;
+    std::string name;
+    std::string value;
+    while (headers.EnumerateHeaderLines(&i, &name, &value)) {
+      dict.Set(name, value);
+    }
+    return dict.GetHandle();
+  }
+};
+
 }  // namespace gin

 namespace electron {
@@ -561,6 +577,8 @@ void SimpleURLLoaderWrapper::OnResponseStarted(
   DCHECK(response_head.raw_request_response_info);
   dict.Set("rawHeaders",
            response_head.raw_request_response_info->response_headers);
+  dict.Set("headers",
+           *response_head.headers);
   Emit("response-started", final_url, dict);
 }
 

(NB. above patch is hacky and doesn’t allow for repeated headers, it was just enough to let me see whether this theory about raw headers was correct.)

The headers object contained the correct content-type and content-length, even when the rawHeaders didn’t. For example:

image

As further confirmation, the above PR was backported to 7.x in v7.1.10. When I run the fiddle on v7.1.9, it works as expected.

cc @zcbenz

Here is a standalone testcase :

https://gist.github.com/arantes555/99f8976266d340bad42b2894cb50d7d5

This shows that when the server responds with a 200, response correctly contains content-type and content-length. But when the server responds with a 304, response is shown to be a 200 but does not contain content-type and content-length.

I also showed the behaviour of onHeadersReceived that @themadtitanmathos noticed : on this callback, headers are correct.

Also, with electron@6, everything is correct.

bump

Any news ? PS: details also does not return the list of headers that are supposed to be used by the request. This seems to be a precedence error. onBeforeSendHeaders should be triggered after the “default” headers have been populated.

we never got to the point of setting up our own server to test, but testing against GitHub and GitLab APIs, we only got a repro while we had something in the cache. For whatever reason, when we manually set our own If-None-Match that would prompt a 304 from the server, we got back an unmolested 304 (i.e. 304 status code, no body). We came to the conclusion that it was because of a bad interaction with the Electron and/or Chrome request caches. I’m confident that if you cleared both caches and ran the 304 first, you would get back the content-type and content-length as expected. In other words, I believe it’s some problematic way that the cache results are being “merged” into the final results that causes the headers to drop. The same mechanism is probably also preventing the onHeadersReceived callback from working (#27894) by overriding the results after the callback occurs.

I just hit the same problem : electron does not repopulate content-type and content-length when a 304 is “transformed” in 200 from the cache.

Also, I can confirm that the behaviour was different on electron <= 6 : up to electron6, the 200 seen in this case correctly has content-type and content-length.

No repro example yet, but just to add some details we’ve found since: net seems to be adding the status header on its own. We’ve mostly (but not exclusively) been testing against GitHub’s REST API, and we’ve confirmed through Postman that the servers we’ve hit don’t include the status header, but it consistently comes back 304 on many requests, unless we obliterate the Electron/Cache folder from the user data. So far it seems like we’re getting back legitimate 200s and net is arbitrarily adding status: 304 and deleting the content-type header (and removing custom headers, #27894 which may be the same issue). When we force GitHub to give us a 304 by including an ETag in If-None-Match, net.request gives us back a completely normal-looking 304 with no body. Basically we’re just really perplexed by the inconsistent and arbitrary way that net sort-of-does-sort-of-doesn’t transparently populate 304 results from the Electron cache, or just decides not to even attempt such a population at all. We haven’t been able to find any existing issues or documentation that speak to its intended behavior.