Abstract
Chunga implements streams capable of chunked encoding on demand as defined in RFC 2616. For an example of how these streams can be used see Drakma.The library needs a Common Lisp implementation that supports Gray streams and relies on David Lichteblau's trivial-gray-streams to offer portability between different Lisps.
Chunga is currently not optimized towards performance - it is rather intended to be easy to use and (if possible) to behave correctly.
The code comes with a BSD-style license so you can basically do with it whatever you want.
Download current version or visit the project on Github.
with-character-stream-semantics
read-line*
read-http-headers
token-char-p
read-token
read-name-value-pair
read-name-value-pairs
assert-char
skip-whitespace
read-char*
peek-char*
trim-whitespace
*current-error-message*
*accept-bogus-eols*
*treat-semicolon-as-continuation*
as-keyword
as-capitalized-string
The esieast way to install Chunga is with Quicklisp
The current development version of Chunga can be found
at https://github.com/edicl/chunga.
MAKE-CHUNKED-STREAM
which
takes an open binary stream (called the underlying stream) as its single argument.
A binary stream in this context means that if it's an input
stream, you can
apply READ-SEQUENCE
to it where the sequence is an array of element
type OCTET
, and similarly for WRITE-SEQUENCE
and output streams. (Note that this specifically holds for bivalent streams like socket streams.)
A chunked stream behaves like an ordinary Lisp stream
of element
type OCTET
with the addition that you can turn chunking on and off for
input as well as for output. With chunking turned on, data is read or
written according to
the definition in RFC
2616.
[Standard class]
chunked-stream
Every chunked stream returned byMAKE-CHUNKED-STREAM
is of this type which is a subtype ofSTREAM
.
[Standard class]
chunked-input-stream
A chunked stream is of this type if its underlying stream is an input stream. This is a subtype ofCHUNKED-STREAM
.
[Standard class]
chunked-output-stream
A chunked stream is of this type if its underlying stream is an output stream. This is a subtype ofCHUNKED-STREAM
.
[Standard class]
chunked-io-stream
A chunked stream is of this type if it is both aCHUNKED-INPUT-STREAM
as well as aCHUNKED-OUTPUT-STREAM
.
[Function]
make-chunked-stream stream => chunked-stream
Creates and returns a chunked stream (a stream of typeCHUNKED-STREAM
) which wrapsstream
.stream
must be an open binary stream.
[Specialized reader]
chunked-stream-stream (stream chunked-stream) => underlying-stream
Returns the underlying stream of the chunked streamstream
.
[Generic reader]
chunked-stream-input-chunking-p object => generalized-boolean
Returns a true value ifobject
is of typeCHUNKED-INPUT-STREAM
and if input chunking is currently enabled.
[Specialized writer]
(setf (chunked-stream-input-chunking-p (stream chunked-input-stream)) new-value)
This function is used to switch input chunking onstream
on or off. Note that input chunking will usally be turned off automatically when the last chunk is read.
[Generic reader]
chunked-stream-output-chunking-p object => generalized-boolean
Returns a true value ifobject
is of typeCHUNKED-OUTPUT-STREAM
and if output chunking is currently enabled.
[Specialized writer]
(setf (chunked-stream-output-chunking-p (stream chunked-output-stream)) new-value)
This function is used to switch output chunking onstream
on or off.
[Specialized reader]
chunked-input-stream-extensions (stream chunked-input-stream) => extensions
Returns an alist of attribute/value pairs corresponding to the optional "chunk extensions" which might have been encountered when reading fromstream
.
[Specialized reader]
chunked-input-stream-trailers (stream chunked-input-stream) => trailers
Returns the optional "trailer" HTTP headers which might have been sent after the last chunk, i.e. directly before input chunking ended onstream
. The format oftrailers
is identical to that returned byREAD-HTTP-HEADERS
.
[Condition]
chunga-condition
All conditions signalled by Chunga are of this type. This is a subtype ofCONDITION
.
[Error]
chunga-error
All errors signalled by Chunga are of this type. This is a subtype ofCHUNGA-CONDITION
and ofSTREAM-ERROR
, soSTREAM-ERROR-STREAM
can be used to access the offending stream.
[Warning]
chunga-warning
All warnings signalled by Chunga are of this type. This is a subtype ofCHUNGA-CONDITION
and ofWARNING
.
[Error]
syntax-error
An error of this type is signalled if Chunga encounters wrong or unknown syntax when reading data. This is a subtype ofCHUNGA-ERROR
.
[Error]
parameter-error
An error of this type is signalled if a function was called with inconsistent or illegal parameters. This is a subtype ofCHUNGA-ERROR
.
[Condition type]
input-chunking-body-corrupted
A condition of this type is signaled if an unexpected character (octet) is read while reading from a chunked stream with input chunking enabled. This is a subtype ofCHUNGA-ERROR
.
[Condition type]
input-chunking-unexpected-end-of-file
A condition of this type is signaled if we reach an unexpected EOF on a chunked stream with input chunking enabled. This is a subtype ofCHUNGA-ERROR
.
Note that all of these functions are designed to work
on binary
streams, specifically on streams with element
type (UNSIGNED-BYTE 8)
. They will not work
with character streams. (But the "bivalent" streams offered by many
Lisp implementations will do.) They must be called within the context
of WITH-CHARACTER-STREAM-SEMANTICS
.
[Macro]
with-character-stream-semantics statement* => result*
Executes thestatement*
forms in such a way that functions within this section can read characters from binary streams (treating octets as the Latin-1 characters with the corresponding code points). All the functions below must be wrapped with this macro. If your code uses several of these functions which interact on the same stream, all of them must be wrapped with the same macro. See the source code of Drakma or Hunchentoot for examples of how to use this macro.
[Function]
read-line* stream &optional log-stream => line
Reads and assembles characters from the binary streamstream
until a carriage return is read. Makes sure that the following character is a linefeed. If*ACCEPT-BOGUS-EOLS*
is notNIL
, then the function will also accept a lone carriage return or linefeed as a line break. Returns the string of characters read excluding the line break. Additionally logs this string tolog-stream
if it is notNIL
.
[Function]
read-http-headers stream &optional log-stream => headers
Reads HTTP header lines from the binary streamstream
(except for the initial status line which is supposed to be read already) and returns a corresponding alist of names and values where the names are keywords and the values are strings. Multiple lines with the same name are combined into one value, the individual values separated by commas. Header lines which are spread across multiple lines are recognized and treated correctly. (But see*TREAT-SEMICOLON-AS-CONTINUATION*
.) Additonally logs the header lines tolog-stream
if it is notNIL
.
[Function]
read-token stream => token
Read characters from the binary streamstream
while they are token constituents (according to RFC 2616). It is assumed that there's a token character at the current position. The token read is returned as a string. Doesn't signal an error (but simply stops reading) ifEND-OF-FILE
is encountered after the first character.
[Function]
token-char-p char => generalized-boolean
Returns a true value if the Lisp characterchar
is a token constituent according to RFC 2616.
[Function]
read-name-value-pair stream &key value-required-p cookie-syntax => pair
Reads a typical (in RFC 2616) name/value or attribute/value combination from the binary streamstream
- a token followed by a#\=
character and another token or a quoted string. Returns a cons of the name and the value, both as strings. Ifvalue-required-p
isNIL
(the default isT
), the#\=
sign and the value are optional. Ifcookie-syntax
is true (the default isNIL
), the value is read like the value of a cookie header.
[Function]
read-name-value-pairs stream &key value-required-p cookie-syntax => pairs
UsesREAD-NAME-VALUE-PAIR
to read and return an alist of name/value pairs from the binary streamstream
. It is assumed that the pairs are separated by semicolons and that the first char read (except for whitespace) will be a semicolon. The parameters are used as inREAD-NAME-VALUE-PAIR
. Stops reading in case ofEND-OF-FILE
(instead of signaling an error).
[Function]
assert-char stream expected-char => char
Reads the next character from the binary streamstream
and checks if it is the characterexpected-char
. Signals an error otherwise.
[Function]
skip-whitespace stream => char-or-nil
Consume characters from the binary streamstream
until anEND-OF-FILE
is encountered or a non-whitespace (according to RFC 2616) characters is seen. This character is returned (orNIL
in case ofEND-OF-FILE
).
[Function]
read-char* stream => char
Reads and returns the next character from the binary streamstream
.
[Function]
peek-char* stream &optional eof-error-p eof-value => boolean
Returns a true value if a character can be read from the binary streamstream
. Ifeof-error-p
has a true value, an error is signalled if no character remains to be read.eof-value
specifies the value to return ifeof-error-p
is false and the end of the file has been reached.
[Function]
trim-whitespace string &key start end => string'
Returns a version of the stringstring
(betweenstart
andend
) where spaces and tab characters are trimmed from the start and the end.
[Special variable]
*current-error-message*
Used by the parsing functions in this section as an introduction to a standardized error message. Should be bound to a string orNIL
if one of these functions is called.
[Special variable]
*accept-bogus-eols*
Some web servers do not respond with a correct CRLF line ending for HTTP headers but with a lone linefeed or carriage return instead. If this variable is bound to a true value,READ-LINE*
will treat a lone LF or CR character as an acceptable end of line. The initial value isNIL
.
[Special variable]
*treat-semicolon-as-continuation*
According to John Foderaro, Netscape v3 web servers bogusly splitSet-Cookie
headers over multiple lines which means that we'd have to treatSet-Cookie
headers ending with a semicolon as incomplete and combine them with the next header. This will only be done if this variable has a true value, though. Its default value isNIL
.
[Function]
as-keyword string &key destructivep => keyword
Converts the stringstring
to a keyword where all characters are uppercase or lowercase, taking into account the current readtable case. Might destructively modifystring
ifdestructivep
is true which is the default. "Knows" several HTTP header names and methods and is optimized to not callINTERN
for these.
[Function]
as-capitalized-string keyword => capitalized-string
Kind of the inverse ofAS-KEYWORD
. Has essentially the same effect asSTRING-CAPITALIZE
but is optimized for "known" keywords like:CONTENT-LENGTH
or:GET
.
Thanks to Jochen Schmidt's chunking code in ACL-COMPAT for inspiration. This documentation was prepared with DOCUMENTATION-TEMPLATE.