From 3e47bfd6586f42f9fe8e49cea03c4df976c781a1 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Sat, 18 Apr 2009 20:15:44 +0000 Subject: refactor main.js into many files and improve show/list error handling git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@766383 13f79535-47bb-0310-9956-ffa450edef68 --- share/server/render.js | 252 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 share/server/render.js (limited to 'share/server/render.js') diff --git a/share/server/render.js b/share/server/render.js new file mode 100644 index 00000000..89c31ea1 --- /dev/null +++ b/share/server/render.js @@ -0,0 +1,252 @@ +// 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. + + +// mimeparse.js +// http://code.google.com/p/mimeparse/ +// Code with comments: http://mimeparse.googlecode.com/svn/trunk/mimeparse.js +// Tests: http://mimeparse.googlecode.com/svn/trunk/mimeparse-js-test.html +// Ported from version 0.1.2 + +var Mimeparse = (function() { + function strip(string) { + return string.replace(/^\s+/, '').replace(/\s+$/, ''); + }; + function parseRanges(ranges) { + var parsedRanges = [], rangeParts = ranges.split(","); + for (var i=0; i < rangeParts.length; i++) { + parsedRanges.push(publicMethods.parseMediaRange(rangeParts[i])); + }; + return parsedRanges; + }; + var publicMethods = { + parseMimeType : function(mimeType) { + var fullType, typeParts, params = {}, parts = mimeType.split(';'); + for (var i=0; i < parts.length; i++) { + var p = parts[i].split('='); + if (p.length == 2) { + params[strip(p[0])] = strip(p[1]); + } + }; + fullType = parts[0].replace(/^\s+/, '').replace(/\s+$/, ''); + if (fullType == '*') fullType = '*/*'; + typeParts = fullType.split('/'); + return [typeParts[0], typeParts[1], params]; + }, + parseMediaRange : function(range) { + var q, parsedType = this.parseMimeType(range); + if (!parsedType[2]['q']) { + parsedType[2]['q'] = '1'; + } else { + q = parseFloat(parsedType[2]['q']); + if (isNaN(q)) { + parsedType[2]['q'] = '1'; + } else if (q > 1 || q < 0) { + parsedType[2]['q'] = '1'; + } + } + return parsedType; + }, + fitnessAndQualityParsed : function(mimeType, parsedRanges) { + var bestFitness = -1, bestFitQ = 0, target = this.parseMediaRange(mimeType); + var targetType = target[0], targetSubtype = target[1], targetParams = target[2]; + + for (var i=0; i < parsedRanges.length; i++) { + var parsed = parsedRanges[i]; + var type = parsed[0], subtype = parsed[1], params = parsed[2]; + if ((type == targetType || type == "*" || targetType == "*") && + (subtype == targetSubtype || subtype == "*" || targetSubtype == "*")) { + var matchCount = 0; + for (param in targetParams) { + if (param != 'q' && params[param] && params[param] == targetParams[param]) { + matchCount += 1; + } + } + + var fitness = (type == targetType) ? 100 : 0; + fitness += (subtype == targetSubtype) ? 10 : 0; + fitness += matchCount; + + if (fitness > bestFitness) { + bestFitness = fitness; + bestFitQ = params["q"]; + } + } + }; + return [bestFitness, parseFloat(bestFitQ)]; + }, + qualityParsed : function(mimeType, parsedRanges) { + return this.fitnessAndQualityParsed(mimeType, parsedRanges)[1]; + }, + quality : function(mimeType, ranges) { + return this.qualityParsed(mimeType, parseRanges(ranges)); + }, + + // Takes a list of supported mime-types and finds the best + // match for all the media-ranges listed in header. The value of + // header must be a string that conforms to the format of the + // HTTP Accept: header. The value of 'supported' is a list of + // mime-types. + // + // >>> bestMatch(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1') + // 'text/xml' + bestMatch : function(supported, header) { + var parsedHeader = parseRanges(header); + var weighted = []; + for (var i=0; i < supported.length; i++) { + weighted.push([publicMethods.fitnessAndQualityParsed(supported[i], parsedHeader), supported[i]]); + }; + weighted.sort(); + return weighted[weighted.length-1][0][1] ? weighted[weighted.length-1][1] : ''; + } + }; + return publicMethods; +})(); + +// this function provides a shortcut for managing responses by Accept header +respondWith = function(req, responders) { + var bestKey = null, accept = req.headers["Accept"]; + if (accept && !req.query.format) { + var provides = []; + for (key in responders) { + if (mimesByKey[key]) { + provides = provides.concat(mimesByKey[key]); + } + } + var bestMime = Mimeparse.bestMatch(provides, accept); + bestKey = keysByMime[bestMime]; + } else { + bestKey = req.query.format; + } + var rFunc = responders[bestKey || responders.fallback || "html"]; + if (rFunc) { + var resp = maybeWrapResponse(rFunc()); + resp["headers"] = resp["headers"] || {}; + resp["headers"]["Content-Type"] = bestMime; + respond(resp); + } else { + throw({code:406, body:"Not Acceptable: "+accept}); + } +}; + +// whoever registers last wins. +mimesByKey = {}; +keysByMime = {}; +registerType = function() { + var mimes = [], key = arguments[0]; + for (var i=1; i < arguments.length; i++) { + mimes.push(arguments[i]); + }; + mimesByKey[key] = mimes; + for (var i=0; i < mimes.length; i++) { + keysByMime[mimes[i]] = key; + }; +}; + +// Some default types +// Ported from Ruby on Rails +// Build list of Mime types for HTTP responses +// http://www.iana.org/assignments/media-types/ +// http://dev.rubyonrails.org/svn/rails/trunk/actionpack/lib/action_controller/mime_types.rb + +registerType("all", "*/*"); +registerType("text", "text/plain", "txt"); +registerType("html", "text/html"); +registerType("xhtml", "application/xhtml+xml", "xhtml"); +registerType("xml", "application/xml", "text/xml", "application/x-xml"); +// http://www.ietf.org/rfc/rfc4627.txt +registerType("json", "application/json", "text/x-json"); +registerType("js", "text/javascript", "application/javascript", "application/x-javascript"); +registerType("css", "text/css"); +registerType("ics", "text/calendar"); +registerType("csv", "text/csv"); +registerType("rss", "application/rss+xml"); +registerType("atom", "application/atom+xml"); +registerType("yaml", "application/x-yaml", "text/yaml"); +// just like Rails +registerType("multipart_form", "multipart/form-data"); +registerType("url_encoded_form", "application/x-www-form-urlencoded"); + + +var Render = (function() { + var row_info; + return { + showDoc : function(funSrc, doc, req) { + var formFun = compileFunction(funSrc); + runRenderFunction(formFun, [doc, req], funSrc); + }, + listBegin : function(head, req) { + row_info = { first_key: null, row_number: 0, prev_key: null }; + runRenderFunction(funs[0], [head, null, req, null], funsrc[0]); + }, + listRow : function(row, req) { + if (row_info.first_key == null) { + row_info.first_key = row.key; + } + runRenderFunction(funs[0], [null, row, req, row_info], funsrc[0], true); + row_info.prev_key = row.key; + row_info.row_number++; + }, + listTail : function(req) { + runRenderFunction(funs[0], [null, null, req, row_info], funsrc[0]); + } + } +})(); + +function runRenderFunction(renderFun, args, funSrc, htmlErrors) { + responseSent = false; + try { + var resp = renderFun.apply(null, args); + if (!responseSent) { + if (resp) { + respond(maybeWrapResponse(resp)); + } else { + respond({error:"render_error",reason:"undefined response from render function"}); + } + } + } catch(e) { + var logMessage = "function raised error: "+e.toString(); + log(logMessage); + // log("stacktrace: "+e.stack); + var errorMessage = htmlErrors ? htmlRenderError(e, funSrc) : logMessage; + respond({ + error:"render_error", + reason:errorMessage}); + } +}; + +function escapeHTML(string) { + return string.replace(/&/g, "&") + .replace(//g, ">"); +} + +function htmlRenderError(e, funSrc) { + var msg = ["

Render Error

", + "

JavaScript function raised error: ", + e.toString(), + "

Stacktrace:

",
+    escapeHTML(e.stack),
+    "

Function source:

",
+    escapeHTML(funSrc),
+    "
"].join(''); + return {body:msg}; +}; + +function maybeWrapResponse(resp) { + var type = typeof resp; + if ((type == "string") || (type == "xml")) { + return {body:resp}; + } else { + return resp; + } +}; -- cgit v1.2.3