Skip to content

Allow users to configure forwarded headers? #916

Description

@jgeewax

The Cloud Trace team wants to make it easy for users to send trace IDs along with various API calls, effectively just adding a special header (X-Cloud-Trace-Context) with an opaque ID.

The issue is that this ID will vary across requests, which presents a bit of an issue in that whole concurrency thing...

For example, here's how I'd want to do this in my app: https://gist.github.com/jgeewax/3b351d3522fdf061bdea

Note that I have zero idea if this is at all reasonable given the isolation model for Node. (I think, for example, that this won't work for Express, and that we should be doing something with req.locals....? see http://stackoverflow.com/questions/33310324/using-express-middleware-for-per-request-module-configuration)

Anyway -- we need to find a way to allow users to...

  1. Set custom headers to be forwarded along with each API request to Google
  2. Configure those headers in one place (ie, not as arguments to each method as people don't want to change all their code for this)
  3. Make sure that it's obvious how to do this the right way (ie, avoid giving people a gun and aiming it at their feet -- which setTraceContext in my example might do...)

Activity

  1. stephenplusplus commented on Oct 26, 2015

    @stephenplusplus
    Contributor

    👍 I imagine most of the time, users might just want their own custom UA, so we should support: (edit: maybe that doesn't make sense actually? Either way, they could manipulate whatever part of the request they want:)

    var gcloud = require('gcloud')({
      decorateRequest: function(reqOpts) {
        reqOpts.headers['User-Agent'] = 'My App';
        return reqOpts;
      }
    });

    Then overriding per service should be pretty straightforward:

    var dataset = gcloud.datastore.dataset({
      decorateRequest: function(reqOpts) {
        reqOpts.headers['X-App-Id'] = 'app-whatever';
        return reqOpts;
      }
    });

    As far as allowing per-request control, like "got a request for resource X, attach a custom header while we download it then remove it after", maybe we can go even one level deeper:

    var bucket = gcloud.storage.bucket('my-app-bucket');
    var file = bucket.file('my-app-file', {
      decorateRequest: function(reqOpts) {
        reqOpts.headers['X-Requester-Id'] = 'user-ip';
        return reqOpts;
      }
    });

    All of these have the decorated request options, while any other service (Dataset) or object (Bucket, File, Topic, etc) wouldn't be touched.

    // X-Requester-Id header in place
    file.createReadStream();
    file.download();
    
    // No X-Requester-Id header in place
    bucket.upload();
    bucket.getMetadata();

    Regarding the gist, I put together how this would look: https://gist.github.com/jgeewax/3b351d3522fdf061bdea

  2. stephenplusplus commented on Oct 26, 2015

    @stephenplusplus
    Contributor

    Another option:

    // Global
    gcloud.decorateRequest(function(reqOpts) {
      // modify reqOpts
      return reqOpts;
    });
    
    // Service
    var gcs = gcloud.storage();
    gcs.decorateRequest(function(reqOpts) {
      // modify reqOpts
      return reqOpts;
    });
    
    // Service object
    var bucket = gcs.bucket('my-bucket');
    var file = bucket.file('my-file');
    
    file.decorateRequest(function(reqOpts) {
      // modify reqOpts
      return reqOpts;
    });
  3. stephenplusplus commented on Nov 23, 2015

    @stephenplusplus
    Contributor

    Let's go with this:

    var opts = { projectId: 'my-project-id' };
    var storage = gcloud.storage(opts);
    
    // Need to set a custom header
    var newOpts = extend({}, opts, {
      decorateRequest: function(reqOpts) {
        reqOpts.headers['X-Cloud-Trace-Context'] = '...';
      }
    });
    
    var newStorage = gcloud.storage(newOpts);
    
    storage.getBuckets(); // no custom header sent
    newStorage.getBuckets(); // custom header sent

    gcloud as well as any service object will accept the decorateRequest function.

    Sound okay?

  4. callmehiphop commented on Nov 23, 2015

    @callmehiphop
    Contributor

    Reminds me of angular interceptors, a similar interface to that could be nice

    var storage = gcloud.storage({ projectId: 'my-project-id' });
    
    storage.interceptors.push({
      request: function (reqOptions) {
        if (someCondition) {
          reqOptions.headers['X-Yay-Headers'] = 'woot';
        }  
        return reqOptions;
      }
    });

    And as a side note, with the upcoming switch to promises this could potentially provide a nice interface for failed request logic, assuming we followed angular's api.

  5. stephenplusplus commented on Nov 23, 2015

    @stephenplusplus
    Contributor

    Looks cool to me. 👍

  6. added a commit that references this issue on Feb 26, 2026
  7. added a commit that references this issue on Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions