Merge branch 'develop' of github.com:Pita/etherpad-lite into feature/frontend-tests

This commit is contained in:
johnyma22 2012-10-04 18:55:57 +01:00
commit c74aed986e
17 changed files with 331 additions and 168 deletions

View file

@ -436,15 +436,22 @@ function handleUserInfoUpdate(client, message)
}
/**
* Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations
* This Method is nearly 90% copied out of the Etherpad Source Code. So I can't tell you what happens here exactly
* Look at https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges()
* Handles a USER_CHANGES message, where the client submits its local
* edits as a changeset.
*
* This handler's job is to update the incoming changeset so that it applies
* to the latest revision, then add it to the pad, broadcast the changes
* to all other clients, and send a confirmation to the submitting client.
*
* This function is based on a similar one in the original Etherpad.
* See https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges()
*
* @param client the client that send this message
* @param message the message from the client
*/
function handleUserChanges(client, message)
{
//check if all ok
// Make sure all required fields are present
if(message.data.baseRev == null)
{
messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!");
@ -487,22 +494,23 @@ function handleUserChanges(client, message)
{
//ex. _checkChangesetAndPool
//Copied from Etherpad, don't know what it does exactly
try
{
//this looks like a changeset check, it throws errors sometimes
// Verify that the changeset has valid syntax and is in canonical form
Changeset.checkRep(changeset);
// Verify that the attribute indexes used in the changeset are all
// defined in the accompanying attribute pool.
Changeset.eachAttribNumber(changeset, function(n) {
if (! wireApool.getAttrib(n)) {
throw "Attribute pool is missing attribute "+n+" for changeset "+changeset;
}
});
}
//there is an error in this changeset, so just refuse it
catch(e)
{
console.warn("Can't apply USER_CHANGES "+changeset+", cause it faild checkRep");
// There is an error in this changeset, so just refuse it
console.warn("Can't apply USER_CHANGES "+changeset+", because it failed checkRep");
client.json.send({disconnect:"badChangeset"});
return;
}
@ -515,7 +523,10 @@ function handleUserChanges(client, message)
//ex. applyUserChanges
apool = pad.pool;
r = baseRev;
// The client's changeset might not be based on the latest revision,
// since other clients are sending changes at the same time.
// Update the changeset so that it can be applied to the latest revision.
//https://github.com/caolan/async#whilst
async.whilst(
function() { return r < pad.getHeadRevisionNumber(); },
@ -526,8 +537,13 @@ function handleUserChanges(client, message)
pad.getRevisionChangeset(r, function(err, c)
{
if(ERR(err, callback)) return;
// At this point, both "c" (from the pad) and "changeset" (from the
// client) are relative to revision r - 1. The follow function
// rebases "changeset" so that it is relative to revision r
// and can be applied after "c".
changeset = Changeset.follow(c, changeset, false, apool);
if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep
async.nextTick(callback);
} else {
@ -558,7 +574,8 @@ function handleUserChanges(client, message)
if (correctionChangeset) {
pad.appendRevision(correctionChangeset);
}
// Make sure the pad always ends with an empty line.
if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) {
var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n");
pad.appendRevision(nlChangeset);
@ -865,6 +882,13 @@ function handleClientReady(client, message)
},
function(callback)
{
//Check that the client is still here. It might have disconnected between callbacks.
if(sessioninfos[client.id] === undefined)
{
callback();
return;
}
//Check if this author is already on the pad, if yes, kick the other sessions!
if(pad2sessions[padIds.padId])
{
@ -879,10 +903,9 @@ function handleClientReady(client, message)
}
//Save in sessioninfos that this session belonges to this pad
var sessionId=String(client.id);
sessioninfos[sessionId].padId = padIds.padId;
sessioninfos[sessionId].readOnlyPadId = padIds.readOnlyPadId;
sessioninfos[sessionId].readonly = padIds.readonly;
sessioninfos[client.id].padId = padIds.padId;
sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId;
sessioninfos[client.id].readonly = padIds.readonly;
//check if there is already a pad2sessions entry, if not, create one
if(!pad2sessions[padIds.padId])
@ -891,7 +914,7 @@ function handleClientReady(client, message)
}
//Saves in pad2sessions that this session belongs to this pad
pad2sessions[padIds.padId].push(sessionId);
pad2sessions[padIds.padId].push(client.id);
//prepare all values for the wire
var atext = Changeset.cloneAText(pad.atext);
@ -956,26 +979,22 @@ function handleClientReady(client, message)
clientVars.userName = authorName;
}
if(sessioninfos[client.id] !== undefined)
//If this is a reconnect, we don't have to send the client the ClientVars again
if(message.reconnect == true)
{
//This is a reconnect, so we don't have to send the client the ClientVars again
if(message.reconnect == true)
{
//Save the revision in sessioninfos, we take the revision from the info the client send to us
sessioninfos[client.id].rev = message.client_rev;
}
//This is a normal first connect
else
{
//Send the clientVars to the Client
client.json.send({type: "CLIENT_VARS", data: clientVars});
//Save the revision in sessioninfos
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
}
//Save the revision and the author id in sessioninfos
sessioninfos[client.id].author = author;
//Save the revision in sessioninfos, we take the revision from the info the client send to us
sessioninfos[client.id].rev = message.client_rev;
}
//This is a normal first connect
else
{
//Send the clientVars to the Client
client.json.send({type: "CLIENT_VARS", data: clientVars});
//Save the current revision in sessioninfos, should be the same as in clientVars
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
}
sessioninfos[client.id].author = author;
//prepare the notification for the other users on the pad, that this user joined
var messageToTheOtherUsers = {

View file

@ -1,4 +1,5 @@
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var http = require('http');
var express = require('express');
var settings = require('../utils/Settings');
var fs = require('fs');
@ -42,22 +43,24 @@ exports.createServer = function () {
}
exports.restartServer = function () {
if (server) {
console.log("Restarting express server");
server.close();
}
server = express.createServer();
var app = express(); // New syntax for express v3
server = http.createServer(app);
server.use(function (req, res, next) {
app.use(function (req, res, next) {
res.header("Server", serverName);
next();
});
server.configure(function() {
hooks.callAll("expressConfigure", {"app": server});
app.configure(function() {
hooks.callAll("expressConfigure", {"app": app});
});
hooks.callAll("expressCreateServer", {"app": server});
hooks.callAll("expressCreateServer", {"app": app, "server": server});
server.listen(settings.port, settings.ip);
}
}

View file

@ -12,14 +12,10 @@ exports.expressCreateServer = function (hook_name, args, cb) {
errors: [],
};
res.send(eejs.require(
"ep_etherpad-lite/templates/admin/plugins.html",
render_args), {});
res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins.html", render_args) );
});
args.app.get('/admin/plugins/info', function(req, res) {
res.send(eejs.require(
"ep_etherpad-lite/templates/admin/plugins-info.html",
{}), {});
res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {}) );
});
}

View file

@ -16,9 +16,6 @@ exports.gracefulShutdown = function(err) {
console.log("graceful shutdown...");
//stop the http server
exports.app.close();
//do the db shutdown
db.db.doShutdown(function() {
console.log("db sucessfully closed.");
@ -35,11 +32,14 @@ exports.gracefulShutdown = function(err) {
exports.expressCreateServer = function (hook_name, args, cb) {
exports.app = args.app;
args.app.error(function(err, req, res, next){
res.send(500);
console.error(err.stack ? err.stack : err.toString());
exports.gracefulShutdown();
});
// Handle errors
args.app.use(function(err, req, res, next){
// if an error occurs Connect will pass it down
// through these "error-handling" middleware
// allowing you to respond however you like
res.send(500, { error: 'Sorry, something bad happened!' });
console.error(err.stack? err.stack : err.toString());
})
//connect graceful shutdown with sigint and uncaughtexception
if(os.type().indexOf("Windows") == -1) {

View file

@ -56,7 +56,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
ERR(err);
if(err == "notfound")
res.send('404 - Not Found', 404);
res.send(404, '404 - Not Found');
else
res.send(html);
});

View file

@ -7,7 +7,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
//ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(padId) || /\/$/.test(req.url))
{
res.send('Such a padname is forbidden', 404);
res.send(404, 'Such a padname is forbidden');
}
else
{
@ -19,7 +19,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
var query = url.parse(req.url).query;
if ( query ) real_url += '?' + query;
res.header('Location', real_url);
res.send('You should be redirected to <a href="' + real_url + '">' + real_url + '</a>', 302);
res.send(302, 'You should be redirected to <a href="' + real_url + '">' + real_url + '</a>');
}
//the pad id was fine, so just render it
else

View file

@ -3,6 +3,7 @@ var socketio = require('socket.io');
var settings = require('../../utils/Settings');
var socketIORouter = require("../../handler/SocketIORouter");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var webaccess = require("ep_etherpad-lite/node/hooks/express/webaccess");
var padMessageHandler = require("../../handler/PadMessageHandler");
@ -10,19 +11,28 @@ var connect = require('connect');
exports.expressCreateServer = function (hook_name, args, cb) {
//init socket.io and redirect all requests to the MessageHandler
var io = socketio.listen(args.app);
var io = socketio.listen(args.server);
/* Require an express session cookie to be present, and load the
* session. See http://www.danielbaulig.de/socket-ioexpress for more
* info */
io.set('authorization', function (data, accept) {
if (!data.headers.cookie) return accept('No session cookie transmitted.', false);
data.cookie = connect.utils.parseCookie(data.headers.cookie);
data.sessionID = data.cookie.express_sid;
args.app.sessionStore.get(data.sessionID, function (err, session) {
if (err || !session) return accept('Bad session / session has expired', false);
data.session = new connect.middleware.session.Session(data, session);
accept(null, true);
// Use connect's cookie parser, because it knows how to parse signed cookies
connect.cookieParser(webaccess.secret)(data, {}, function(err){
if(err) {
console.error(err);
accept("Couldn't parse request cookies. ", false);
return;
}
data.sessionID = data.signedCookies.express_sid;
args.app.sessionStore.get(data.sessionID, function (err, session) {
if (err || !session) return accept('Bad session / session has expired', false);
data.session = new connect.middleware.session.Session(data, session);
accept(null, true);
});
});
});
@ -62,5 +72,5 @@ exports.expressCreateServer = function (hook_name, args, cb) {
socketIORouter.setSocketIO(io);
socketIORouter.addComponent("pad", padMessageHandler);
hooks.callAll("socketio", {"app": args.app, "io": io});
hooks.callAll("socketio", {"app": args.app, "io": io, "server": args.server});
}

View file

@ -56,10 +56,10 @@ exports.basicAuth = function (req, res, next) {
res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
if (req.headers.authorization) {
setTimeout(function () {
res.send('Authentication required', 401);
res.send(401, 'Authentication required');
}, 1000);
} else {
res.send('Authentication required', 401);
res.send(401, 'Authentication required');
}
}));
}
@ -88,14 +88,13 @@ exports.basicAuth = function (req, res, next) {
});
}
var secret = null;
exports.secret = null;
exports.expressConfigure = function (hook_name, args, cb) {
// If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158.
// Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.
if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR"))
args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
args.app.use(express.cookieParser());
/* Do not let express create the session, so that we can retain a
* reference to it for socket.io to use. Also, set the key (cookie
@ -104,13 +103,14 @@ exports.expressConfigure = function (hook_name, args, cb) {
if (!exports.sessionStore) {
exports.sessionStore = new express.session.MemoryStore();
secret = randomString(32);
exports.secret = randomString(32);
}
args.app.use(express.cookieParser(exports.secret));
args.app.sessionStore = exports.sessionStore;
args.app.use(express.session({store: args.app.sessionStore,
key: 'express_sid',
secret: secret}));
key: 'express_sid' }));
args.app.use(exports.basicAuth);
}

View file

@ -15,7 +15,7 @@ module.exports = function (req, res, callback) {
callback();
//no access
} else {
res.send("403 - Can't touch this", 403);
res.send(403, "403 - Can't touch this");
}
});
}

View file

@ -21,8 +21,15 @@
* limitations under the License.
*/
// set up logger
var log4js = require('log4js');
log4js.replaceConsole();
var settings = require('./utils/Settings');
//set loglevel
log4js.setGlobalLogLevel(settings.loglevel);
var db = require('./db/DB');
var async = require('async');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
@ -31,9 +38,6 @@ var npm = require("npm/lib/npm.js");
hooks.plugins = plugins;
//set loglevel
log4js.setGlobalLogLevel(settings.loglevel);
async.waterfall([
//initalize the database
function (callback)