grpc/grpc-node

Add server interceptors

anjmao opened this issue Β· 28 comments

Hi,
I'm using grpc-node native package for building backend API. I noticed that there is not server interceptors (correct me if I'm wrong). I want to write server interceptors for logging, auth and so on. Without it grpc-node is pretty match useless for production. Could you share what is the status on this feature, because I'm thinking to implement it in my fork branch. Thanks.

Server interceptors aren't in our roadmap at the moment.

@nicolasnoble Thanks for response. Would you accept PR if I implement them, since I need server interceptors any way to be able to use grpc for production. I saw that there is also no native C++ server interceptors, but I'm looking to implement them in js side.

We would require this to go through a GRFC process in order to discuss the API, but otherwise, yes.

Check https://github.com/grpc/proposal

@theogravity Thanks for a link. I rewrote my server in go since it has the best grpc implementation.

any updates on server interceptors as of now? @nicolasnoble

Hi, any updates?

Any updates about server interceptors?

Nobody has proposed an API design yet, so nothing has changed.

Any updates here? Seems like interceptors would be pretty useful for things like auth and observability. Now that gRPC-web has hit 1.0, one could serve websites with gRPC and we'll need some sort of auth mechanism. It's not hard to roll your own interceptors, but it would be better if there was a standard way to do this.

Any updates about server interceptors?

Any updates on this?

Also curious about this. Would be nice for rolling gRPC servers in Node to have some β€œofficial” support for interceptors

In case someone is interested and wants to support this, there're some discussion about this here - https://groups.google.com/g/grpc-io/c/sF8rO9ltkpA/m/qWC8an4IAwAJ

huan commented

@Abhi347 thanks for pushing this moving forward, appreciate it!

https://github.com/deeplay-io/nice-grpc/tree/master/packages/nice-grpc has implemented server side middleware for grpc-js.

Any updates about server interceptors?

@murgatroid99 Any plan to implement interceptors on grpc-node server side?

We currently have no plans to implement server interceptors in the gRPC Node library.

Hey @murgatroid99 - thanks for still being active on this thread

Do you have the grpc-node roadmap documented? I'm surprised after ~4 years interceptors have still have not been prioritised

Someone made a comment that you need a proposed API design - do you have a guide on contributing?

Do you have the grpc-node roadmap documented?

No. We don't really have a roadmap. It's really just team-level quarterly objectives. For the past couple of years, the main thing I have been working on is xDS features.

Someone made a comment that you need a proposed API design - do you have a guide on contributing?

The proposal process is described in the README in the https://github.com/grpc/proposal repository. Check out the Node client interceptors proposal for an example that is probably similar to what a server interceptors proposal might look like.

@murgatroid99 and other memeber of grpc. Please keep on going. The interceptors on the node server side is very importent for users authentication. Is there workaround about authentication with jwt (json web token)? Thanks.

Is there workaround about authentication with jwt (json web token)?

You may have an idea how to do it with this code :

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

const proxyHandler = {
    get(target, propKey) {
        if (propKey !== 'addService') {
            return target[propKey];
        }
        return (service, implementation) => {
            const newImplementation = {};
            const lookup = lookupServiceMetadata(service, implementation);
            for (const k in implementation) {
                const name = k;
                const fn = implementation[k];
                newImplementation[name] = (call, callback) => {
                    const ctx = {
                        call,
                        service: lookup(name),
                    };
                    const newCallback = callback => {
                        return (...args) => {
                            ctx.status = {
                                code: 0,//grpc.status.OK,
                            };
                            const err = args[0];
                            if (err) {
                                ctx.status = {
                                    code: 2,//grpc.status.UNKNOWN,
                                    details: err,
                                };
                            }
                            callback(...args);
                        };
                    };

                    const interceptors = target.intercept();
                    const first = interceptors.next();
                    if (!first.value) { // if we don't have any interceptors
                        return new Promise(resolve => {
                            return resolve(fn(call, newCallback(callback)));
                        });
                    }
                    first.value(ctx, function next() {
                        return new Promise(resolve => {
                            const i = interceptors.next();
                            if (i.done) {
                                return resolve(fn(call, newCallback(callback)));
                            }
                            return resolve(i.value(ctx, next, callback));
                        });
                    }, callback);
                };
            }
            return target.addService(service, newImplementation);
        };
    },
};

const proxyServer = (server) => {
  server.interceptors = [];
  server.use = fn => {
      server.interceptors.push(fn);
  };
  server.intercept = function* intercept() {
      let i = 0;
      while (i < server.interceptors.length) {
          yield server.interceptors[i];
          i++;
      }
  };
  return new Proxy(server, proxyHandler);
}

function startServer() {

    const protoDefinition = protoLoader.loadSync("PATH_TO_PROTOFILE",
            {
                keepCase: true,
                longs: String,
                enums: String,
                defaults: true,
                oneofs: true,
            });
        const proto = grpc.loadPackageDefinition(protoDefinition);
        const server = proxyServer(new grpc.Server());

      async function login(call, callback) {
          // login code where you generate a token for the session, etc. ...
      }

       server.addService( proto.myservice, login); // ... adaptation necessary

       //middleware function that will be called before each grpc methods
        const grpcInterceptor = async (ctx, next, callback) => {
            function copyMetadata(call) {
                const metadata = call.metadata.getMap();
                const responseMetadata = new grpc.Metadata();
                for (let key in metadata) {
                    responseMetadata.set(key, metadata[key]);
                }
                return responseMetadata;
            }
            //token passed by http metadata
            const token = ctx.call.metadata.internalRepr.get("token")[0]
            
            // ... block of code to validate token, etc.
            let validated = true;

            // if all is well, we call original grpc method
            if (validated) {
                await next();
            } else {
                  error = new Error("Invalid token");
                  error.code = 16; //UNAUTHENTICATED
                  callback(error);
            }           
        };

    
        server.use(grpcInterceptor);
        server.bindAsync(address, grpc.ServerCredentials.createInsecure(), (err, result) => !err ? server.start() : console.error(err));
       
}

startServer();


update : added syntax highlighting to the code example. Thanks to @richardpringle.

@mfecteau42, use ```js to get syntax highlighting in your code example

I have proposed a design for a server interceptors API: grpc/proposal#406. If you have any feedback on the design please comment there.

@murgatroid99 do you plan to merge these changes soon as they are approved? thanks for proposing a design! πŸ™

Our convention is to have a review period of at least two weeks before finalizing a proposal. I am working on the implementation in parallel to the proposal review, and even once the proposal is accepted it will still take me some time to finish the implementation.

Update: the design proposal grpc/proposal#406 is going through final review next week, and the implementation PR is out at #2650.