<commit_before>/*
 * ca-http.js: HTTP server abstraction for Cloud Analytics
 */

var mod_fs = require('fs');
var mod_http = require('http');
var mod_url = require('url');
var mod_querystring = require('querystring');
var mod_sys = require('sys');

var mod_connect = require('connect');
var mod_ca = require('./ca-common');
var ASSERT = require('assert');
var HTTP = require('./http-constants');

var ca_http_log_requests = true;
var ca_http_maxentity = 4096;		/* maximum request size (bytes) */
var ca_http_allowed_methods = [ 'POST', 'GET', 'DELETE', 'PUT' ].join(', ');

/*
 * Tell "connect" not to vomit exception stacktraces at the browser.
 */
if (process.connectEnv && process.connectEnv['name'])
	process.connectEnv['name'] = 'production';

function caHttpContentType(request)
{
	var content_type = request.headers['content-type'];
	var semi;

	/*
	 * See RFC2616 section 7.2.1.
	 */
	if (!content_type)
		return ('application/octet-stream');

	semi = content_type.indexOf(';');
	if (semi == -1)
		return (content_type);

	return (content_type.substring(0, semi));
}

function caHttpRouter(router)
{
	return (function (server) {
		/*
		 * We have to process OPTIONS because our response will have the
		 * appropriate Access-Control headers that will enable the
		 * browser to make a subsequent request (e.g., DELETE).  We add
		 * our handler after our caller's handlers to make sure we don't
		 * stomp on them.  With Chrome and Safari we can actually return
		 * "Bad method" and they'll still work as long as we supply the
		 * appropriate headers, but Firefox requires a successful
		 * return.  So we always return OK for OPTIONS even if the path
		 * is invalid.
		 */
		router(server);

		/* JSSTYLED */
		server.options(/.*/, function (request, response) {
			response.send(HTTP.OK);
		});
	});
}

function caHttpCatchall(request, response)
{
	response.send(HTTP.ENOTFOUND);
}

/*
 * Construct a basic server.  The 'conf' argument must specify the following
 * members:
 *
 *	log	logger for recording debug data
 *
 *	router	Connect-style router function describing how to handle URIs
 *
 * and one of the following members:
 *
 *	port		port number for incoming connections
 *
 *	port_base	same as port, but implies that the implementation should
 *			attempt to use consecutively numbered ports starting
 *			from this value until a free port is found
 *
 * and may specify any of the following members:
 *
 *	log_requests	logger for all requests
 *
 */
function caHttpServer(conf)
{
	ASSERT.ok(conf.log !== undefined);
	ASSERT.ok(conf.router !== undefined);
	ASSERT.ok(conf.port !== undefined || conf.port_base !== undefined);

	this.chs_log = conf.log;
	this.chs_port = conf.port || conf.port_base;
	this.chs_retry = conf.port_base !== undefined;
	this.chs_router = conf.router;
	this.chs_reqlog = conf.log_requests;
	this.chs_nrequests = 0;
	this.chs_nrequests_bycode = {};
	this.chs_pending = {};
	this.chs_npending = 0;
}

exports.caHttpServer = caHttpServer;

caHttpServer.prototype.port = function ()
{
	return (this.chs_port);
};

caHttpServer.prototype.start = function (callback)
{
	var srv = this;
	var init = function () { srv.initRequest.apply(srv, arguments); };
	var decode = function () { srv.decodeRequest.apply(srv, arguments); };
	var dotry;

	ASSERT.ok(this.chs_server === undefined,
	    'server object cannot be reused');

	dotry = function () {
		srv.chs_server = mod_connect.createServer(init, decode,
		    mod_connect.router(caHttpRouter(srv.chs_router)),
		    caHttpCatchall);
		srv.chs_server.listen(srv.chs_port, callback);
		srv.chs_server.showVersion = false;
		srv.chs_server.on('error', function (err) {
			srv.chs_server.removeListener('error',
			    arguments.callee);

			if (!srv.chs_retry || !err.errno ||
			    err.errno != 'EADDRINUSE')
				throw (err);

			srv.chs_port++;
			dotry();
		});
	};

	dotry();
};

/*
 * Although the server can be closed, it's not supported to start it back up.
 */
