<commit_before>
var logger = require('./logging').getLogger(__LOGGER__),
	React = require('react'),
	MobileDetect = require('mobile-detect'),
	RequestContext = require('./context/RequestContext'),
	RequestLocalStorage = require('./util/RequestLocalStorage'),
	RLS = RequestLocalStorage.getNamespace(),
	LABString = require('./util/LABString'),
	Q = require('q'),
	config = require('./config'),
	ExpressServerRequest = require("./ExpressServerRequest"),
	expressState = require('express-state'),
	cookieParser = require('cookie-parser'),
	PageUtil = require("./util/PageUtil"),
	TritonAgent = require('./TritonAgent'),
	StringEscapeUtil = require('./util/StringEscapeUtil'),
	{PAGE_CSS_NODE_ID, PAGE_LINK_NODE_ID, PAGE_CONTENT_NODE_ID} = require('./constants');


// TODO FIXME ??
// It *might* be worthwhile to get rid of all the closure-y things in render()
// https://developers.google.com/speed/articles/optimizing-javascript

// If an element hasn't rendered in this long it gets the axe.
var FAILSAFE_RENDER_TIMEOUT = 20e3;

// If a page's `handleRoute` fails to resolve this fast it gets the axe.
var FAILSAFE_ROUTER_TIMEOUT = 20e3;

// We'll use this for keeping track of request concurrency per worker.
var ACTIVE_REQUESTS = 0;

// Some non-content items that can live in the elements array.
var ELEMENT_PENDING         = -1;
var ELEMENT_ALREADY_WRITTEN = -2;

/**
 * renderMiddleware entrypoint. Called by express for every request.
 */
module.exports = function(server, routes) {

	expressState.extend(server);

	// parse cookies into req.cookies property
	server.use(cookieParser());

	// sets the namespace that data will be exposed into client-side
	// TODO: express-state doesn't do much for us until we're using a templating library
	server.set('state namespace', '__tritonState');

	server.use((req, res, next) => { RequestLocalStorage.startRequest(() => {
		ACTIVE_REQUESTS++;

		var start = RLS().startTime = new Date();
		var startHR = process.hrtime();

		logger.debug(`Incoming request for ${req.path}`);

		// Just to keep an eye out for leaks.
		logger.gauge("requestLocalStorageNamespaces", RequestLocalStorage.getCountNamespaces());

		// monkey-patch `res.write` so that we don't try to write to the stream if it's
		// already closed
		var origWrite = res.write;
		res.write = function () {
			if (!res.finished) {
				origWrite.apply(res, arguments);
			} else {
				logger.error("Attempted write after response finished", { path: req && req.path || "unknown", stack: logger.stack() });
			}
		};

		// TODO? pull this context building into its own middleware
		var context = new RequestContext.Builder()
				.setRoutes(routes)
				.setDefaultXhrHeadersFromRequest(req)
				.create({
					// TODO: context opts?
				});

		// Need this stuff in corvair for logging.
		context.setServerStash({ req, res, start, startHR });

		context.setMobileDetect(new MobileDetect(req.get('user-agent')));

		// setup navigation handler (TODO: should we have a 'once' version?)
		context.onNavigate( (err, page) => {

			if (!navigateDfd.promise.isPending()) {
				logger.error("Finished navigation after FAILSAFE_ROUTER_TIMEOUT", {
					page: context.page,
					path: req.path,
				});
				return;
			}

			// Success.
			navigateDfd.resolve();

			if (err) {
				logger.log("onNavigate received a non-2xx HTTP code", err);
				handleResponseComplete(req, res, context, start, page);
				if (err.status && err.status === 404) {
					next();
				} else if (err.status === 301 || err.status === 302 || err.status === 307) {
					res.redirect(err.status, err.redirectUrl);
				} else {
					next(err);
				}
				return;
			}

			renderPage(req, res, context, start, page);

		});


		var navigateDfd = Q.defer();

		setTimeout(navigateDfd.reject, FAILSAFE_ROUTER_TIMEOUT);

		// If we fail to navigate, we'll throw a 500 and move on.
		navigateDfd.promise.catch(() => {
			logger.error("Failed to navigate after FAILSAFE_ROUTER_TIMEOUT", {
				page: context.navigator.getCurrentRoute().name,
				path: req.path,
			});
			handleResponseComplete(req, res, context, start, context.page);
			next({status: 500});
		});

		context.navigate(new ExpressServerRequest(req));

	})});
}

module.exports.getActiveRequests = () => ACTIVE_REQUESTS;

function handleResponseComplete(req, res, context, start, page) {

	res.on('finish', RequestLocalStorage.bind(() => {

		// All intentional response completion should funnel through
		// this function.  If this value starts climbing gradually
		// that's an indication that we have some _unintentional_
		// response completion going on that we should deal with.
		ACTIVE_REQUESTS--;

		// Note that if the navigator couldn't even map the request to
		// a page, we won't be able to call middleware
		// `handleComplete()` here.
		//
		if (page) {
			logRequestStats(req, res, context, start, page);

			page.handleComplete();
		}
	}));
}

