The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Protocol::SPDY::Stream - single stream representation within a Protocol::SPDY connection

VERSION

version 1.001

SYNOPSIS

 # You'd likely be using a subclass or other container here instead
 my $spdy = Protocol::SPDY->new;
 # Create initial stream - this example is for an HTTP request
 my $stream = $spdy->create_frame(
   # 0 is the default, use 1 if you don't want anything back from the
   # other side, for example server push
   unidirectional => 0,
   # Set to 1 if we're not expecting to send any further frames on this stream
   # - a GET request with no additional headers for example
   fin => 0,
   # Normally headers are provided as an arrayref to preserve order,
   # but for convenience you could use a hashref instead
   headers => [
     ':method'  => 'PUT',
     ':path:'   => '/some/path?some=param',
     ':version' => 'HTTP/1.1',
     ':host'    => 'localhost:1234',
     ':scheme'  => 'https',
   ]
 );
 # Update the headers - regular HTTP allows trailing headers, with SPDY
 # you can send additional headers at any time
 $stream->headers(
   # There's more to come
   fin => 0,
   # Again, arrayref or hashref are allowed here
   headers => [
     'content-length' => 5,
   ]
 );
 # Normally scalar (byte) data here, although scalar ref (\'something')
 # and Future are also allowed
 $stream->send_data('hello');
 # as a scalar ref:
 # $stream->send_data(\(my $buffer = "some data"));
 # as a Future:
 # $stream->send_data(my $f = Future->new);
 # $f->done('the data you expected');
 # If you want to cancel the stream at any time, use ->reset
 $stream->reset('CANCEL'); # or STREAM_CANCEL if you've imported the constants
 # Normally you'd indicate finished by marking a data packet as the final one:
 $stream->send_data('</html>', fin => 1);
 # ... and an empty data packet should also be fine:
 # $stream->send_data('', fin => 1);

DESCRIPTION

HTTP semantics

