Like JSGI, but using streams for the request bodies.
An EJSGI Application is a JavaScript Function which takes exactly one argument, the Request as a JavaScript Object, and returns its Response as EITHER (a) a JavaScript Object with data relevant to the HTTP response, OR (b) a Promise which resolves to said object.
EJSGI Middleware are EJSGI applications that can call other EJSGI applications. Middleware can be stacked up into a call chain to provide useful services to Requests and Responses.
An EJSGI Server is the glue that connects EJSGI Applications to HTTP request and response messages.
The reference implementation is built on nodejs.
A promise is an object that exposes an addCallback
method. addCallback
takes a single function argument, which is called with the appropriate arguments when they're ready. For the purpose of EJSGI, this is the only required functionality, but servers MAY add additional features.
(IZS Maybe apps should just take a callback, and then we don't have to talk about promises at all? It'd make post-app middleware a lot simpler.)
Stream Objects are representations of a stream of data in an asynchronous evented paradigm.
Stream Objects have the following methods:
- write - Send data down the stream. If the stream is closed, then throw an Error. This must not actually perform the write until after the current scope of execution is completed. The order of written data MUST be consistent with the order in which
write
is called. Returnstrue
if the data could be written to the output stream immediately, orfalse
if the data was buffered for writing later. If it returnsfalse
, then the caller can attach a listener to thedrain
event to know when it's ready to receive more data. - close - Close the stream. Once closed, no more data may be written to the Stream.
- pause - Temporarily prevent the firing of
data
events. This is useful when a reader needs to throttle a stream of incoming data. - resume - Resume a paused thread, so that
data
events will begin firing again. - addListener - Attach an event handler to an event. The first argument is the event name, and the second is the callback.
Stream Objects emit the following events
- data - All the data that is passed through
write
eventually triggers adata
event. The argument is the data that was written. - end - Emitted when all data has been written, and the stream is closed.
- drain - Emitted when the internal buffer empties after a buffered
write()
call. - pause - Emitted when the stream is paused with the
pause
method. - resume - Emitted when the stream is resumed with the
resume
method.
Stream objects MUST implement some sort of "event queue" in order to defer callbacks until after the current execution context has exited. Specifically, the data
, end
, and drain
events MUST NOT be fired immediately on the corresponding calls to write()
, close()
, and resume()
.
All streams SHOULD be both readable and writeable. This allows for middleware to step into the flow and filter the input or output to/from an app. Of course, the underlying input and output streams may be read- or write-only.
The request environment MUST be an Object representative of an HTTP request. Applications are free to modify the Request.
The Request is required to have 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.
- url - The URL on the first line of the request. This MUST be exactly the requested URL as it appears on the first line of the HTTP request, without any modifications or corrections. (Note that it generally will not contain the hostname.)
- 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 withscheme
,port
,scriptName
,pathInfo
, andqueryString
this variable can be used to complete the URL. If not found in the request line, then theHOST
header, can be used. If not found in theHOST
header. - 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 URLport
can be derived from thescheme
. Restriction: MUST NOT be undefined and MUST be an integer. - scheme - URL scheme (per RFC 1738). "http", "https", etc.
- 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 theheaders
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.
- 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 theenv
object. Requirements: MUST be an object. - input - The input Stream. Requirements: MUST be a Stream Object.
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
The Request body object is required to emit the following events at the appropriate time, in this order:
- data - Fired as each chunk of the body is uploaded. Argument: the data that has been uploaded in this chunk
- end - Fired when the entire body has been uploaded, and the request is completed. Argument: none.
The app may call the following methods on the request body:
- pause - Pause the incoming stream of data.
- resume - Resume the incoming stream.
Applications MUST return a JavaScript object.
The Response is required to have these fields:
- 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. Theheaders
object MUST NOT contain astatus
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. Additionally:- content-type - There MUST be a
content-type
header key, except when the Status is 1xx, 204 or 3xx, 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 3xx.
- content-type - There MUST be a
- body - A Stream representing the response body.
The application should call the following methods on the response body stream:
- write - To send data to the client, call the
write
method, passing the data as an argument. This is optional; in cases where a response body is not appropriate, don't do it. - close - To complete the request,
close
the output stream. This signals the end of the connection. This event is optional; in cases where it is not appropriate to terminate the connection (such as long-polling comet connections), do not call it.
- Body encoding needs to be sorted out. Right now, it's just sending everything binary, and that's not ideal for UTF-8 text.
- Use a vendor/submodule pattern for the stream lib.
- Figure out how to merge this into JSGI proper.
- Port Jack's middleware (not here, but into a separate project which will be forthcoming)
0.0.4
- Add the return value fromwrite()
, clarify the semantics ofdrain
, renameeof
toend
.0.0.3
- More JSGI compliance. At this point, it's ready to be written up as an extension.0.0.2
- Updated to use Streams instead of direct Emitters. (Makes the name make less sense, but the code make more sense.)0.0.1
- Initial pass.