summaryrefslogtreecommitdiff
path: root/lib/nodejs/lib/thrift/web_server.js
diff options
context:
space:
mode:
authorRoger Meier <roger@apache.org>2014-03-12 09:38:42 +0100
committerRoger Meier <roger@apache.org>2014-03-12 09:38:42 +0100
commit52744eed7b8cc8b758825d2ba188933f907e07df (patch)
treed217770dfbb50dfb62781f251d005da9df30742b /lib/nodejs/lib/thrift/web_server.js
parent1f78987c49b2a44dd3b000c4e96d96244cf1cb9a (diff)
downloadthrift-52744eed7b8cc8b758825d2ba188933f907e07df.tar.gz
THRIFT-2397 Add CORS and CSP support for JavaScript and Node.js libraries
Patch: Randy Abernethy
Diffstat (limited to 'lib/nodejs/lib/thrift/web_server.js')
-rw-r--r--lib/nodejs/lib/thrift/web_server.js130
1 files changed, 104 insertions, 26 deletions
diff --git a/lib/nodejs/lib/thrift/web_server.js b/lib/nodejs/lib/thrift/web_server.js
index c888a8079..a04038062 100644
--- a/lib/nodejs/lib/thrift/web_server.js
+++ b/lib/nodejs/lib/thrift/web_server.js
@@ -31,18 +31,14 @@ var TBinaryProtocol = require('./protocol').TBinaryProtocol;
// WSFrame constructor and prototype
/////////////////////////////////////////////////////////////////////
-/** Apache Thrift RPC Web Socket Frame Layout
- * Conforming to RFC 6455 circa 12/2011
+/** Apache Thrift RPC Web Socket Transport
+ * Frame layout conforming to RFC 6455 circa 12/2011
*
* Theoretical frame size limit is 4GB*4GB, however the Node Buffer
* limit is 1GB as of v0.10. The frame length encoding is also
* configured for a max of 4GB presently and needs to be adjusted
* if Node/Browsers become capabile of > 4GB frames.
*
- * data - buffer to send (data.length is length to transmit)
- * mask - Must be null if sending to client or mask-key if sending to server
- * binEncoding - true for binary, false for text (the default)
- *
* - FIN is always 1, ATRPC messages are sent in a single frame
* - RSV1/2/3 are always 0
* - Opcode is 1(TEXT) for TJSONProtocol and 2(BIN) for TBinaryProtocol
@@ -116,10 +112,24 @@ var wsFrame = {
return frame;
},
- /** Decodes a WebSocket frame
+ /**
+ * @class
+ * @name WSDecodeResult
+ * @property {Buffer} data - The decoded data for the first ATRPC message
+ * @property {Buffer} mask - The frame mask
+ * @property {Boolean} binEncoding - True if binary (TBinaryProtocol),
+ * False if text (TJSONProtocol)
+ * @property {Buffer} nextFrame - Multiple ATRPC messages may be sent in a
+ * single WebSocket frame, this Buffer contains
+ * any bytes remaining to be decoded
+ */
+
+ /** Decodes a WebSocket frame
*
* @param {Buffer} frame - The raw inbound frame
* @returns {WSDecodeResult} - The decoded payload
+ *
+ * @see {@link WSDecodeResult}
*/
decode: function(frame) {
var result = {
@@ -216,14 +226,17 @@ var wsFrame = {
/**
* @class
- * @name ThriftWebServerOptions
- * @property {string} staticFilePath - Path to serve static files from, if
- * absent or "" static file service is disabled
- * @property {TLSOptions} tlsOptions - Node.js TLS options
- * (see: nodejs.org/api/tls.html), if not present or null regular http
- * is used, at least a key and a cert must be defined to use SSL/TLS
- * @property {object} services - An object hash mapping service URIs to
- * ThriftServiceOptions objects
+ * @name WebServerOptions
+ * @property {string} staticFilePath - Path to serve static files from, if absent or ""
+ * static file service is disabled
+ * @property {object} tlsOptions - Node.js TLS options (see: nodejs.org/api/tls.html),
+ * if not present or null regular http is used,
+ * at least a key and a cert must be defined to use SSL/TLS
+ * @property {object} services - An object hash mapping service URI strings
+ * to ThriftServiceOptions objects
+ * @property {object} headers - An object hash mapping header strings to header value,
+ * strings, these headers are transmitted in response to
+ * static file GET operations.
* @see {@link ThriftServiceOptions}
*/
@@ -231,19 +244,23 @@ var wsFrame = {
* @class
* @name ThriftServiceOptions
* @property {object} transport - The layered transport to use (defaults
- * to none).
+ * to TBufferedTransport).
* @property {object} protocol - The Thrift Protocol to use (defaults to
* TBinaryProtocol).
- * @property {object} cls - The Thrift Service class generated by the IDL
- * Compiler for the service.
+ * @property {object} processor - The Thrift Service class generated by the IDL
+ * Compiler for the service (the "cls" key can also
+ * be used for this attribute).
* @property {object} handler - The handler methods for the Thrift Service.
+ * @property {array} corsOrigins - Array of CORS origin strings to permit requests from.
*/
/**
* Creates a Thrift server which can serve static files and/or one or
* more Thrift Services.
- * @param {ThriftWebServerOptions} options - The server configuration.
+ * @param {WebServerOptions} options - The server configuration.
* @returns {object} - The Thrift server.
+ *
+ * @see {@link WebServerOptions}
*/
exports.createWebServer = function(options) {
var baseDir = options.staticFilePath;
@@ -263,25 +280,76 @@ exports.createWebServer = function(options) {
//Setup all of the services
var services = options.services;
- for (svc in services) {
- var svcObj = services[svc];
- var processor = svcObj.cls.Processor || svcObj.cls;
+ for (uri in services) {
+ var svcObj = services[uri];
+ var processor = (svcObj.processor) ? (svcObj.processor.Processor || svcObj.processor) :
+ (svcObj.cls.Processor || svcObj.cls);
svcObj.processor = new processor(svcObj.handler);
svcObj.transport = svcObj.transport ? svcObj.transport : TBufferedTransport;
svcObj.protocol = svcObj.protocol ? svcObj.protocol : TBinaryProtocol;
}
+
+ //Verify CORS requirements
+ function VerifyCORSAndSetHeaders(request, response, svc) {
+ if (request.headers.origin && svc.corsOrigins) {
+ if (svcObj.corsOrigins["*"] || svcObj.corsOrigins[request.headers.origin]) {
+ //Sucess, origin allowed
+ response.setHeader("access-control-allow-origin", request.headers.origin);
+ response.setHeader("access-control-allow-methods", "POST, OPTIONS");
+ response.setHeader("access-control-allow-headers", "content-type, accept");
+ response.setHeader("access-control-max-age", "60");
+ return true;
+ } else {
+ //Failure, origin denied
+ return false;
+ }
+ }
+ //Success, CORS is not in use
+ return true;
+ }
+
+ //Handle OPTIONS method (CORS support)
+ ///////////////////////////////////////////////////
+ function processOptions(request, response) {
+ var uri = url.parse(request.url).pathname;
+ var svc = services[uri];
+ if (!svc) {
+ //Unsupported service
+ response.writeHead("403", "No Apache Thrift Service at " + uri, {});
+ response.end();
+ return;
+ }
+
+ //Verify CORS requirements
+ if (VerifyCORSAndSetHeaders(request, response, svc)) {
+ response.writeHead("204", "No Content", {"content-length": 0});
+ } else {
+ response.writeHead("403", "Origin " + request.headers.origin + " not allowed", {});
+ }
+ response.end();
+ }
+
//Handle POST methods (TXHRTransport)
+ ///////////////////////////////////////////////////
function processPost(request, response) {
+ //Lookup service
var uri = url.parse(request.url).pathname;
var svc = services[uri];
if (!svc) {
- //TODO: add support for non Thrift posts
- response.writeHead(500);
+ response.writeHead("403", "No Apache Thrift Service at " + uri, {});
response.end();
return;
}
+ //Verify CORS requirements
+ if (!VerifyCORSAndSetHeaders(request, response, svc)) {
+ response.writeHead("403", "Origin " + request.headers.origin + " not allowed", {});
+ response.end();
+ return;
+ }
+
+ //Process XHR payload
request.on('data', svc.transport.receiver(function(transportWithData) {
var input = new svc.protocol(transportWithData);
var output = new svc.protocol(new svc.transport(undefined, function(buf) {
@@ -311,6 +379,7 @@ exports.createWebServer = function(options) {
}
//Handle GET methods (Static Page Server)
+ ///////////////////////////////////////////////////
function processGet(request, response) {
//Undefined or empty base directory means do not serve static files
if (!baseDir || "" == baseDir) {
@@ -318,7 +387,7 @@ exports.createWebServer = function(options) {
response.end();
return;
}
- //Locate the file requested
+ //Locate the file requested and send it
var uri = url.parse(request.url).pathname;
var filename = path.join(baseDir, uri);
fs.exists(filename, function(exists) {
@@ -343,6 +412,9 @@ exports.createWebServer = function(options) {
if (contentType) {
headers["Content-Type"] = contentType;
}
+ for (k in options.headers) {
+ headers[k] = options.headers[k];
+ }
response.writeHead(200, headers);
response.write(file, "binary");
response.end();
@@ -351,6 +423,7 @@ exports.createWebServer = function(options) {
}
//Handle WebSocket calls (TWebSocketTransport)
+ ///////////////////////////////////////////////////
function processWS(data, socket) {
var svc = services[Object.keys(services)[0]];
//TODO: add multiservice support (maybe multiplexing is the answer for both XHR and WS?)
@@ -389,12 +462,17 @@ exports.createWebServer = function(options) {
server = http.createServer();
}
- //Wire up listeners for request(GET[files]), request(POST[XHR]), upgrade(WebSocket)
+ //Wire up listeners for upgrade(to WebSocket) & request methods for:
+ // - GET static files,
+ // - POST XHR Thrift services
+ // - OPTIONS CORS requests
server.on('request', function(request, response) {
if (request.method === 'POST') {
processPost(request, response);
} else if (request.method === 'GET') {
processGet(request, response);
+ } else if (request.method === 'OPTIONS') {
+ processOptions(request, response);
} else {
response.writeHead(500);
response.end();