From 4df24f457aceb77ab7660222253edb4d15243a61 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Fri, 21 Mar 2014 19:47:23 -0500 Subject: [PATCH] Replaces pythons web server --- make.js | 7 +- test/webserver.js | 250 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 test/webserver.js diff --git a/make.js b/make.js index 418947e94..da7624ef2 100644 --- a/make.js +++ b/make.js @@ -1248,8 +1248,10 @@ target.server = function() { echo(); echo('### Starting local server'); - cd('test'); - exec(PYTHON_BIN + ' -u test.py --port=8888 --noDownload', {async: true}); + var WebServer = require('./test/webserver.js').WebServer; + var server = new WebServer(); + server.port = 8888; + server.start(); }; // @@ -1267,6 +1269,7 @@ target.lint = function() { 'web/', 'test/driver.js', 'test/reporter.js', + 'test/webserver.js', 'test/unit/', 'extensions/firefox/', 'extensions/chromium/' diff --git a/test/webserver.js b/test/webserver.js new file mode 100644 index 000000000..670206da1 --- /dev/null +++ b/test/webserver.js @@ -0,0 +1,250 @@ +/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* + * Copyright 2014 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*jslint node: true */ + +'use strict'; + +var http = require('http'); +var path = require('path'); +var fs = require('fs'); + +var mimeTypes = { + '.css': 'text/css', + '.html': 'text/html', + '.js': 'application/javascript', + '.json': 'application/json', + '.svg': 'image/svg+xml', + '.pdf': 'application/pdf', + '.xhtml': 'application/xhtml+xml', + '.gif': 'image/gif', + '.ico': 'image/x-icon', + '.png': 'image/png', + '.log': 'text/plain', + '.bcmap': 'application/octet-stream', + '.properties': 'text/plain' +}; + +var defaultMimeType = 'application/octet-stream'; + +function WebServer() { + this.root = '.'; + this.host = 'localhost'; + this.port = 8000; + this.server = null; + this.verbose = false; + this.hooks = { + 'GET': [], + 'POST': [] + }; +} +WebServer.prototype = { + start: function (callback) { + this.server = http.createServer(this._handler.bind(this)); + this.server.listen(this.port, this.host, callback); + console.log( + 'Server running at http://' + this.host + ':' + this.port + '/'); + }, + stop: function (callback) { + this.server.close(callback); + this.server = null; + }, + _handler: function (req, res) { + var url = req.url; + var urlParts = /([^?]*)((?:\?(.*))?)/.exec(url); + var pathPart = decodeURI(urlParts[1]), queryPart = urlParts[3]; + var verbose = this.verbose; + + var methodHooks = this.hooks[req.method]; + if (!methodHooks) { + res.writeHead(405); + res.end('Unsupported request method', 'utf8'); + return; + } + var handled = methodHooks.some(function (hook) { + return hook(req, res); + }); + if (handled) { + return; + } + + if (pathPart === '/favicon.ico') { + fs.realpath(path.join(this.root, 'test/resources/favicon.ico'), + checkFile); + return; + } + + var filePath; + fs.realpath(path.join(this.root, pathPart), checkFile); + + function checkFile(err, file) { + if (err) { + res.writeHead(404); + res.end(); + if (verbose) { + console.error(url + ': not found'); + } + return; + } + filePath = file; + fs.stat(filePath, statFile); + } + + var fileSize; + + function statFile(err, stats) { + if (err) { + res.writeHead(500); + res.end(); + return; + } + + fileSize = stats.size; + var isDir = stats.isDirectory(); + if (isDir && !/\/$/.test(pathPart)) { + res.setHeader('Location', pathPart + '/' + urlParts[2]); + res.writeHead(301); + res.end('Redirected', 'utf8'); + return; + } + if (isDir) { + serveDirectoryIndex(filePath); + return; + } + + var range = req.headers['range']; + if (range) { + var rangesMatches = /^bytes=(\d+)\-(\d+)?/.exec(range); + if (!rangesMatches) { + res.writeHead(501); + res.end('Bad range', 'utf8'); + if (verbose) { + console.error(url + ': bad range: "' + range + '"'); + } + return; + } + var start = +rangesMatches[1]; + var end = +rangesMatches[2]; + if (verbose) { + console.log(url + ': range ' + start + ' - ' + end); + } + serveRequestedFileRange(filePath, + start, + isNaN(end) ? fileSize : (end + 1)); + return; + } + if (verbose) { + console.log(url); + } + serveRequestedFile(filePath); + } + + function serveDirectoryIndex(dir) { + res.setHeader('Content-Type', 'text/html'); + res.writeHead(200); + + var content = ''; + if (queryPart === 'frame') { + res.end('' + + '', 'utf8'); + return; + } + var all = queryPart === 'all'; + fs.readdir(dir, function (err, files) { + if (err) { + res.end(); + return; + } + res.write('

PDFs of ' + pathPart + '

\n'); + if (pathPart !== '/') { + res.write('..
\n'); + } + files.forEach(function (file) { + var stat = fs.statSync(path.join(dir, file)); + var item = pathPart + file; + if (stat.isDirectory()) { + res.write('' + + file + '
\n'); + return; + } + var ext = path.extname(file).toLowerCase(); + if (ext === '.pdf') { + res.write('' + + file + '
\n'); + } else if (all) { + res.write('' + + file + '
\n'); + } + }); + if (files.length === 0) { + res.write('

no files found

\n'); + } + if (!all && queryPart !== 'side') { + res.write('

(only PDF files are shown, ' + + 'show all)

\n'); + } + res.end(''); + }); + } + + function serveRequestedFile(filePath) { + var stream = fs.createReadStream(filePath, {flags: 'rs'}); + + stream.on('error', function (error) { + res.writeHead(500); + res.end(); + }); + + var ext = path.extname(filePath).toLowerCase(); + var contentType = mimeTypes[ext] || defaultMimeType; + + res.setHeader('Accept-Ranges', 'bytes'); + res.setHeader('Content-Type', contentType); + res.setHeader('Content-Length', fileSize); + res.writeHead(200); + + stream.pipe(res); + } + + function serveRequestedFileRange(filePath, start, end) { + var stream = fs.createReadStream(filePath, { + flags: 'rs', start: start, end: end - 1}); + + stream.on('error', function (error) { + res.writeHead(500); + res.end(); + }); + + var ext = path.extname(filePath).toLowerCase(); + var contentType = mimeTypes[ext] || defaultMimeType; + + res.setHeader('Accept-Ranges', 'bytes'); + res.setHeader('Content-Type', contentType); + res.setHeader('Content-Length', (end - start)); + res.setHeader('Content-Range', + 'bytes ' + start + '-' + (end - 1) + '/' + fileSize); + res.writeHead(206); + + stream.pipe(res); + } + + } +}; + +exports.WebServer = WebServer;