caHttpServer.prototype.stop = function (callback)
{
	this.chs_server.on('close', callback);
	this.chs_server.close();
};

/*
 * This first stage of request processing sets up the 'send' method of the
 * response for use by subsequent stages, then reads in the request and triggers
 * the next stage.
 */
caHttpServer.prototype.initRequest = function (request, response, next)
{
	var server = this;
	var orig_end, orig_write, orig_head;

	/*
	 * We add a 'send' method to the response object which provides a simple
	 * interface for consumers to send most types of responses.  Consumers
	 * need only specify a response code, but can also specify a string
	 * (e.g. an error message) or an object to be JSON-encoded.  We assert
	 * that the method doesn't exist to future-proof ourselves against
	 * future versions of node using this method name in the future.
	 */
	ASSERT.ok(!response.send);
	response.send = function (code, body, headers) {
		var response_data, logtext;

		if (!headers)
			headers = {};

		/*
		 * The Access-Control-Allow-* headers allow strict clients (like
		 * Google Chrome) to know that this server really does allow
		 * itself to be invoked from web pages it didn't serve.
		 */
		headers['Access-Control-Allow-Origin'] = '*';
		headers['Access-Control-Allow-Methods'] =
		    ca_http_allowed_methods;

		if (!body) {
			response_data = '';
			logtext = '';
		} else if (typeof (body) == typeof ('')) {
			response_data = body;
			logtext = body;
		} else {
			headers['Content-Type'] = 'application/json';
			logtext = JSON.stringify(body);
			response_data = logtext + '\n';
		}

		if (code >= HTTP.EBADREQUEST && request.method != 'OPTIONS') {
			server.chs_log.warn('HTTP request failed with %d: ' +
			    '%s %s: %s', code, request.method, request.url,
			    logtext);
		}

		response.writeHead(code, headers);
		response.end(response_data);
	};

	response.sendError = function (exn, code) {
		var status, errcode;

		if (arguments.length > 1)
			status = code;<commit_msg>INTRO-670 ca should allow content-type for CORS<commit_after>/*
 * ca-http.js: HTTP server abstraction for Cloud Analytics
 */

var mod_fs = require('fs');
var mod_http = require('http');
var mod_url = require('url');
var mod_querystring = require('querystring');
var mod_sys = require('sys');

var mod_connect = require('connect');
var mod_ca = require('./ca-common');
var ASSERT = require('assert');
var HTTP = require('./http-constants');

var ca_http_log_requests = true;
var ca_http_maxentity = 4096;		/* maximum request size (bytes) */
var ca_http_allowed_methods = [ 'POST', 'GET', 'DELETE', 'PUT' ].join(', ');

/*
 * Tell "connect" not to vomit exception stacktraces at the browser.
 */
if (process.connectEnv && process.connectEnv['name'])
	process.connectEnv['name'] = 'production';

function caHttpContentType(request)
{
	var content_type = request.headers['content-type'];
	var semi;

	/*
	 * See RFC2616 section 7.2.1.
	 */
	if (!content_type)
		return ('application/octet-stream');

	semi = content_type.indexOf(';');
	if (semi == -1)
		return (content_type);

	return (content_type.substring(0, semi));
}

function caHttpRouter(router)
{
	return (function (server) {
		/*
		 * We have to process OPTIONS because our response will have the
		 * appropriate Access-Control headers that will enable the
		 * browser to make a subsequent request (e.g., DELETE).  We add
		 * our handler after our caller's handlers to make sure we don't
		 * stomp on them.  With Chrome and Safari we can actually return
		 * "Bad method" and they'll still work as long as we supply the
		 * appropriate headers, but Firefox requires a successful
		 * return.  So we always return OK for OPTIONS even if the path
		 * is invalid.
		 */
		router(server);

		/* JSSTYLED */
		server.options(/.*/, function (request, response) {
			response.send(HTTP.OK);
		});
	});
}

function caHttpCatchall(request, response)
{
	response.send(HTTP.ENOTFOUND);
}

/*
 * Construct a basic server.  The 'conf' argument must specify the following
 * members:
 *
 *	log	logger for recording debug data
 *
 *	router	Connect-style router function describing how to handle URIs
 *
 * and one of the following members:
 *
 *	port		port number for incoming connections
 *
 *	port_base	same as port, but implies that the implementation should
 *			attempt to use consecutively numbered ports starting
 *			from this value until a free port is found
 *
 * and may specify any of the following members:
 *
 *	log_requests	logger for all requests
 *
 */
function caHttpServer(conf)
{
	ASSERT.ok(conf.log !== undefined);
	ASSERT.ok(conf.router !== undefined);
	ASSERT.ok(conf.port !== undefined || conf.port_base !== undefined);

	this.chs_log = conf.log;
	this.chs_port = conf.port || conf.port_base;
	this.chs_retry = conf.port_base !== undefined;
	this.chs_router = conf.router;
	this.chs_reqlog = conf.log_requests;
	this.chs_nrequests = 0;
	this.chs_nrequests_bycode = {};
	this.chs_pending = {};
	this.chs_npending = 0;
}

exports.caHttpServer = caHttpServer;

caHttpServer.prototype.port = function ()
{
	return (this.chs_port);
};

caHttpServer.prototype.start = function (callback)
{
	var srv = this;
	var init = function () { srv.initRequest.apply(srv, arguments); };
	var decode = function () { srv.decodeRequest.apply(srv, arguments); };
	var dotry;

	ASSERT.ok(this.chs_server === undefined,
	    'server object cannot be reused');

	dotry = function () {
		srv.chs_server = mod_connect.createServer(init, decode,
		    mod_connect.router(caHttpRouter(srv.chs_router)),
		    caHttpCatchall);
		srv.chs_server.listen(srv.chs_port, callback);
		srv.chs_server.showVersion = false;
		srv.chs_server.on('error', function (err) {
			srv.chs_server.removeListener('error',
			    arguments.callee);

			if (!srv.chs_retry || !err.errno ||
			    err.errno != 'EADDRINUSE')
				throw (err);

			srv.chs_port++;
			dotry();
		});
	};

	dotry();
};

/*
 * Although the server can be closed, it's not supported to start it back up.
 */
caHttpServer.prototype.stop = function (callback)
{
	this.chs_server.on('close', callback);
	this.chs_server.close();
};

/*
 * This first stage of request processing sets up the 'send' method of the
 * response for use by subsequent stages, then reads in the request and triggers
 * the next stage.
 */
caHttpServer.prototype.initRequest = function (request, response, next)
{
	var server = this;
	var orig_end, orig_write, orig_head;

	/*
	 * We add a 'send' method to the response object which provides a simple
	 * interface for consumers to send most types of responses.  Consumers
	 * need only specify a response code, but can also specify a string
	 * (e.g. an error message) or an object to be JSON-encoded.  We assert
	 * that the method doesn't exist to future-proof ourselves against
	 * future versions of node using this method name in the future.
	 */
	ASSERT.ok(!response.send);
	response.send = function (code, body, headers) {
		var response_data, logtext;

		if (!headers)
			headers = {};

		/*
		 * The Access-Control-Allow-* headers allow strict clients (like
		 * Google Chrome) to know that this server really does allow
		 * itself to be invoked from web pages it didn't serve.
		 */
		headers['Access-Control-Allow-Headers'] = 'Content-type';
		headers['Access-Control-Allow-Origin'] = '*';
		headers['Access-Control-Allow-Methods'] =
		    ca_http_allowed_methods;

		if (!body) {
			response_data = '';
			logtext = '';
		} else if (typeof (body) == typeof ('')) {
			response_data = body;
			logtext = body;
		} else {
			headers['Content-Type'] = 'application/json';
			logtext = JSON.stringify(body);
			response_data = logtext + '\n';
		}

		if (code >= HTTP.EBADREQUEST && request.method != 'OPTIONS') {
			server.chs_log.warn('HTTP request failed with %d: ' +
			    '%s %s: %s', code, request.method, request.url,
			    logtext);
		}

		response.writeHead(code, headers);
		response.end(response_data);
	};

	response.sendError = function (exn, code) {
		var status, errcode;

		if (arguments.length > 1)
			status = code;
{'fraction_non_alphanumeric': 0.12668329177057355, 'fraction_numerical': 0.0029093931837073984, 'mean_word_length': 3.7856006364359587, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6092978', 'n_tokens_mistral': 4052, 'n_tokens_neox': 3715, 'n_words': 1163}