function renderPage(req, res, context, start, page) {

	var routeName = context.navigator.getCurrentRoute().name;

	logger.debug("Route Name: " + routeName);

	var renderTimer = logger.timer("renderFunction");

	res.status(page.getStatus()||200);

	// Each of these functions has the same signature and returns a<commit_msg>PLAT-527: Handle response objects that don't fire `finish`<commit_after>
var logger = require('./logging').getLogger(__LOGGER__),
	React = require('react'),
	MobileDetect = require('mobile-detect'),
	RequestContext = require('./context/RequestContext'),
	RequestLocalStorage = require('./util/RequestLocalStorage'),
	RLS = RequestLocalStorage.getNamespace(),
	LABString = require('./util/LABString'),
	Q = require('q'),
	config = require('./config'),
	ExpressServerRequest = require("./ExpressServerRequest"),
	expressState = require('express-state'),
	cookieParser = require('cookie-parser'),
	PageUtil = require("./util/PageUtil"),
	TritonAgent = require('./TritonAgent'),
	StringEscapeUtil = require('./util/StringEscapeUtil'),
	{PAGE_CSS_NODE_ID, PAGE_LINK_NODE_ID, PAGE_CONTENT_NODE_ID} = require('./constants');


// TODO FIXME ??
// It *might* be worthwhile to get rid of all the closure-y things in render()
// https://developers.google.com/speed/articles/optimizing-javascript

// If an element hasn't rendered in this long it gets the axe.
var FAILSAFE_RENDER_TIMEOUT = 20e3;

// If a page's `handleRoute` fails to resolve this fast it gets the axe.
var FAILSAFE_ROUTER_TIMEOUT = 20e3;

// We'll use this for keeping track of request concurrency per worker.
var ACTIVE_REQUESTS = 0;

// Some non-content items that can live in the elements array.
var ELEMENT_PENDING         = -1;
var ELEMENT_ALREADY_WRITTEN = -2;

/**
 * renderMiddleware entrypoint. Called by express for every request.
 */
module.exports = function(server, routes) {

	expressState.extend(server);

	// parse cookies into req.cookies property
	server.use(cookieParser());

	// sets the namespace that data will be exposed into client-side
	// TODO: express-state doesn't do much for us until we're using a templating library
	server.set('state namespace', '__tritonState');

	server.use((req, res, next) => { RequestLocalStorage.startRequest(() => {
		ACTIVE_REQUESTS++;

		var start = RLS().startTime = new Date();
		var startHR = process.hrtime();

		logger.debug(`Incoming request for ${req.path}`);

		// Just to keep an eye out for leaks.
		logger.gauge("requestLocalStorageNamespaces", RequestLocalStorage.getCountNamespaces());

		// monkey-patch `res.write` so that we don't try to write to the stream if it's
		// already closed
		var origWrite = res.write;
		res.write = function () {
			if (!res.finished) {
				origWrite.apply(res, arguments);
			} else {
				logger.error("Attempted write after response finished", { path: req && req.path || "unknown", stack: logger.stack() });
			}
		};

		// TODO? pull this context building into its own middleware
		var context = new RequestContext.Builder()
				.setRoutes(routes)
				.setDefaultXhrHeadersFromRequest(req)
				.create({
					// TODO: context opts?
				});

		// Need this stuff in corvair for logging.
		context.setServerStash({ req, res, start, startHR });

		context.setMobileDetect(new MobileDetect(req.get('user-agent')));

		// setup navigation handler (TODO: should we have a 'once' version?)
		context.onNavigate( (err, page) => {

			if (!navigateDfd.promise.isPending()) {
				logger.error("Finished navigation after FAILSAFE_ROUTER_TIMEOUT", {
					page: context.page,
					path: req.path,
				});
				return;
			}

			// Success.
			navigateDfd.resolve();

			if (err) {
				logger.log("onNavigate received a non-2xx HTTP code", err);
				handleResponseComplete(req, res, context, start, page);
				if (err.status && err.status === 404) {
					next();
				} else if (err.status === 301 || err.status === 302 || err.status === 307) {
					res.redirect(err.status, err.redirectUrl);
				} else {
					next(err);
				}
				return;
			}

			renderPage(req, res, context, start, page);

		});


		var navigateDfd = Q.defer();

		setTimeout(navigateDfd.reject, FAILSAFE_ROUTER_TIMEOUT);

		// If we fail to navigate, we'll throw a 500 and move on.
		navigateDfd.promise.catch(() => {
			logger.error("Failed to navigate after FAILSAFE_ROUTER_TIMEOUT", {
				page: context.navigator.getCurrentRoute().name,
				path: req.path,
			});
			handleResponseComplete(req, res, context, start, context.page);
			next({status: 500});
		});

		context.navigate(new ExpressServerRequest(req));

	})});
}

module.exports.getActiveRequests = () => ACTIVE_REQUESTS;

function handleResponseComplete(req, res, context, start, page) {

	var finish = RequestLocalStorage.bind(() => {

		// All intentional response completion should funnel through
		// this function.  If this value starts climbing gradually
		// that's an indication that we have some _unintentional_
		// response completion going on that we should deal with.
		ACTIVE_REQUESTS--;

		// Note that if the navigator couldn't even map the request to
		// a page, we won't be able to call middleware
		// `handleComplete()` here.
		//
		if (page) {
			logRequestStats(req, res, context, start, page);

			page.handleComplete();
		}
	});

	// The socket may have been destroyed under us if the connection was
	// closed by the client before we called `res.end()`.
	if (res.socket.destroyed) {
		setImmediate(finish);
	} else {
		res.on('finish', finish);
	}
}

function renderPage(req, res, context, start, page) {

	var routeName = context.navigator.getCurrentRoute().name;

	logger.debug("Route Name: " + routeName);

	var renderTimer = logger.timer("renderFunction");

	res.status(page.getStatus()||200);

	// Each of these functions has the same signature and returns a
{'fraction_non_alphanumeric': 0.13344562979599475, 'fraction_numerical': 0.006082725060827251, 'mean_word_length': 3.8643604915794265, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 2, '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': '7357061', 'n_tokens_mistral': 3646, 'n_tokens_neox': 3207, 'n_words': 941}