Skip to content

Commit

Permalink
Add new init(options) signature for server endpoints
Browse files Browse the repository at this point in the history
An endpoint can now take a single object containing 'method', 'pathname',
'query', 'headers', and 'data' and return an integer, an array containing
[statusCode, contentType, body], or a promise for either. This allows the
handlers to use the HTTP method and headers and removes the need for callbacks
when some handlers already use coroutine().

If init() returns a promise, it now has to use the new single-parameter
signature (because the check is done with Function.length, and combining
promises and callbacks doesn't make sense anyway).
  • Loading branch information
dstillman committed Dec 5, 2016
1 parent 5760342 commit b5bc18c
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 39 deletions.
56 changes: 50 additions & 6 deletions chrome/content/zotero/xpcom/server.js
Expand Up @@ -282,7 +282,7 @@ Zotero.Server.DataListener.prototype._headerFinished = function() {
if(method[1] == "HEAD" || method[1] == "OPTIONS") {
this._requestFinished(this._generateResponse(200));
} else if(method[1] == "GET") {
this._processEndpoint("GET", null);
this._processEndpoint("GET", null); // async
} else if(method[1] == "POST") {
const contentLengthRe = /[\r\n]Content-Length: +([0-9]+)/i;

Expand Down Expand Up @@ -322,7 +322,7 @@ Zotero.Server.DataListener.prototype._bodyData = function() {
}

// handle envelope
this._processEndpoint("POST", this.body);
this._processEndpoint("POST", this.body); // async
}
}

Expand Down Expand Up @@ -358,7 +358,7 @@ Zotero.Server.DataListener.prototype._generateResponse = function(status, conten
/**
* Generates a response based on calling the function associated with the endpoint
*/
Zotero.Server.DataListener.prototype._processEndpoint = function(method, postData) {
Zotero.Server.DataListener.prototype._processEndpoint = Zotero.Promise.coroutine(function* (method, postData) {
try {
var endpoint = new this.endpoint;

Expand Down Expand Up @@ -423,10 +423,54 @@ Zotero.Server.DataListener.prototype._processEndpoint = function(method, postDat
me._requestFinished(me._generateResponse(code, contentType, arg));
}

// pass to endpoint
if (endpoint.init.length === 2) {
// Pass to endpoint
//
// Single-parameter endpoint
// - Takes an object with 'method', 'pathname', 'query', 'headers', and 'data'
// - Returns a status code, an array containing [statusCode, contentType, body],
// or a promise for either
if (endpoint.init.length === 1
// Return value from Zotero.Promise.coroutine()
|| endpoint.init.length === 0) {
let headers = {};
let headerLines = this.header.trim().split(/\r\n/);
for (let line of headerLines) {
line = line.trim();
let pos = line.indexOf(':');
if (pos == -1) {
continue;
}
let k = line.substr(0, pos);
let v = line.substr(pos + 1).trim();
headers[k] = v;
}

let maybePromise = endpoint.init({
method,
pathname: this.pathname,
query: this.query ? Zotero.Server.decodeQueryString(this.query.substr(1)) : {},
headers,
data: decodedData
});
let result;
if (maybePromise.then) {
result = yield maybePromise;
}
else {
result = maybePromise;
}
if (Number.isInteger(result)) {
sendResponseCallback(result);
}
else {
sendResponseCallback(...result);
}
}
// Two-parameter endpoint takes data and a callback
else if (endpoint.init.length === 2) {
endpoint.init(decodedData, sendResponseCallback);
}
// Three-parameter endpoint takes a URL, data, and a callback
else {
const uaRe = /[\r\n]User-Agent: +([^\r\n]+)/i;
var m = uaRe.exec(this.header);
Expand All @@ -442,7 +486,7 @@ Zotero.Server.DataListener.prototype._processEndpoint = function(method, postDat
this._requestFinished(this._generateResponse(500), "text/plain", "An error occurred\n");
throw e;
}
}
});

/*
* returns HTTP data from a request
Expand Down
66 changes: 33 additions & 33 deletions chrome/content/zotero/xpcom/server_connector.js
Expand Up @@ -320,10 +320,10 @@ Zotero.Server.Connector.SaveItem.prototype = {
/**
* Either loads HTML into a hidden browser and initiates translation, or saves items directly
* to the database
* @param {Object} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
init: Zotero.Promise.coroutine(function* (url, data, sendResponseCallback) {
init: Zotero.Promise.coroutine(function* (options) {
var data = options.data;

// figure out where to save
var zp = Zotero.getActiveZoteroPane();
try {
Expand Down Expand Up @@ -351,13 +351,12 @@ Zotero.Server.Connector.SaveItem.prototype = {
}
else {
Zotero.logError("Can't add item to read-only library " + library.name);
sendResponseCallback(500);
return;
return 500;
}
}

var cookieSandbox = data["uri"] ? new Zotero.CookieSandbox(null, data["uri"],
data["detailedCookies"] ? "" : data["cookie"] || "", url.userAgent) : null;
data["detailedCookies"] ? "" : data["cookie"] || "", options.userAgent) : null;
if(cookieSandbox && data.detailedCookies) {
cookieSandbox.addCookiesFromHeader(data.detailedCookies);
}
Expand All @@ -374,6 +373,7 @@ Zotero.Server.Connector.SaveItem.prototype = {
forceTagType: 1,
cookieSandbox
});
var deferred = Zotero.Promise.defer();
itemSaver.saveItems(data.items, function(returnValue, items) {
if(returnValue) {
try {
Expand All @@ -387,16 +387,17 @@ Zotero.Server.Connector.SaveItem.prototype = {
}
}

sendResponseCallback(201, "application/json", JSON.stringify({items: data.items}));
deferred.resolve([201, "application/json", JSON.stringify({items: data.items})]);
} catch(e) {
Zotero.logError(e);
sendResponseCallback(500);
deferred.resolve(500);
}
} else {
Zotero.logError(items);
sendResponseCallback(500);
deferred.resolve(500);
}
}, Zotero.Server.Connector.AttachmentProgressManager.onProgress);
return deferred.promise;
})
}

Expand All @@ -419,10 +420,10 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {

/**
* Save snapshot
* @param {String} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
init: Zotero.Promise.coroutine(function* (url, data, sendResponseCallback) {
init: Zotero.Promise.coroutine(function* (options) {
var data = options.data;

Zotero.Server.Connector.Data[data["url"]] = "<html>"+data["html"]+"</html>";

var zp = Zotero.getActiveZoteroPane();
Expand Down Expand Up @@ -451,8 +452,7 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
}
else {
Zotero.logError("Can't add item to read-only library " + library.name);
sendResponseCallback(500);
return;
return 500;
}
}

Expand All @@ -466,7 +466,7 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
filesEditable = true;
}

var cookieSandbox = new Zotero.CookieSandbox(null, data["url"], data["cookie"], url.userAgent);
var cookieSandbox = new Zotero.CookieSandbox(null, data["url"], data["cookie"], options.userAgent);

if (data.pdf && filesEditable) {
delete Zotero.Server.Connector.Data[data.url];
Expand All @@ -479,14 +479,15 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
contentType: "application/pdf",
cookieSandbox
});
sendResponseCallback(201)
return 201;
}
catch (e) {
sendResponseCallback(500);
throw e;
Zotero.logError(e);
return 500;
}
}
else {
let deferred = Zotero.Promise.defer();
Zotero.HTTP.processDocuments(
["zotero://connector/" + encodeURIComponent(data.url)],
Zotero.Promise.coroutine(function* (doc) {
Expand All @@ -512,16 +513,17 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
});
}

sendResponseCallback(201);
deferred.resolve(201);
} catch(e) {
Zotero.debug("ERROR");
Zotero.debug(e);
sendResponseCallback(500);
deferred.resolve(500);
throw e;
}
}),
null, null, false, cookieSandbox
);
return deferred.promise;
}
})
}
Expand Down Expand Up @@ -600,16 +602,16 @@ Zotero.Server.Connector.Import.prototype = {
supportedDataTypes: '*',
permitBookmarklet: false,

init: Zotero.Promise.coroutine(function* (url, data, sendResponseCallback){
init: Zotero.Promise.coroutine(function* (options) {
let translate = new Zotero.Translate.Import();
translate.setString(data);
translate.setString(options.data);
let translators = yield translate.getTranslators();
if (!translators || !translators.length) {
return sendResponseCallback(400);
return 400;
}
translate.setTranslator(translators[0]);
let items = yield translate.translate();
return sendResponseCallback(201, "application/json", JSON.stringify(items));
return [201, "application/json", JSON.stringify(items)];
})
}

Expand All @@ -627,13 +629,13 @@ Zotero.Server.Connector.InstallStyle.prototype = {
supportedDataTypes: '*',
permitBookmarklet: false,

init: Zotero.Promise.coroutine(function* (url, data, sendResponseCallback){
init: Zotero.Promise.coroutine(function* (options) {
try {
var styleName = yield Zotero.Styles.install(data, url.query.origin || null, true);
var styleName = yield Zotero.Styles.install(options.data, options.query.origin || null, true);
} catch (e) {
sendResponseCallback(400, "text/plain", e.message)
return [400, "text/plain", e.message];
}
sendResponseCallback(201, "application/json", JSON.stringify({name: styleName}));
return [201, "application/json", JSON.stringify({name: styleName})];
})
};

Expand Down Expand Up @@ -741,16 +743,14 @@ Zotero.Server.Connector.GetClientHostnames.prototype = {

/**
* Returns a 200 response to say the server is alive
* @param {String} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
init: Zotero.Promise.coroutine(function* (url, postData, sendResponseCallback) {
init: Zotero.Promise.coroutine(function* (options) {
try {
var hostnames = yield Zotero.Proxies.DNS.getHostnames();
} catch(e) {
sendResponseCallback(500);
return 500;
}
sendResponseCallback(200, "application/json", JSON.stringify(hostnames));
return [200, "application/json", JSON.stringify(hostnames)];
})
};

Expand Down

0 comments on commit b5bc18c

Please sign in to comment.