Protocol::SPDY::Stream - single stream representation within a Protocol::SPDY connection
version 1.001
# 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);
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).
:status
:version
200 OK
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
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.
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.
needs_all
Attempting to initiate server-pushed streams after sending content is liable to hit race conditions - see section 3.3.1 in the SPDY spec.
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
Constructs a new instance from a Protocol::SPDY::Frame::Control::SYN_STREAM frame object.
Updates "received_headers" from the given frame.
Returns true if we initiated this stream.
Returns the ID for this stream.
Returns true if we have seen a reply for this stream yet.
Returns the Protocol::SPDY::Base instance which owns us.
Returns the priority for this stream (0-7).
Returns the SPDY version for this stream (probably 3).
Generates a SYN_STREAM frame for starting this stream.
Returns the given header from our recorded list of 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.
Returns the given header from our recorded list of received headers.
Returns the hashref of all received headers.
Attempt to handle the given frame.
Send out any pending window updates.
Request a window update due to data frame processing.
Asks our connection object to queue the given frame instance.
Start this stream off by sending a SYN_STREAM frame.
Sends a reply to the stream instantiation request.
Sends a reset request for this frame.
Creates and returns a new server push stream.
server push
Note that a pushed stream starts with a SYN_STREAM frame but with headers that are usually found in a SYN_REPLY frame.
Send out headers for this frame.
Update information on the current window progress.
Sends a data packet.
These provide read-only access to various pieces of state information.
Which stream we're associated to. Returns 0 if there isn't one.
The Protocol::SPDY::Stream for the associated stream (the "parent" stream to this one, if it exists). Returns undef if not found.
Returns true if the remote has sent us a FIN (half-closed state).
Returns true if we have sent FIN to the remote (half-closed state).
Initial window size. Default is 64KB for a new stream.
Remaining bytes in the current transfer window.
String representation of this stream, for debugging.
The following Future-returning methods are available. Attach events using on_done, on_fail or on_cancel or helpers such as then as usual:
on_done
on_fail
on_cancel
then
$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; });
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.
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.
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.
The stream has been closed on both sides - either through reset or "natural causes". Might still be cancelled if the parent object disappears.
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.
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"; } );
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.
This will be called whenever we receive data from the other side. Will be passed the data payload as a scalar.
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.
New headers have been received on this stream. Will be called with the Protocol::SPDY::Frame::Control::HEADERS containing the header information.
Further documentation can be found in the following modules:
Protocol::SPDY - top-level protocol object
Protocol::SPDY::Frame - generic frame class
Protocol::SPDY::Frame::Control - specific subclass for control frames
Protocol::SPDY::Frame::Data - specific subclass for data frames
add_handler_for_event, clear_event_handlers, event_handlers, invoke_event, subscribe_to_event, unsubscribe_from_event
Tom Molesworth <cpan@perlsite.co.uk>
Copyright Tom Molesworth 2011-2015. Licensed under the same terms as Perl itself.
To install Protocol::SPDY, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Protocol::SPDY
CPAN shell
perl -MCPAN -e shell install Protocol::SPDY
For more information on module installation, please visit the detailed CPAN module installation guide.