Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Middleware Server Transport (Express/Connect) #12

Merged
merged 1 commit into from over 1 year ago

2 participants

David Ellis countrodrigo
David Ellis
Collaborator

Wrote a Connect/Express-style middleware for the JSON-RPC server, after some prompting.

The most difficult part was remembering the Express API. :)

countrodrigo countrodrigo commented on the diff
test/index.js
((16 lines not shown))
  53 + loopback: function(arg, callback) { callback(null, arg); }
  54 + });
  55 + app.use('/rpc', server.transport.middleware);
  56 +
  57 + //app.listen(55555); // Express 3.0 removed the ability to cleanly shutdown an express server
  58 + // The following is copied from the definition of app.listen()
  59 + var server = http.createServer(app);
  60 + server.listen(55555);
  61 +
  62 + var client = new Client(new ClientHttp('localhost', 55555, { path: '/rpc' }));
  63 + client.register('loopback');
  64 +
  65 + http.get({
  66 + port: 55555,
  67 + path: '/foo'
  68 + }, function(res) {
2
countrodrigo Collaborator

Why bother performing a plain http test? It makes the test case a bit confusing. Are you testing that your middleware doesn't screw up plain http requests?

David Ellis Collaborator
dfellis added a note

Yes, that's exactly why. The whole point of a middleware-style transport over the regular HTTP transport is that the transport can share the same HTTP server as the regular HTTP traffic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
countrodrigo countrodrigo commented on the diff
lib/transports/server/middleware.js
((8 lines not shown))
  8 +function MiddlewareTransport(config) {
  9 + // Initialize the EventEmitter for this object
  10 + EventEmitter.call(this);
  11 +
  12 + // Make sure the config object exists, the handler function exists,
  13 + // and the Access-Control-Allow-Origin header is properly set. Also
  14 + // allow the user to provide a reference to the underlying HTTP server
  15 + // so the ``shutdown`` method can work as expected, if desired.
  16 + config = config || {};
  17 + this.handler = function fakeHandler(json, next) { next({}); };
  18 + this.acao = config.acao ? config.acao : "*";
  19 + this.server = config.server || null;
  20 + this.middleware = this.requestHandler.bind(this);
  21 +
  22 + return this;
  23 +}
2
countrodrigo Collaborator

; ?

David Ellis Collaborator
dfellis added a note

I'm not assigning this function to a variable, here (see line 8 above), so no semicolon needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
countrodrigo countrodrigo commented on the diff
lib/transports/server/middleware.js
((3 lines not shown))
  3 +
  4 +// Connect/Express middleware style JSON-RPC server transport
  5 +// Let's you have a hybrid Connect/Express server that also performs JSON-RPC
  6 +// on a particular path. Still done as an instance so you can conceivably have
  7 +// multiple JSON-RPC servers on a single Connect/Express server.
  8 +function MiddlewareTransport(config) {
  9 + // Initialize the EventEmitter for this object
  10 + EventEmitter.call(this);
  11 +
  12 + // Make sure the config object exists, the handler function exists,
  13 + // and the Access-Control-Allow-Origin header is properly set. Also
  14 + // allow the user to provide a reference to the underlying HTTP server
  15 + // so the ``shutdown`` method can work as expected, if desired.
  16 + config = config || {};
  17 + this.handler = function fakeHandler(json, next) { next({}); };
  18 + this.acao = config.acao ? config.acao : "*";
6
countrodrigo Collaborator

Do you really want to set the access control to * by default? That seems like a security risk that you are encouraging. If you just don't send the header then the browsers will force the requests to be coming from the same subdomain, which is a better default

David Ellis Collaborator
dfellis added a note

ACAO is security-by-stupidity, to be honest with you. You have to trust the client to obey the ACAO directives to not access things they aren't supposed to, which you can't do because older browsers and anyone with curl/wget can bypass them anyways and hand-craft malicious requests.

Your server security needs to be enforced on the server-side, not the client-side, so this ACAO configuration is just to placate Firefox (the ones that started this nonsense). You're no less secure with ACAO: "*" than with restricting the ACAO value, but just making things more difficult on yourself and possibly your own users if you forgot to include a subdomain in the ACAO listing and missed it during testing, which makes it the same kind of "security" as DRM.

countrodrigo Collaborator
David Ellis Collaborator
dfellis added a note

I still don't see the benefit to ACAO -- if you make a service public on the 'net, you need to protect it against malicious connection attempts. ACAO gives young developers (and they disproportionately gravitate towards the web) a false sense of security that setting the ACAO value correctly will "protect" them.

On the flip-side, stupid bugs for clients arise from different browsers having a different "strictness" on interpreting ACAO (for instance IE didn't enforce ACAO at all until years after Firefox started, so "Windows Shops (tm)" didn't fix their ACAO-busted sites for a while. Simple user pain.

On top of that, young developers that don't know about the ACAO headers may think cross-site AJAX is simply impossible and do crazy things like the proxy server thing you mentioned, just because they didn't know how to configure their original server correctly.

ACAO = all pain, no gain, and I hate that kind of nonsensical bullshit tech with a firey passion. :P

countrodrigo Collaborator

I take that to be a merge as-is.

David Ellis Collaborator
dfellis added a note

Didn't mean to be combative, but ACAO is a (mis)feature I don't really care for, and I'm being nice by letting people override it. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
countrodrigo
Collaborator

Comments addressed, views expressed, nothing to duel over. Merging.

countrodrigo countrodrigo merged commit 953ecd0 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Mar 12, 2013
David Ellis dfellis Wrote a Connect/Express-style middleware for the JSON-RPC server. 66ccce9
This page is out of date. Refresh to see the latest.
32 README.md
Source Rendered
@@ -29,6 +29,7 @@ var Client = jsonrpc.client; // The client constructor function
29 29
30 30 var ServerHttp = jsonrpc.transports.server.http; // The server HTTP transport constructor function
31 31 var ServerTcp = jsonrpc.transports.server.tcp; // The server TCP transport constructor function
  32 +var ServerMiddleware = jsonrpc.transports.server.middleware; // The server Middleware transport constructor function (for Express/Connect)
32 33
33 34 var ClientHttp = jsonrpc.transports.client.http;
34 35 var ClientTcp = jsonrpc.transports.client.tcp;
@@ -41,6 +42,15 @@ var jsonRpcTcpServer = new Server(new ServerTcp(8001), {
41 42 loopback: function(obj, callback) { callback(undefined, obj); }
42 43 });
43 44
  45 +var express = require('express');
  46 +var app = express();
  47 +app.use(express.bodyParser());
  48 +var jsonRpcMiddlewareServer = new Server(new ServerMiddleware(), {
  49 + loopback: function(obj, callback) { callback(undefined, obj); }
  50 +});
  51 +app.use('/rpc', jsonRpcMiddlewareServer.transport.middleware);
  52 +app.listen(8002);
  53 +
44 54 // Either explicitly register the remote methods
45 55 var jsonRcpHttpClient = new Client(new ClientHttp('localhost', 8000));
46 56 jsonRpcHttpClient.register('loopback');
@@ -54,6 +64,12 @@ new Client(new ClientTcp('localhost', 8001), {}, function(jsonRpcTcpClient) {
54 64 console.log(val); // Prints 'foo'
55 65 });
56 66 });
  67 +
  68 +var jsonRpcExpressClient = new Client(new ClientHttp('localhost', 8002, { path: '/rpc' }));
  69 +jsonRpcExpressClient.register('loopback');
  70 +jsonRpcExpressClient.loopback('foo', function(err, val) {
  71 + console.log(val); // Prints 'foo'
  72 +});
57 73 ```
58 74
59 75 ### Constructor Function Parameters
@@ -164,6 +180,22 @@ The Server TCP Transport events are:
164 180
165 181 ``shutdown`` - This event is fired when the server is shutdown.
166 182
  183 +#### jsonrpc.transports.server.middleware
  184 +
  185 +``new jsonrpc.transports.server.middleware(config)``
  186 +
  187 +``config`` - The configuration settings. For the Connect/Express middleware transport, these are:
  188 +
  189 +``acao`` - The ``Access-Control-Allow-Origin`` header value, which defaults to ``*``.
  190 +
  191 +``server`` - A reference to the underlying server the middleware relies on. Used only for ``shutdown`` compatibility, if desired.
  192 +
  193 +The Server Middleware Transport events are:
  194 +
  195 +``message`` - This event is fired whenever a complete message is received, and the registered callbacks receive the JSON-RPC object as their only argument.
  196 +
  197 +``shutdown`` - This event is fired when the transport is shutdown.
  198 +
167 199 ## Defining JSON-RPC Server Methods
168 200
169 201 By default, JSON-RPC server methods are asynchronous, taking a callback function as the last argument. The callback function assumes the first argument it receives is an error and the second argument is a result, in the Node.js style.
3  lib/index.js
@@ -8,7 +8,8 @@ module.exports = {
8 8 },
9 9 server: {
10 10 http: require('./transports/server/http'),
11   - tcp: require('./transports/server/tcp')
  11 + tcp: require('./transports/server/tcp'),
  12 + middleware: require('./transports/server/middleware')
12 13 }
13 14 }
14 15 };
62 lib/transports/server/middleware.js
... ... @@ -0,0 +1,62 @@
  1 +var util = require('util');
  2 +var EventEmitter = require('events').EventEmitter;
  3 +
  4 +// Connect/Express middleware style JSON-RPC server transport
  5 +// Let's you have a hybrid Connect/Express server that also performs JSON-RPC
  6 +// on a particular path. Still done as an instance so you can conceivably have
  7 +// multiple JSON-RPC servers on a single Connect/Express server.
  8 +function MiddlewareTransport(config) {
  9 + // Initialize the EventEmitter for this object
  10 + EventEmitter.call(this);
  11 +
  12 + // Make sure the config object exists, the handler function exists,
  13 + // and the Access-Control-Allow-Origin header is properly set. Also
  14 + // allow the user to provide a reference to the underlying HTTP server
  15 + // so the ``shutdown`` method can work as expected, if desired.
  16 + config = config || {};
  17 + this.handler = function fakeHandler(json, next) { next({}); };
  18 + this.acao = config.acao ? config.acao : "*";
  19 + this.server = config.server || null;
  20 + this.middleware = this.requestHandler.bind(this);
  21 +
  22 + return this;
  23 +}
  24 +
  25 +// Attach the EventEmitter prototype to the prototype chain
  26 +util.inherits(MiddlewareTransport, EventEmitter);
  27 +
  28 +// The ``requestHandler`` method gets the request and response objects, and passes
  29 +// the request body and the bound responseHandler to the JSON-RPC hander function
  30 +MiddlewareTransport.prototype.requestHandler = function requestHandler(req, res) {
  31 + // All requests are assumed to be "Express-like" and have the bodyParser run
  32 + // before it. Express doesn't have a good way (that I'm aware of) to specify
  33 + // "run this middleware if it hasn't already been run".
  34 + this.emit('message', req.body);
  35 + this.handler(req.body, this.responseHandler.bind(this, res));
  36 +};
  37 +
  38 +// The ``responseHandler`` method takes the output object and sends it to the client
  39 +MiddlewareTransport.prototype.responseHandler = function responseHandler(res, retObj) {
  40 + var outString = JSON.stringify(retObj);
  41 + res.writeHead(retObj.error? 500:200, {
  42 + "Access-Control-Allow-Origin": this.acao,
  43 + "Content-Length": Buffer.byteLength(outString, 'utf8'),
  44 + "Content-Type": "application/json;charset=utf-8"
  45 + });
  46 + res.end(outString);
  47 +};
  48 +
  49 +// If the user defined the server in the config, the ``shutdown`` method will
  50 +// tell the server to shut down. Likely, when a JSON-RPC server is used as a
  51 +// middleware, this will not be done, but for API consistency's sake, it could.
  52 +MiddlewareTransport.prototype.shutdown = function shutdown(done) {
  53 + if(this.server) {
  54 + this.emit('shutdown');
  55 + this.server.close(done);
  56 + } else {
  57 + if(done instanceof Function) done();
  58 + }
  59 +};
  60 +
  61 +// Export the Server Middleware Transport
  62 +module.exports = MiddlewareTransport;
3  package.json
@@ -22,7 +22,8 @@
22 22 },
23 23 "devDependencies": {
24 24 "nodeunit": "*",
25   - "docco-husky": "*"
  25 + "docco-husky": "*",
  26 + "express": "*"
26 27 },
27 28 "scripts": {
28 29 "realpublish": "./prepublish.sh",
42 test/index.js
@@ -5,6 +5,9 @@ var ClientHttp = jsonrpc.transports.client.http;
5 5 var ClientTcp = jsonrpc.transports.client.tcp;
6 6 var ServerHttp = jsonrpc.transports.server.http;
7 7 var ServerTcp = jsonrpc.transports.server.tcp;
  8 +var ServerMiddleware = jsonrpc.transports.server.middleware;
  9 +var express = require('express');
  10 +var http = require('http');
8 11
9 12 exports.loopbackHttp = function(test) {
10 13 test.expect(1);
@@ -35,4 +38,43 @@ exports.failureTcp = function(test) {
35 38 });
36 39 });
37 40 });
  41 +};
  42 +
  43 +exports.loopbackExpress = function(test) {
  44 + test.expect(2);
  45 +
  46 + var app = express();
  47 + app.use(express.bodyParser());
  48 + app.get('/foo', function(req, res) {
  49 + res.end('bar');
  50 + });
  51 +
  52 + var server = new Server(new ServerMiddleware(), {
  53 + loopback: function(arg, callback) { callback(null, arg); }
  54 + });
  55 + app.use('/rpc', server.transport.middleware);
  56 +
  57 + //app.listen(55555); // Express 3.0 removed the ability to cleanly shutdown an express server
  58 + // The following is copied from the definition of app.listen()
  59 + var server = http.createServer(app);
  60 + server.listen(55555);
  61 +
  62 + var client = new Client(new ClientHttp('localhost', 55555, { path: '/rpc' }));
  63 + client.register('loopback');
  64 +
  65 + http.get({
  66 + port: 55555,
  67 + path: '/foo'
  68 + }, function(res) {
  69 + res.setEncoding('utf8');
  70 + var data = '';
  71 + res.on('data', function(chunk) { data += chunk; });
  72 + res.on('end', function() {
  73 + test.equal(data, 'bar', 'regular http requests work');
  74 + client.loopback('bar', function(err, result) {
  75 + test.equal(result, 'bar', 'JSON-RPC as a middleware works');
  76 + server.close(test.done.bind(test));
  77 + });
  78 + });
  79 + });
38 80 };

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.