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:
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
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.