Documentation ¶
Overview ¶
Package jstream defines and implements the JSON Stream protocol
Purpose ¶
The purpose of the jstream protocol is to provide a very simple layer on top of an existing JSON implementation to allow for streaming arbitrary numbers of JSON objects and byte blobs of arbitrary size in a standard way, and to allow for embedding streams within each other.
The order of priorities when designing jstream is as follows:
- Protocol simplicity
- Implementation simplicity
- Efficiency, both in parsing speed and bandwidth
The justification for this is that protocol simplicity generally spills into implementation simplicity anyway, and accounts for future languages which have different properties than current ones. Parsing speed isn't much of a concern when reading data off a network (the primary use-case here), as RTT is always going to be the main blocker. Bandwidth can be a concern, but it's one better solved by wrapping the byte stream with a compressor.
jstream protocol
The jstream protocol is carried over a byte stream (in go: an io.Reader). To read the protocol a JSON object is read off the byte stream and inspected to determine what kind of jstream element it is.
Multiple jstream elements are sequentially read off the same byte stream. Each element may be separated from the other by any amount of whitespace, with whitespace being defined as spaces, tabs, carriage returns, and/or newlines.
jstream elements
There are three jstream element types:
- JSON Value: Any JSON value
- Byte Blob: A stream of bytes of unknown, and possibly infinite, size
- Stream: A heterogenous sequence of jstream elements of unknown, and possibly infinite, size
JSON Value elements are defined as being JSON objects with a `val` field. The value of that field is the JSON Value.
{ "val":{"foo":"bar"} }
Byte Blob elements are defined as being a JSON object with a `bytesStart` field with a value of `true`. Immediately following the JSON object are the bytes which are the Byte Blob, encoded using standard base64. Immediately following the encoded bytes is the character `$`, to indicate the bytes have been completely written. Alternatively the character `!` may be written immediately after the bytes to indicate writing was canceled prematurely by the writer.
{ "bytesStart":true }wXnxQHgUO8g=$ { "bytesStart":true }WGYcTI8=!
The JSON object may also contain a `sizeHint` field, which gives the estimated number of bytes in the Byte Blob (excluding the trailing delimiter). The hint is neither required to exist or be accurate if it does. The trailing delimeter (`$` or `!`) is required to be sent even if the hint is sent.
Stream elements are defined as being a JSON object with a `streamStart` field with a value of `true`. Immediately following the JSON object will be zero more jstream elements of any type, possibly separated by whitespace. Finally the Stream is ended with another JSON object with a `streamEnd` field with a value of `true`.
{ "streamStart":true } { "val":{"foo":"bar"} } { "bytesStart":true }7TdlDQOnA6isxD9C$ { "streamEnd":true }
A Stream may also be prematurely canceled by the sending of a JSON object with the `streamCancel` field set to `true` (in place of one with `streamEnd` set to `true`).
The Stream's original JSON object (the "head") may also have a `sizeHint` field, which gives the estimated number of jstream elements in the Stream. The hint is neither required to exist or be accurate if it does. The tail JSON object (with the `streamEnd` field) is required even if `sizeHint` is given.
One of the elements in a Stream may itself be a Stream. In this way Streams may be embedded within each other.
Here's an example of a complex Stream, which carries within it two different streams and some other elements:
{ "streamStart":true } { "val":{"foo":"bar" } { "streamStart":true, "sizeHint":2 } { "val":{"foo":"baz"} } { "val":{"foo":"biz"} } { "streamEnd":true } { "bytesStart":true }X7KCpLIjqIBJt9vA$ { "streamStart":true } { "bytesStart":true }0jT+kNCuxHywUYy0$ { "bytesStart":true }LUqjR6OACB2p1BG4$ { "streamEnd":true } { "streamEnd":true }
Finally, the byte stream off of which the jstream is based (i.e. the io.Reader) is implicitly treated as a Stream, with the Stream ending when the byte stream is closed.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrCanceled is returned when reading either a Byte Blob or a Stream, // indicating that the writer has prematurely canceled the element. ErrCanceled = errors.New("canceled by writer") // ErrStreamEnded is returned from Next when the Stream being read has been // ended by the writer. ErrStreamEnded = errors.New("stream ended") )
Functions ¶
This section is empty.
Types ¶
type Element ¶
type Element struct { // Err will be set if the StreamReader encountered an error while reading // the next Element. If set then the Element is otherwise unusable. // // Err may be ErrCanceled or ErrStreamEnded, which would indicate the end of // the stream but would not indicate the StreamReader is no longer usable, // depending on the behavior of the writer on the other end. Err error // contains filtered or unexported fields }
Element is a single jstream element which is read off a StreamReader.
If a method is called which expects a particular Element type (e.g. DecodeValue, which expects a JSONValue Element) but the Element is not of that type then an ErrWrongType will be returned.
If there was an error reading the Element off the StreamReader that error is kept in the Element and returned from any method call.
func (Element) Bytes ¶
Bytes returns an io.Reader which will contain the contents of a ByteBlob element. The io.Reader _must_ be read till io.EOF or ErrCanceled before the StreamReader may be used again.
This method should not be called more than once.
func (Element) SizeHint ¶
SizeHint returns the size hint which may have been optionally sent for ByteBlob and Stream elements, or zero. The hint is never required to be sent or to be accurate.
func (Element) Stream ¶
func (el Element) Stream() (*StreamReader, error)
Stream returns the embedded stream represented by this Element as a StreamReader. The returned StreamReader _must_ be iterated (via the Next method) till ErrStreamEnded or ErrCanceled is returned before the original StreamReader may be used again.
This method should not be called more than once.
type ErrWrongType ¶
type ErrWrongType struct {
Actual Type
}
ErrWrongType is an error returned by the Decode* methods on Decoder when the wrong decoding method has been called for the element which was read. The error contains the actual type of the element.
func (ErrWrongType) Error ¶
func (err ErrWrongType) Error() string
type StreamReader ¶
type StreamReader struct {
// contains filtered or unexported fields
}
StreamReader represents a Stream from which Elements may be read using the Next method.
func NewStreamReader ¶
func NewStreamReader(r io.Reader) *StreamReader
NewStreamReader takes an io.Reader and interprets it as a jstream Stream.
func (*StreamReader) Next ¶
func (sr *StreamReader) Next() Element
Next reads, decodes, and returns the next Element off the StreamReader. If the Element is a ByteBlob or embedded Stream then it _must_ be fully consumed before Next is called on this StreamReader again.
The returned Element's Err field will be ErrStreamEnd if the Stream was ended, or ErrCanceled if it was canceled, and this StreamReader should not be used again in those cases.
If the underlying io.Reader is closed the returned Err field will be io.EOF.
type StreamWriter ¶
type StreamWriter struct {
// contains filtered or unexported fields
}
StreamWriter represents a Stream to which Elements may be written using any of the Encode methods.
func NewStreamWriter ¶
func NewStreamWriter(w io.Writer) *StreamWriter
NewStreamWriter takes an io.Writer and returns a StreamWriter which will write to it.
func (*StreamWriter) EncodeBytes ¶
func (sw *StreamWriter) EncodeBytes(sizeHint uint, r io.Reader) error
EncodeBytes copies the given io.Reader, until io.EOF, onto the Stream as a ByteBlob element. This method will block until copying is completed or an error is encountered.
If the io.Reader returns any error which isn't io.EOF then the ByteBlob is canceled and that error is returned from this method. Otherwise nil is returned.
sizeHint may be given if it's known or can be guessed how many bytes the io.Reader will read out.
func (*StreamWriter) EncodeStream ¶
func (sw *StreamWriter) EncodeStream(sizeHint uint, fn func(*StreamWriter) error) error
EncodeStream encodes an embedded Stream element onto the Stream. The callback is given a new StreamWriter which represents the embedded Stream and to which any elemens may be written. This methods blocks until the callback has returned.
If the callback returns nil the Stream is ended normally. If it returns anything else the embedded Stream is canceled and that error is returned from this method.
sizeHint may be given if it's known or can be guessed how many elements will be in the embedded Stream.
func (*StreamWriter) EncodeValue ¶
func (sw *StreamWriter) EncodeValue(i interface{}) error
EncodeValue marshals the given value and writes it to the Stream as a JSONValue element.