Translucent Intercepting Proxy built with NodeJS – tip.js

Writing AJAX-driven web-applications without being able to modify the according web-service can be quite tricky, especially if the API is under active development or review.

Inspired by Peteris Krumins’ post, we wrote tip.js: a translucent intercepting proxy written in nodeJS. tip.js resides between the web-application and the according web-service and is able to influence the responses of AJAX-requests in a comfortable way.

La Gentz tip.js - translucent intercepting proxy written in NodeJSWorking with a rich API requires to identify and to pick the requests / responses which should be modified without touching and influencing all other traffic (e.g. vanilla image-GET calls). And here’s why it’s a translucent intercepting proxy: without having defined any requests to modify, tip.js behaves like a transparent proxy. After adding a description of the calls you want to tackle, tip.js becomes “translucent”.

Getting started:

In order to get the idea behind tip.js without pain, follow the step-by-step example which is explained in detail below.

Start the demo-backend via

flo@devhost:~$ node demo-backend.js

This will launch a very simple web-server (listening on port 8000) responding to POSTs sent to /LogOn. You can point your browser to http://localhost:8000 for a quick test, I’m using curl here to test this setup in more detail:

flo@devhost:~$ curl -i -X POST -d '{"username":"gregor"}' \

http://localhost:8000/LogOn

In response, you’ll get a json object:

{"state":{"RC":1,"ErrMsg":"Everything's fine (demo-backend)"}}

Now we’ll start tip.js (assuming, that the example interceptor.js resides in the same folder as tip.js)

flo@devhost:~$ node tips.js host=localhost target_port=8000

tips.js can be used with following (optional) command line parameters:

host=hostname    // that's the host of the web-service
target_port=80   // the http-port of the web-service (default is 80)
listen_port=8001 // the listen_port (default is 8001)
interceptor_file=interceptors.js // contains interceptor-patterns
                                 // (default is interceptors.js)

Now use curl again, this time use tip.js’ listen_port:

flo@devhost:~$ curl -i -X POST -d '{"username":"gregor"}' \

http://localhost:8001/LogOn

Using ‘gregor’ as username will trigger a modified response defined in interceptors.js and results in a static response directly from tip.js (try to curl with a different username and you’ll still get a response from the demo-backend!)

{"RC":-2,"ErrMsg":"This is a tip.js generated response"}

Now use ‘flo’ as username, ‘flo’ triggers an interceptor-definition which uses the original response from the demo-backend and modifes it (and also adds a new property ‘lang’):

flo@devhost:~$ curl -i -X POST -d '{"username":"gregor"}' \

http://localhost:8001/LogOn

This will yield:

{"state":{"RC":1,"ErrMsg":"Everything's fine (demo-backend) \\
                            + Message from tip.js"},"lang":"de"}

What’s happening here?

After starting tip.js, http://localhost:8001 will deliver everything from your web-service (http://host:target_port) except the part of the traffic which is defined in interceptor.js.
The interceptor-file is a simple file containing an array with interceptor-objects. The method-property denotes the url-specific web-service-entry point you want to modify, the request-property describes the request which should be altered and response can either be a simple object (Example 1) or a function (see Example 2).

// Example 1
interceptors = [{
   method: '/LogOn',
   request: {
      username: 'gregor'
   } 
   response: {
      state: {
         RC: -1,
         ErrMsg: "This is a tip.js generated response"
      }
   }
},
{ // add more interceptor-definitions here
}
];

Let’s imagine you’ve just added a fancy error-message handling in your front-end code which should appear, whenever the /LogOn call fails, but you can not test this feature, because your web-service can’t be forced to return the specific error code needed.
To test your new feature, just use an interceptor as described above: the method is ‘/LogOn’ and you will intercept all ‘/LogOn’-calls containing the the username ‘gregor’. Whenever a /LogOn call with username ‘gregor’ happens, tip.js returns the object defined in the response-property (which in this case is an error-code you are waiting for).

But what, if your web-application needs more than a simple response-object? Defining a function at the response-property allows to access both, the initial response-object (via args.request and the answer from the back-end via args.response. You can now work with both of them to define a modified response – in the example below it is assumed that we want to modify the language of the user ‘flo’ by adding a ‘lang’ property and additionally the original ‘ErrMsg’ is modified:

// Example 2
interceptors = [
{
   method: '/LogOn',
   request: {
      username: 'flo'
   }
   response: function(args){
      // args.request holds the complete response-object
      // args.request is the response of the web-service
      args.response['lang']='fr'; // Let's talk french now!
      args.response['state']['ErrMsg'] = args.response['state']['ErrMsg']
                                            + " + Message from tip.js";
      return args.response;
   }
},
{ // add more interceptor-definitions here
}
];

After starting tip.js, the provided interceptor-file is being watched and reloaded, as soon as its content changes – that is: you don’t need to restart tip.js when you add or modify your interceptor-definitions. nodeJS provides this functionality in a single line (see also source code):

 fs.watchFile(cfg.interceptor_filename, update_interceptors);

There are some current limitations with tip.js you should be aware of:

  • tip.js can only handle HTTP-POST requests from the frontend
  • both, request and response of your back-end have to be JSON objects
  • tip.js is just a developer-tool, it’s not designed to be used in a productive environment, since it collects all chunked requests / response to be able to decide which call should be modified. (which slows down the communication between web-application and web-service)

Hope this helps, don’t hesitate to post questions / remarks!
Florian.

This entry was posted in nodejs and tagged , , , , . Bookmark the permalink.

3 Responses to Translucent Intercepting Proxy built with NodeJS – tip.js

  1. Carl Bourne says:

    Hi Florian,

    Very interesting article!

    Could this be used to intercept then modify a form field before it is sent to the backend web sevice? Also, could you then do the same with the response before sending it back to the browser?

    Regards,

    Carl

    • Hi Carl,
      It’s definitely doable, however, the code as it is discussed here can not be used without modification. Anyways, the primary structure of the code for your question would be pretty much the same as tip.js
      Thanks for stopping by,
      Florian

  2. Pingback: tableau toile design