jstream

package
v0.0.0-...-c20f884 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2019 License: MIT Imports: 6 Imported by: 0

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:

  1. Protocol simplicity
  2. Implementation simplicity
  3. 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

View Source
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

func (el Element) Bytes() (io.Reader, error)

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

func (el Element) SizeHint() uint

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.

func (Element) Type

func (el Element) Type() (Type, error)

Type returns the Element's Type, or an error

func (Element) Value

func (el Element) Value(i interface{}) error

Value attempts to unmarshal a JSON Value Element's value into the given receiver.

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.

type Type

type Type string

Type is used to enumerate the types of jstream elements

const (
	TypeJSONValue Type = "jsonValue"
	TypeByteBlob  Type = "byteBlob"
	TypeStream    Type = "stream"
)

The jstream element types

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL