# frozen_string_literal: true# Released under the MIT License.# Copyright, 2018-2026, by Samuel Williams.# Copyright, 2022, by Marco Concetto Rudilosso.# Copyright, 2023, by Thomas Morgan.require"protocol/http2/stream"require_relative"input"require_relative"output"moduleAsyncmoduleHTTPmoduleProtocolmoduleHTTP2# An HTTP/2 stream that manages headers, input data, and output data for a single request/response exchange.classStream<::Protocol::HTTP2::Stream# Initialize the stream state.definitialize(*)super@headers=nil@pool=nil# Input buffer, reading request body, or response body (receive_data):@length=nil@input=nil# The application can close its input before the peer finishes sending. HTTP/2 cannot close only the receiving side of a stream, so incoming data is discarded until local output also finishes. At that point, a no-error reset terminates the remaining wire stream.@input_closed=false# Output buffer, writing request body or response body (window_updated):@output=nilendattr_accessor:headersattr_accessor:poolattr:input# Add a header to the stream, validating against HTTP/2 constraints.# @parameter key [String] The header name.# @parameter value [String] The header value.defadd_header(key,value,trailer: false)ifkey==CONNECTIONraise::Protocol::HTTP2::HeaderError,"Connection header is not allowed!"elsifkey.start_with?":"raise::Protocol::HTTP2::HeaderError,"Invalid pseudo-header #{key}!"elsifkey=~/[A-Z]/raise::Protocol::HTTP2::HeaderError,"Invalid upper-case characters in header #{key}!"else@headers.add(key,value,trailer: trailer)endend# Process trailing headers received after the body.# @parameter headers [Array] The trailing header key-value pairs.# @parameter end_stream [Boolean] Whether the stream ends after these headers.defreceive_trailing_headers(headers,end_stream)headers.eachdo|key,value|add_header(key,value,trailer: true)endend# Process an incoming HEADERS frame, dispatching to initial or trailing header handling.# @parameter frame [Protocol::HTTP2::HeadersFrame] The headers frame to process.defprocess_headers(frame)if@headersandframe.end_stream?self.receive_trailing_headers(super,frame.end_stream?)elseself.receive_initial_headers(super,frame.end_stream?)endif@inputandframe.end_stream?@input.close_writeendrescue::Protocol::HTTP::InvalidTrailerError=>errorConsole.warn(self,error)send_reset_stream(::Protocol::HTTP2::Error::PROTOCOL_ERROR)rescue::Protocol::HTTP2::HeaderError=>errorConsole.debug(self,"Error while processing headers!",error)send_reset_stream(error.code)end# @returns [Input | Nil] The input body for this stream, if available.defwait_for_inputreturn@inputend# Prepare the input stream which will be used for incoming data frames.# @return [Input] the input body.defprepare_input(length)if@input.nil?@input=Input.new(self,length)elseraiseArgumentError,"Input body already prepared!"endend# Update the local flow control window after receiving data.# @parameter frame [Protocol::HTTP2::DataFrame] The received data frame.defupdate_local_window(frame)consume_local_window(frame)# This is done on demand in `Input#read`:# request_window_updateend# Process an incoming DATA frame and write it to the input body.# @parameter frame [Protocol::HTTP2::DataFrame] The data frame to process.# @returns [String] The unpacked data.defprocess_data(frame)data=frame.unpackifinput=@inputunlessdata.empty?input.write(data)endifframe.end_stream?input.close_writeendelse# The application has closed the input, so discard incoming data while maintaining flow control for the stream.request_window_updateendreturndatarescue::Protocol::HTTP2::ProtocolErrorraiserescue# Anything else...send_reset_stream(::Protocol::HTTP2::Error::INTERNAL_ERROR)end# Close the application-facing receiving side of the stream. While local output remains active, incoming data is discarded with flow-control updates. Once local output is also closed, the remaining wire stream is terminated without an error.# @parameter input [Input] The input body being closed.# @parameter error [Exception | Nil] The error which closed the input.deffinish_input(input,error=nil)if@input.equal?(input)@input=nil@input_closed=trueiferrorsend_reset_stream(::Protocol::HTTP2::Error::INTERNAL_ERROR)elseclose_if_finishedendendend# Set the body and begin sending it.defsend_body(body,trailer=nil)@output=Output.new(self,body,trailer)@output.startend# Called when the output terminates normally.deffinish_output(error=nil)returnifself.closed?trailer=@output&.trailer@output=niliferrorsend_reset_stream(::Protocol::HTTP2::Error::INTERNAL_ERROR)else# Write trailer?iftrailer&.any?send_headers(trailer,::Protocol::HTTP2::END_STREAM)elsesend_data(nil,::Protocol::HTTP2::END_STREAM)endendend# Called when the flow control window is updated.# @parameter size [Integer] The new window size.# @returns [Boolean] Always returns `true`.defwindow_updated(size)super@output&.window_updated(size)returntrueend# Send headers and apply any pending application-side closure.defsend_headers(...)result=superclose_if_finishedreturnresultend# Send data and apply any pending application-side closure.defsend_data(...)result=superclose_if_finishedreturnresultend# When the stream transitions to the closed state, this method is called. There are roughly two ways this can happen:# - A frame is received which causes this stream to enter the closed state. This method will be invoked from the background reader task.# - A frame is sent which causes this stream to enter the closed state. This method will be invoked from that task.# While the input stream is relatively straight forward, the output stream can trigger the second case abovedefclosed(error)iferror.is_a?(::Protocol::HTTP2::StreamError)&&error.code==::Protocol::HTTP2::Error::NO_ERRORerror=nilendsuperifinput=@input@input=nilinput.close_write(error)endifoutput=@output@output=niliferroroutput.stop(error)elseoutput.close_streamendendifpool=@pooland@connectionpool.release(@connection)endreturnselfendprivate# If both application-facing directions are closed but the peer has not finished, terminate the remaining wire stream without an error.defclose_if_finishedif@input_closed&&@state==:half_closed_localsend_reset_stream(::Protocol::HTTP2::Error::NO_ERROR)endendendendendendend