From d7c5ad7d6263fd1baf9bfdbaa4c50b70ef2fbdb2 Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Tue, 8 Jun 2010 08:22:05 +0200 Subject: reverted folder structure change for better mergeing with upstream --- .../framework-src/modules/global/appjet.js | 107 +++++++ .../framework-src/modules/global/request.js | 312 +++++++++++++++++++++ .../framework-src/modules/global/response.js | 294 +++++++++++++++++++ 3 files changed, 713 insertions(+) create mode 100644 trunk/infrastructure/framework-src/modules/global/appjet.js create mode 100644 trunk/infrastructure/framework-src/modules/global/request.js create mode 100644 trunk/infrastructure/framework-src/modules/global/response.js (limited to 'trunk/infrastructure/framework-src/modules/global') diff --git a/trunk/infrastructure/framework-src/modules/global/appjet.js b/trunk/infrastructure/framework-src/modules/global/appjet.js new file mode 100644 index 0000000..135ac44 --- /dev/null +++ b/trunk/infrastructure/framework-src/modules/global/appjet.js @@ -0,0 +1,107 @@ +/** + * Copyright 2009 Google Inc. + * + * 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. + */ + +import("jsutils.scalaF0"); + +//---------------------------------------------------------------- +// global static "appjet" object +//---------------------------------------------------------------- + +/** + * @fileOverview The global appjet object contains access to the AppJet runtime, + * app meta-data, and other information. + */ +var appjet = { + +/** + * This is the interface to the execution context. You probably won't need + * to use this, but if you do, be careful! + * @type object + */ +get context() { + return net.appjet.oui.ExecutionContextUtils.currentContext(); +}, + +get executionId() { + return this.context.executionId(); +}, + +// /** +// * Holds the current request's requestId. (These IDs may be reused!) +// * @type String +// */ +// get requestId() { +// return this.context.requestId(); +// }, + +/** + * Volatile cache that persists between requests. (JavaScript object). + */ +get cache() { + return Packages.net.appjet.ajstdlib.ajstdlib.attributes() + .getOrElseUpdate("cache", scalaF0({})); +}, + +get cacheRoot() { + return function(name) { + return Packages.net.appjet.ajstdlib.ajstdlib.attributes() + .getOrElseUpdate("cache-"+(name?name:""), scalaF0({})); + }; +}, + +/** + * A global lock for this app (ReentrantLock object). + */ +get globalLock() { + return net.appjet.ajstdlib.ajstdlib.globalLock(); +}, + +/** + * Per-request cache, cleared between requests. + */ +get requestCache() { + return this.context.attributes().getOrElseUpdate("requestCache", scalaF0({})) +}, + +/** + * Per-scope cache, persisted in this "server" instance. + */ +get scopeCache() { + return this.context.runner().attributes().getOrElseUpdate("scopeCache", scalaF0({})); +}, + +/** + * config params for app. + */ +get config() { + return Packages.net.appjet.oui.config.configObject(this.context.runner().globalScope()); +}, + +/** + * tells appjet not to re-use this "scope"/"server" + */ +get retireScope() { + return function() { this.context.runner().reuseOk_$eq(false); } +}, + +/** + * How many milliseconds the server has been running for. + */ +get uptime() { + return Date.now() - Packages.net.appjet.oui.main.startTime().getTime(); +} + +}; // end: var appjet = {... \ No newline at end of file diff --git a/trunk/infrastructure/framework-src/modules/global/request.js b/trunk/infrastructure/framework-src/modules/global/request.js new file mode 100644 index 0000000..a4327f9 --- /dev/null +++ b/trunk/infrastructure/framework-src/modules/global/request.js @@ -0,0 +1,312 @@ +/** + * Copyright 2009 Google Inc. + * + * 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. + */ + +import("stringutils.trim"); +import("jsutils.scalaF0") + +function _cx() { return appjet.context }; + +function _addIfNotPresent(obj, key, value) { + if (!(key in obj)) obj[key] = value; +} + +var request = { + +get isDefined() { + return ( + _cx() != null && + _cx().request() != null && + (! _cx().request().isFake()) && + _cx().request().req() != null + ); +}, + +get cache() { + var req = _cx().request().req(); + if (req.getAttribute("jsCache") == null) { + req.setAttribute("jsCache", {}); + } + return req.getAttribute("jsCache"); +}, + +get continuation() { + if (this.isDefined) { + var c = Packages.net.appjet.ajstdlib.execution.getContinuation(_cx()); + var u = this.underlying; + return { + suspend: function(timeout) { + return Packages.net.appjet.ajstdlib.execution.sync( + u, scalaF0(function() { return c.suspend(timeout); })); + }, + resume: function() { + Packages.net.appjet.ajstdlib.execution.sync( + u, scalaF0(function() { c.resume(); })) + } + } + } +}, + +get underlying() { + if (this.isDefined) { + return _cx().request().req(); + } +}, + +/** + * The request path following the hostname. For example, if the user + * is visiting yourapp.appjet.net/foo, then this will be set to + * "/foo". + * + * This does not include CGI parameters or the domain name, and always + * begins with a "/". + * + * @type string + */ +get path() { + if (this.isDefined) { + return String(_cx().request().path()); + } +}, + +/** + * The value request query string. + * + * For example, if the user visits "yourapp.appjet.net/foo?id=20", then + * query will be "id=20". + * + * @type string + */ +get query() { + if (this.isDefined) { + if (_cx().request().query() != null) { + return _cx().request().query(); + } + } +}, + +/** + * The content of a POST request. Retrieving this value may interfere + * with the ability to get post request parameters sent in the body of + * a request via the "params" property. Use with care. + * + * @type string + */ +get content() { + if (this.isDefined) { + if (_cx().request().content() != null) { + return _cx().request().content(); + } + } +}, + +/** + * Either "GET" or "POST" (uppercase). + * @type string + */ +get method() { + if (this.isDefined) { + return String(_cx().request().method().toUpperCase()); + } +}, + +/** + * Whether the curent HTTP request is a GET request. + * @type boolean + */ +get isGet() { + return (this.method == "GET"); +}, + +/** + * Whether the current HTTP request is a POST request. + * @type boolean + */ +get isPost() { + return (this.method == "POST"); +}, + +/** + * Either "http" or "https" (lowercase). + * @type string + */ +get scheme() { + if (this.isDefined) { + return String(_cx().request().scheme()); + } +}, + +/** + * Whether the current request arrived using HTTPS. + * @type boolean + */ +get isSSL() { + return (this.scheme == "https"); +}, + +/** + * Holds the IP address of the user making the request. + * @type string + */ +get clientAddr() { + if (this.isDefined) { + return String(_cx().request().clientAddr()); + } +}, + +/** + * Parameters associated with the request, either from the query string + * or from the contents of a POST, e.g. from a form. Parameters are accessible + * by name as properties of this object. The property value is either a + * string (typically) or an array of strings (if the parameter occurs + * multiple times in the request). + * + * @type object + */ +get params() { + if (this.isDefined) { + var cx = _cx(); + var req = cx.request(); + return cx.attributes().getOrElseUpdate("requestParams", + scalaF0(function() { return req.params(cx.runner().globalScope()); })); + } +}, + +/** + * Uploaded files associated with the request, from the contents of a POST. + * + * @type object + */ +get files() { + if (this.isDefined) { + var cx = _cx(); + var req = cx.request(); + return cx.attributes().getOrElseUpdate("requestFiles", + scalaF0(function() { return req.files(cx.runner().globalScope()); })); + } +}, + +/** + * Used to access the HTTP headers of the current request. Properties are + * header names, and each value is either a string (typically) or an + * array of strings (if the header occurs multiple times in the request). + * + * @example +print(request.headers["User-Agent"]); + * + * @type object + */ +get headers() { + if (this.isDefined) { + var cx = _cx(); + var req = cx.request(); + return cx.attributes().getOrElseUpdate("requestHeaders", + scalaF0(function() { return req.headers(cx.runner().globalScope()); })); + } +}, + +// TODO: this is super inefficient to do each time someone accesses +// request.cookies.foo. We should probably store _cookies in the requestCache. +get cookies() { + var _cookies = {}; + var cookieHeaderArray = this.headers['Cookie']; + if (!cookieHeaderArray) { return {}; } + if (!(cookieHeaderArray instanceof Array)) + cookieHeaderArray = [cookieHeaderArray]; + var name, val; + + cookieHeaderArray.forEach(function (cookieHeader) { + cookieHeader.split(';').forEach(function(cs) { + var parts = cs.split('='); + if (parts.length == 2) { + name = trim(parts[0]); + val = trim(unescape(parts[1])); + _addIfNotPresent(_cookies, name, val); + } + }); + }); + + return _cookies; +}, + +/** + * Holds the full URL of the request. + */ +get url() { + if (this.isDefined) { + return this.scheme+"://"+this.host+this.path+(this.query ? "?"+this.query : ""); + } +}, + +get host() { + if (this.isDefined) { + // required by HTTP/1.1 to be present. + return String(this.headers['Host']).toLowerCase(); + } +}, + +get domain() { + if (this.isDefined) { + // like host, but without the port if there is one. + return this.host.split(':')[0]; + } +}, + +get uniqueId() { + return String(_cx().executionId()); +}, + +get protocol() { + if (this.isDefined) { + return String(_cx().request().protocol()); + } +}, + +get userAgent() { + if (this.isDefined) { + var agentString = (request.headers['User-Agent'] || "?"); + return { + toString: function() { return agentString; }, + isIPhone: function() { return (agentString.indexOf("(iPhone;") > 0); } + }; + } +}, + +get acceptsGzip() { + if (this.isDefined) { + var headerArray = this.headers["Accept-Encoding"]; + if (! (headerArray instanceof Array)) { + headerArray = [headerArray]; + } + // Want to see if some accept-encoding header OK's gzip. + // Starting with: "Accept-Encoding: gzip; q=0.5, deflate; q=1.0" + // 1. Split into ["gzip; q=0.5", "delfate; q=1.0"] + // 2. See if some entry is gzip with q > 0. (q is optional.) + return headerArray.some(function(header) { + if (! header) return false; + return header.split(/,\s*/).some(function(validEncoding) { + if (!validEncoding.indexOf("gzip") == 0) { + return false; + } + if (/q=[0\.]*$/.test(validEncoding)) { + return false; + } + return true; + }); + }); + } +} + +}; // end: var request = {... diff --git a/trunk/infrastructure/framework-src/modules/global/response.js b/trunk/infrastructure/framework-src/modules/global/response.js new file mode 100644 index 0000000..7236920 --- /dev/null +++ b/trunk/infrastructure/framework-src/modules/global/response.js @@ -0,0 +1,294 @@ +/** + * Copyright 2009 Google Inc. + * + * 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. + */ + +/** + * @fileOverview Helpers for the HTTP response. + */ + +/** @ignore */ +function _cx() { return appjet.context }; + +/** @ignore */ +function _cookiestring(c) { + var x = ''; + if (!c.name) { throw new Error('cookie name is required'); } + if (!c.value) { c.value = ''; } + x += (c.name + '=' + escape(c.value)); + + // expires + if (c.expires instanceof Date) { + x += ('; expires='+_cookiedate(c.expires)); + } + if (typeof(c.expires) == 'number') { + var today = (new Date()).valueOf(); + var d = new Date(today + 86400000*c.expires); + x += ('; expires='+_cookiedate(d)); + } + + // domain + if (c.domain) { x += ('; domain='+c.domain); } + + // path + if (c.path) { x += ('; path='+c.path); } + + // secure + if (c.secure == true) { x += '; secure'; } + + return x; +}; + +/** @ignore */ +function _cookiedate(d) { + var x = d.toGMTString(); + var p = x.split(' '); + return [p[0], [p[1], p[2], p[3]].join('-'), p[4], p[5]].join(' '); +}; + +var response = { + +get isDefined() { + return _cx().response() != null; +} + +}; + +/** + * Halts the program immediately and returns 403 Forbidden error to the user. + */ +response.forbid = function() { + _cx().response().error(403, "Forbidden"); +}; + +/** + * Halts the program immediately. + * + * @param {boolean} renderCurrentPage if false, an empty page will be rendered, + * otherwise calls to print() so far will be displayed. Either way, no more + * code will be executed. + */ +response.stop = function(renderCurrentPage) { + _cx().response().stop(); +}; + +/** + * Halts the program immediately and returns a 404 not found error to the user. + */ +response.notFound = function() { + _cx().response().error(404, "404: Not found"); +}; + +/** + * Halts the program immediately and sends an HTTP redirect response (302), + * redirecting to the given path (relative or absolute). + * + * @param {string} path The new path + */ +response.redirect = function(path) { + if ((! path) && path != "") { + throw new Error("Invalid redirect: "+path); + } + if (path.indexOf('/') == 0) { + // make sure absolute URL has proper host/port + path = request.scheme+"://"+request.host+path; + } + _cx().response().redirect(path); +}; + +/** + * Sets the status code in the HTTP response. + * + * @param {number} newCode + */ +response.setStatusCode = function(newCode) { + _cx().response().setStatusCode(newCode); +}; +response.getStatusCode = function() { + return _cx().response().getStatusCode(); +}; + +response.sendError = function(errorCode, errorHtml) { + _cx().response().error(errorCode, errorHtml); +}; + +response.reset = function() { + _cx().response().reset(); +}; + +/** + * Sets any header of the HTTP response. + * + * @example +response.setHeader('Cache-Control', 'no-cache'); + * + * @param {string} name + * @param {string} value + */ +response.setHeader = function(name, value) { + _cx().response().setHeader(name, value); +}; + +/** + * Adds the name,value pair to the headers. Useful for headers that are + * allowed to repeat, such as Set-Cookie. + * + * @param {string} name + * @param {string} value + */ +response.addHeader = function(name, value) { + _cx().response().addHeader(name, value); +}; + +/** + * Returns the value of a previously-set header. Useful in the + * postRequestHandler to see values of headers set during normal + * request processing. + * + * @param {string} name + * @return {array} An array of header values. Empty array if none set. + */ +response.getHeader = function(name) { + if (! this.isDefined) { + return []; + } else { + return _cx().response().getHeader(name); + } +}; + +/** + * Removes all instances of a header of the HTTP response. + * + * @param {string} name + */ +response.removeHeader = function(name) { + _cx().response().removeHeader(name); +}; + +/** + * Low-level hook for writing raw data to the response. + * @param {string} data will be written, verbatim, to the HTTP resonse. + */ +response.write = function(data) { + _cx().response().write(data); +}; + +/** + * Low-level hook for writing raw byte data to the response. Especially + * useful for writing the result of a wget of image data, + * or writing an uploaded file. + * @param {string} data will be written, verbatim, to the HTTP resonse. + */ +response.writeBytes = function(data) { + _cx().response().writeBytes(data); +}; + +//---------------------------------------------------------------- +// Cookies! +//---------------------------------------------------------------- + +/** + * Set a cookie in the response. + * + * @example +response.setCookie({ + name: "SessionID", + value: "25", + secure: true, + expires: 14 // 14 days +}); + * + * @param {object} cookieObject This may contain any of the following: + + */ +response.setCookie = function(cookieObject) { + this.addHeader('Set-Cookie', _cookiestring(cookieObject)); + + var p3pHeader = this.getHeader("P3P"); + if ((! p3pHeader) || p3pHeader.length == 0) { + // The existence of this "privacy policy" header allows cookies set on + // pages inside iframes to be accepted by IE. (This is some kind of + // default policy copied from an example online. -- dgreensp) + this.setHeader('P3P', 'CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA"'); + } +}; + +/** + * Tells the client to delete the cookie of the given name (by setting + * its expiration time to zero). + * @param {string} name The name of the cookie to delete. + */ +response.deleteCookie = function(name) { + this.setCookie({name: name, value: '', expires: 0}); +}; + +function _trim(s) { + return String((new java.lang.String(s)).trim()); +} + +response.getCookie = function(name) { + var cookieHeaders = this.getHeader('Set-Cookie'); + if (! cookieHeaders) { return; } + for (var i = 0; i < cookieHeaders.length; ++i) { + if (_trim(cookieHeaders[i].split("=")[0]) == name) + return _trim(cookieHeaders[i].split(";")[0].split("=")[1]); + } +}; + +/** + * Sets the Content-Type header of the response. If the content-type includes + * a charset, that charset is used to send the response. + * @param {string} contentType the new content-type + */ +response.setContentType = function(contentType) { + _cx().response().setContentType(contentType); +}; + +response.getCharacterEncoding = function() { + return _cx().response().getCharacterEncoding(); +} + +response.neverCache = function() { + // be aggressive about not letting the response be cached. + var that = this; + function sh(k,v) { that.setHeader(k,v); } + sh('Expires', 'Sat, 18 Jun 1983 07:07:07 GMT'); + sh('Last-Modified', (new Date()).toGMTString()); + sh('Cache-Control', ('no-store, no-cache, must-revalidate, '+ + 'post-check=0, pre-check=0')); + sh('Pragma', 'no-cache'); +}; + +response.alwaysCache = function() { + var that = this; + function sh(k,v) { that.setHeader(k,v); } + that.removeHeader('Last-Modified'); + that.removeHeader('Pragma'); + var futureDate = new Date(); + futureDate.setTime(Date.now() + 315360000000); + sh('Expires', futureDate.toGMTString()); + sh('Cache-Control', 'max-age=315360000'); +}; + +response.setGzip = function(gzip) { + _cx().response().setGzip(gzip); +} -- cgit v1.2.3-1-g7c22