JSGI/Level0/A/Draft2
JSGI Level 0/A Draft 2 Proposal (AKA JSGI 0.3)
Contents
Philosophy
- Straightforward but convenient mapping to HTTP and existing gateway interface conventions
- Suitable for extension with a defined mechanism for Extension Proposals
- Cosmetic modifications more inline with JavaScript conventions and idioms
Principles
- Discourage duplicate data in request and response objects
- Optimize for the Application developer, not the Server or Middleware developer
- Adhere to HTTP semantics wherever possible
Specification
Application
A JSGI Application is a JavaScript Function which takes exactly one argument, the Request as a JavaScript Object, and returns its Response as a JavaScript Object containing three required attributes: status, headers and body (DL: should we mention `then` here?).
Middleware
JSGI Middleware are JSGI Applications that can call other JSGI Applications. Middleware can be stacked up into a call chain to provide useful services to Requests and Responses.
Server
A JSGI Server is the glue that connects JSGI Applications to HTTP request and response messages. (DL: this definition sucks, but Server should be defined...ideas?)
Request
The request environment MUST be a JavaScript Object representative of an HTTP request. Applications are free to modify the Request.
Required Keys
The Request is required to include these keys:
- .method: The HTTP request method, such as "GET" or "POST". Request's method cannot be undefined and so MUST be present with a value of an UPPERCASE string.
- .scriptName: The initial portion of the request URL's "path" that corresponds to the Application object, so that the Application knows its virtual "location". This MAY be an empty string, if the Application corresponds to the "root" of the Server. Restriction: if non-empty `scriptName` MUST start with "/", MUST NOT end with "/" and MUST NOT be decoded.
- .pathInfo: The remainder of the request URL's "path", designating the virtual "location" of the Request's target within the Application. This may be an empty string, if the request URL targets the Application root and does not have a trailing slash. Restriction: if non-empty `pathInfo` MUST start with "/" and MUST NOT be decoded.
- .queryString: The portion of the request URL that follows the first "?", if any. Restriction: MAY be an empty string but `queryString` key MUST NOT be undefined.
- .host: The portion of the request URL that follows the `scheme`. Restriction: MUST be non-empty String, MUST NOT contain a colon or slash. Note: when combined with `scheme`, `port`, `scriptName`, `pathInfo`, and `queryString` this variable can be used to complete the URL. However Request's `headers.host`, if present, should be used in preference to `host` for reconstructing the request URL. DL: not according to RFC 2616: If Request-URI is an absoluteURI, the host is part of the Request-URI. Any Host header field value in the request MUST be ignored.
- .port: Representative of the Request port. If `host` is followed by a colon this is all digits following this colon. If not present in the request URL `port` can be derived from the `scheme`. Restriction: MUST NOT be undefined and MUST be an integer.
- .scheme: URL scheme (per RFC 1738). "http", "https", etc.
- .input: The request body. Requirement: MUST be an input stream.
- .headers: Variables corresponding to the client-supplied HTTP request headers are stored in the `headers` object. The presence or absence of these variables should correspond with the presence or absence of the appropriate HTTP header in the request. All keys in the `headers` object MUST be the lower-case equivalent of the request's HTTP header keys. See: example requests for more details.
- .jsgi: The Request MUST include these JSGI-specific variables in a `jsgi` key:
- .jsgi.version: The Array [0,3], representing this version of JSGI.
- .jsgi.errors: Stream for Application errors (anyone have better wording?). Requirement: MUST be an output stream. (MOB: shouldn't this also be moved to the top level?)
- .jsgi.multithread: truthy if the Application object may be simultaneously invoked by another thread in the same process, otherwise falsey.
- .jsgi.multiprocess: truthy if an equivalent Application object may be simultaneously invoked by another process, otherwise falsey.
- .jsgi.runOnce: truthy if Server expects (but does not guarantee) that the Application will only be invoked this one time during the life of its containing process, otherwise falsey. Note: normally, this will only be true for a server based on CGI (or something similar).
- .jsgi.cgi: CGI version Array in [major, minor] form if Server is implemented atop CGI, otherwise falsey.
- .jsgi.ext: Defined in the Extensions section.
- .env: The top level of the Request object SHOULD only consist of the keys specified above. Servers and Middleware MAY add additional information the `env` key. Servers are encouraged to add any additional relevant data relating to the Request in a key in the `env` object. Requirements: MUST be an object.
Optional Keys
The Request MAY contain contain these keys:
- .authType: Corresponds to the CGI key AUTH_TYPE
- .pathTranslated: Corresponds to the CGI key PATH_TRANSLATED
- .remoteAddr: Corresponds to the CGI key REMOTE_ADDR
- .remoteHost: Corresponds to the CGI key REMOTE_HOST
- .remoteIdent: Corresponds to the CGI key REMOTE_IDENT
- .remoteUser: Corresponds to the CGI key REMOTE_USER
- .serverSoftware: Corresponds to the CGI key SERVER_SOFTWARE
(DL: added the above keys (everything spec'd by CGI) to effectively reserve them in the top level request so that we can safely extend the JSGI spec without worrying about conflicts -- is this alright?)
Response
Applications MUST return a JavaScript Object representative of an HTTP response:
status
The status MUST be a three-digit integer (RFC 2616 Section 6.1.1)
headers
MUST be a JavaScript object containing key/value pairs of Strings or Arrays. Servers SHOULD output multiple headers for `header` values supplied as Arrays. All keys MUST be lower-case. The `headers` object MUST NOT contain a `status` key and MUST NOT contain key names that end in `-` or `_`. It MUST contain keys that consist of letters, digits, `_` or `-` and start with a letter. Header values MUST NOT contain characters below 037.
- content-type: There MUST be a `content-type` header key, except when the [#status Status] is 1xx, 204 or 304, in which case `content-type MUST NOT be present.
- content-length: There MUST NOT be `content-length` header key when the Status is 1xx, 204 or 304.
body
MUST be an object which responds to `forEach` and MUST only yield objects which have a toByteString method (including Strings and Binary<ref>Binary, as defined in Binary/D (DL: or whatever binary module is ratified) objects</ref>).
If `body` responds to close, it SHOULD be called after iteration.
Conventions
To facilitate interoperability among JSGI Applications and Servers, Applications SHOULD be added as the `app` key on their module's `exports` object.
Upon initialization Servers MUST provide an object representative of every Request's `jsgi` object as a second argument to the `app` function. (AB: this needs more detail)
Extensions
This specification can be extended by JSGI Extension Proposals. Ratification of extensions occurs by the standard CommonJS process. An Extension Proposal MUST specify a key String and version Array to be placed in `request.jsgi.ext` to be ratified.
The following sections of this specification are non-normative.
Rationale
Multiple header values with the same key should now be provided as an Array value. JSGI 0.2 specified this as a string containing `\ ` but working with them as arrays is easier and should a middleware mistakenly concatenate to the header value Array's toString will do the right thing according to RFC 2616 and coerce header value into a comma-joined string.
Header casing is specified as lower-case to aid interoperability amongst middleware. While case is irrelevant according to RFC 2616 Servers MAY correct the case of Response header keys or expose this functionality to Applications.
(DL: notes about potential CGI information loss, particularly with regard to decoded paths, headers actually containing an _ and multiple headers, as well as the security implications (X_FORWARDED_FOR/x-forwarded-for)
(AB: If server on top of CGI and undecoded PATH_INFO available, we should note that servers should use it)
(DL: there should be a note about forEach for sync streaming and a promise's progressCallback for async streaming, which has been the subject of confusion. Any other areas that deserve special explanation?)
Changes from JSGI 0.2
Specification changes
- Added definitions for Middleware and Server
- Added specification for Extensions
Request changes
- Explicitly override CGI-like variables:
- Required
- env.REQUEST_METHOD => request.method
- env.SERVER_PROTOCOL => request.version, parsed "HTTP/1.1" => [1, 1]
- env.SERVER_NAME => request.host
- env.SERVER_PORT => request.port, parsed
- env.SCRIPT_NAME => request.scriptName, MUST NOT be decoded, MUST NOT end in slash
- env.PATH_INFO => request.pathInfo, MUST NOT be decoded
- env.QUERY_STRING => request.queryString
- Optional
- env.AUTH_TYPE => request.authType
- env.PATH_TRANSLATED => request.pathTranslated DL: this is duplicate data, should we forbid?
- env.REMOTE_ADDR => request.remoteAddr DL: remoteAddress?
- env.REMOTE_HOST => request.remoteHost
- env.REMOTE_IDENT => request.remoteIdent DL: remoteIdentity?
- env.REMOTE_USER => request.remoteUser
- env.SERVER_SOFTWARE => request.serverSoftware
- Forbidden
- env.CONTENT_TYPE: exists in request.jsgi.headers["content-type"] if applicable
- env.CONTENT_LENGTH: exists in request.jsgi.headers["content-length"] if applicable
- env.GATEWAY_INTERFACE: exists in request.jsgi.cgi if applicable
- Allow request.headers["content-length"] and request.headers["content-type"] and disallow env.CONTENT_LENGTH and env.CONTENT_TYPE
- Add request.env object for all non-specified keys
- Move env["jsgi.url_scheme"] to request.scheme
- Move env["jsgi.input"] to request.input
request.jsgi
- Move env["jsgi.*"] to request.jsgi.*
- Added request.jsgi.ext required object
- Move request.jsgi.run_once to request.jsgi.runOnce (DL: Mike Wilson suggested this be camelCase like everything else and noone objected -- speak up if you don't like)
- Server SHOULD add request.jsgi.async key if Server supports returning response as promise
- Add request.jsgi.cgi key if Server sits atop CGI
- Note CGI security deficiencies, e.g. X_FORWARDED_FOR/x-forwarded-for
- Bump request.jsgi.version to [0,3]
request.headers
- Move env["HTTP_*"] to request.headers.* (eliminating the "HTTP_" prefix)
- Header keys MUST be lowercase but otherwise SHOULD be equivelent to the HTTP request headers
- Compliant Servers MUST unmangled header keys if CGI (replace(/_/g, "-"))
- Servers MAY provide request header values as Array of Strings if HTTP request contains multiple headers with the same key (DL: should this be SHOULD? It's impossible with CGI -- any ideas?)
request.env
- All custom keys added to the request by Servers or Middleware MUST be placed in request.env
response
- Specify status, headers and body keys MUST be present in Response OR Response must contain a then function that, when resolved, returns an object with all three keys
- Middleware SHOULD preserve additional keys supplied in response object if not intending to supersede it
response.status
- No changes
response.headers
- The header MUST be a JavaScript Object containing values which are Objects that respond to toString
- Keys MUST be in lower-case form
- If a header value responds to a forEach method Server SHOULD provide multiple HTTP headers with values corresponding to toString
response.body
- If the Body responds to close it MUST be called after iteration with the same arguments passed to forEach
Conventions
- Applications: SHOULD be added as the `app` key on their module's `exports` object for interoperability.
- Servers: To allow Applications on Servers with unsupported features to fail fast servers must now supply the contents of the `jsgi` key as a second argument to the Application.
Examples
Request
{ version: [1, 1], // parsed HTTP version ~ "HTTP/1.1" method: "GET", headers: { // HTTP headers, lowercased but otherwise unmangled host: "jackjs.org", "user-agent": "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3", accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "accept-language": "en-us,en;q=0.5", "accept-encoding": "gzip,deflate", "accept-charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", "keep-alive": "300", connection: "keep-alive", "if-modified-since": "Fri, 04 Sep 2009 07:47:22 GMT", "cache-control": "max-age=0" }, input: (new InputStream), // request body stream scheme: "http", host: "jackjs.org", port: 80, env: { // this is the place where Server and Middleware put keys juice: { version: [0, 1] }, jack: { request: null } }, jsgi: { version: [0, 3], errors: (new OutputStream), multithread: false, multiprocess: true, runOnce: false, async: true, cgi: false // [1, 1] if CGI/1.1 } }
(DL: I'd like to have at least a simple response and Async Extension promise response example)
Acknowledgements
This specification is adapted from the Rack specification written by Christian Neukirchen. Some parts of this specification are adopted from PEP333: Python Web Server Gateway Interface v1.0. Special thanks to the many contributors of the CommonJS group.