Protocol::HTTP2SourceProtocolHTTP2Stream

class Stream

A single HTTP/2 connection can multiplex multiple streams in parallel: multiple requests and responses can be in flight simultaneously and stream data can be interleaved and prioritized.

This class encapsulates all of the state, transition, flow-control, and error management as defined by the HTTP 2.0 specification. All you have to do is subscribe to appropriate events (marked with ":" prefix in diagram below) and provide your application logic to handle request and response processing.

                         ┌────────┐
                 send PP │        │ recv PP
              ┌──────────┤  idle  ├──────────┐
              │          │        │          │
              ▼          └───┬────┘          ▼
       ┌──────────┐          │           ┌──────────┐
       │          │          │ send H /  │          │
┌──────┼ reserved │          │ recv H    │ reserved ├──────┐
│      │ (local)  │          │           │ (remote) │      │
│      └───┬──────┘          ▼           └──────┬───┘      │
│          │             ┌────────┐             │          │
│          │     recv ES │        │ send ES     │          │
│   send H │   ┌─────────┤  open  ├─────────┐   │ recv H   │
│          │   │         │        │         │   │          │
│          ▼   ▼         └───┬────┘         ▼   ▼          │
│      ┌──────────┐          │           ┌──────────┐      │
│      │   half   │          │           │   half   │      │
│      │  closed  │          │ send R /  │  closed  │      │
│      │ (remote) │          │ recv R    │ (local)  │      │
│      └────┬─────┘          │           └─────┬────┘      │
│           │                │                 │           │
│           │ send ES /      │       recv ES / │           │
│           │ send R /       ▼        send R / │           │
│           │ recv R     ┌────────┐   recv R   │           │
│ send R /  └───────────►│        │◄───────────┘  send R / │
│ recv R                 │ closed │               recv R   │
└───────────────────────►│        │◄───────────────────────┘
                         └────────┘

State transition methods use a trailing "!".

Definitions

attr :connection

The connection this stream belongs to.

attr :id

Stream ID (odd for client initiated streams, even otherwise).

attr_accessor :state

Stream state, e.g. idle, closed.

def close(error = nil)

Transition directly to closed state. Do not pass go, do not collect $200. This method should only be used by Connection#close.

Implementation

def close(error = nil)
	unless closed?
		@state = :closed
		self.closed(error)
	end
end

def send_headers?

HEADERS frames can be sent on a stream in the "idle", "reserved (local)", "open", or "half-closed (remote)" state. Despite it's name, it can also be used for trailers.

Implementation

def send_headers?
	@state == :idle or @state == :reserved_local or @state == :open or @state == :half_closed_remote
end

def send_headers(*arguments)

The HEADERS frame is used to open a stream, and additionally carries a header block fragment. HEADERS frames can be sent on a stream in the "idle", "reserved (local)", "open", or "half-closed (remote)" state.

Implementation

def send_headers(*arguments)
	if @state == :idle
		frame = write_headers(*arguments)
		
		if frame.end_stream?
			@state = :half_closed_local
		else
			open!
		end
	elsif @state == :reserved_local
		frame = write_headers(*arguments)
		
		@state = :half_closed_remote
	elsif @state == :open
		frame = write_headers(*arguments)
		
		if frame.end_stream?
			@state = :half_closed_local
		end
	elsif @state == :half_closed_remote
		frame = write_headers(*arguments)
		
		if frame.end_stream?
			close!
		end
	else
		raise ProtocolError, "Cannot send headers in state: #{@state}"
	end
end

def closed(error = nil)

The stream has been closed. If closed due to a stream reset, the error will be set.

Implementation

def closed(error = nil)
end

def close!(error_code = nil)

Transition the stream into the closed state.

Implementation

def close!(error_code = nil)
	@state = :closed
	@connection.delete(@id)
	
	if error_code
		error = StreamError.new("Stream closed!", error_code)
	end
	
	self.closed(error)
	
	return self
end

def process_data(frame)

Implementation

def process_data(frame)
	frame.unpack
end

def receive_data(frame)

DATA frames are subject to flow control and can only be sent when a stream is in the "open" or "half-closed (remote)" state. The entire DATA frame payload is included in flow control, including the Pad Length and Padding fields if present. If a DATA frame is received whose stream is not in "open" or "half-closed (local)" state, the recipient MUST respond with a stream error of type STREAM_CLOSED.

Implementation

def receive_data(frame)
	if @state == :open
		update_local_window(frame)
		
		if frame.end_stream?
			@state = :half_closed_remote
		end
		
		process_data(frame)
	elsif @state == :half_closed_local
		update_local_window(frame)
		
		process_data(frame)
		
		if frame.end_stream?
			close!
		end
	elsif self.closed?
		ignore_data(frame)
	else
		# If a DATA frame is received whose stream is not in "open" or "half-closed (local)" state, the recipient MUST respond with a stream error (Section 5.4.2) of type STREAM_CLOSED.
		self.send_reset_stream(Error::STREAM_CLOSED)
	end
end

def create_push_promise_stream(headers)

Override this function to implement your own push promise logic.

Implementation

def create_push_promise_stream(headers)
	@connection.create_push_promise_stream
end

def send_push_promise(headers)

Server push is semantically equivalent to a server responding to a request; however, in this case, that request is also sent by the server, as a PUSH_PROMISE frame.

Implementation

def send_push_promise(headers)
	if @state == :open or @state == :half_closed_remote
		promised_stream = self.create_push_promise_stream(headers)
		promised_stream.reserved_local!
		
		# The headers are the same as if the client had sent a request:
		write_push_promise(promised_stream.id, headers)
		
		# The server should call send_headers on the promised stream to begin sending the response:
		return promised_stream
	else
		raise ProtocolError, "Cannot send push promise in state: #{@state}"
	end
end

def accept_push_promise_stream(stream_id, headers)

Override this function to implement your own push promise logic.

Implementation

def accept_push_promise_stream(stream_id, headers)
	@connection.accept_push_promise_stream(stream_id)
end