Each stream corresponds to a single HTTP request/response exchange. The request is contained within the SYN_STREAM frame, with optional additional HEADERS after the initial stream creation, and the response will be in the SYN_REPLY, which must at least include the :status and :version headers (so the SYN_REPLY must contain the 200 OK response, you can't send that in a later HEADERS packet).

Window handling

Each outgoing data frame will decrement the window size; a data frame can only be sent if the data length is less than or equal to the remaining window size. Sending will thus be paused if the window size is insufficient; note that it may be possible for the window size to be less than zero.

* Each frame we receive and process will trigger a window update response. This applies to data frames only; windowing does not apply to control frames. If we have several frames queued up for processing, we will defer the window update until we know the total buffer space freed by processing those frames. * Each data frame we send will cause an equivalent reduction in our window size

* Extract all frames from buffer * For each frame: * If we have a stream ID for the frame, pass it to that stream * Stream processing for new data * Calculate total from all new data frames * Send window update if required

Error handling

There are two main types of error case: stream-level errors, which can be handled by closing that stream, or connection-level errors, where things have gone so badly wrong that the entire connection needs to be dropped.

Stream-level errors are handled by RST_STREAM frames.

Connection-level errors are typically cases where framing has gone out of sync (compression failures, incorrect packet lengths, etc.) and these are handled by sending a single GOAWAY frame then closing the connection immediately.

Server push support

The server can push additional streams to the client to avoid the unnecessary extra SYN_STREAM request/response cycle for additional resources that the server knows will be needed to fulfull the main request.

A server push response is requested with "push_stream" - this example involves a single associated stream:

 try {
   my $assoc = $stream->push_stream;
   $assoc->closed->on_ready(sub {
     # Associated stream completed or failed - either way,
         # we can now start sending the main data
         $stream->send_data($html);
   })->on_fail(sub {
     # The other side might already have the data or not
         # support server push, so don't panic if our associated
         # stream closes before we expected it
     warn "Associated stream was rejected";
   });
 } catch {
   # We'll get an exception if we tried to push data on a stream
   # we'd already marked as FIN on our side.
   warn "Our code is broken";
   $stream->connection->goaway;
 };

You can then send that stream using "start" as usual:

 $assoc->start(
   headers => {
     ':scheme' => 'https',
     ':host'   => 'localhost',
     ':path'   => '/image/logo.png',
   }
 );

Note that associated streams can only be initiated before the main stream is in FIN state.

Generally it's safest to create all the associated streams immediately after the initial SYN_STREAM request has been received from the client, since that will pass enough information back that the client will know how to start arranging the responses for caching. You should then be able to send data on the streams as and when it becomes available. The Future needs_all method may be useful here.

Attempting to initiate server-pushed streams after sending content is liable to hit race conditions - see section 3.3.1 in the SPDY spec.

METHODS

new

Instantiates a new stream. Expects the following named parameters:

  • connection - the Protocol::SPDY::Base subclass which is managing this side of the connection

  • stream_id - the ID to use for this stream

  • version - SPDY version, usually 3

new_from_syn

Constructs a new instance from a Protocol::SPDY::Frame::Control::SYN_STREAM frame object.

update_received_headers_from

Updates "received_headers" from the given frame.

from_us

Returns true if we initiated this stream.

id

Returns the ID for this stream.

seen_reply

Returns true if we have seen a reply for this stream yet.

connection

Returns the Protocol::SPDY::Base instance which owns us.

priority

Returns the priority for this stream (0-7).

version

Returns the SPDY version for this stream (probably 3).

syn_frame

Generates a SYN_STREAM frame for starting this stream.

sent_header

Returns the given header from our recorded list of sent headers

sent_headers

Returns the hashref of all sent headers. Please don't change the value, it might break something: changing this will not send any updates to the other side.

received_header

Returns the given header from our recorded list of received headers.

received_headers

Returns the hashref of all received headers.

handle_frame

Attempt to handle the given frame.

send_window_update

Send out any pending window updates.

queue_window_update

Request a window update due to data frame processing.

queue_frame

Asks our connection object to queue the given frame instance.

start

Start this stream off by sending a SYN_STREAM frame.

reply

Sends a reply to the stream instantiation request.

reset

Sends a reset request for this frame.

push_stream

Creates and returns a new server push stream.

Note that a pushed stream starts with a SYN_STREAM frame but with headers that are usually found in a SYN_REPLY frame.

headers

Send out headers for this frame.

window_update

Update information on the current window progress.

send_data

Sends a data packet.

METHODS - Accessors

These provide read-only access to various pieces of state information.

associated_stream_id

Which stream we're associated to. Returns 0 if there isn't one.

associated_stream

The Protocol::SPDY::Stream for the associated stream (the "parent" stream to this one, if it exists). Returns undef if not found.

remote_fin

Returns true if the remote has sent us a FIN (half-closed state).

local_fin

Returns true if we have sent FIN to the remote (half-closed state).

initial_window_size

Initial window size. Default is 64KB for a new stream.

transfer_window

Remaining bytes in the current transfer window.

to_string

String representation of this stream, for debugging.

METHODS - Futures

The following Future-returning methods are available. Attach events using on_done, on_fail or on_cancel or helpers such as then as usual:

 $stream->replied->then(sub {
   # This also returns a Future, allowing chaining
   $stream->send_data('...')
 })->on_fail(sub {
   die 'here';
 });

or from the server side:

 $stream->closed->then(sub {
   # cleanup here after the stream goes away
 })->on_fail(sub {
   die "Our stream was reset from the other side: " . shift;
 });

replied

We have received a SYN_REPLY from the other side. If the stream is reset before that happens, this will be cancelled with the reason as the first parameter.

finished

This frame has finished sending everything, i.e. we've set the FIN flag on a packet. The difference between this and "closed" is that the other side may have more to say. Will be cancelled with the reason on reset.

remote_finished

This frame has had all the data it's going to get from the other side, i.e. we're sending unidirectional data or we have seen the FIN flag on an incoming packet.

closed

The stream has been closed on both sides - either through reset or "natural causes". Might still be cancelled if the parent object disappears.

accepted

The remote accepted this stream immediately after our initial SYN_STREAM. If you want notification on rejection, use an ->on_fail handler on this method.

EVENTS

The following events may be raised by this class - use "subscribe_to_event" in Mixin::Event::Dispatch to watch for them:

 $stream->subscribe_to_event(
   push => sub {
     my ($ev, $stream) = @_;
         print "Server push: received new stream $stream\n";
   }
 );

push event

Called when we have received a new stream from the other side with an associated stream. This currently means the server is pre-emptively sending data to us, see "Server push support". Will be passed the new Protocol::SPDY::Stream instance.

data event

This will be called whenever we receive data from the other side. Will be passed the data payload as a scalar.

transfer_window event

The remote has sent us a WINDOW_UPDATE packet which means we have just updated our transfer window. Will be called with the new transfer window size and the delta in bytes.

headers event

New headers have been received on this stream. Will be called with the Protocol::SPDY::Frame::Control::HEADERS containing the header information.

COMPONENTS

Further documentation can be found in the following modules:

INHERITED METHODS

Mixin::Event::Dispatch

add_handler_for_event, clear_event_handlers, event_handlers, invoke_event, subscribe_to_event, unsubscribe_from_event

AUTHOR

Tom Molesworth <cpan@perlsite.co.uk>

LICENSE

Copyright Tom Molesworth 2011-2015. Licensed under the same terms as Perl itself.