# frozen_string_literal: true# Released under the MIT License.# Copyright, 2018-2026, by Samuel Williams.require_relative"../request"require_relative"stream"moduleAsyncmoduleHTTPmoduleProtocolmoduleHTTP2# Typically used on the server side to represent an incoming request, and write the response.classRequest<Protocol::Request# Represents the HTTP/2 stream associated with an incoming server-side request.classStream<HTTP2::Stream# Initialize the request stream.definitialize(*)super@enqueued=false@request=Request.new(self)endattr:request# Process the initial headers received from the client and construct the request.# @parameter headers [Array] The list of header key-value pairs.# @parameter end_stream [Boolean] Whether the stream is complete after these headers.defreceive_initial_headers(headers,end_stream)@headers=::Protocol::HTTP::Headers.newheaders.eachdo|key,value|ifkey==SCHEMEraise::Protocol::HTTP2::HeaderError,"Request scheme already specified!"if@request.scheme@request.scheme=valueelsifkey==AUTHORITYraise::Protocol::HTTP2::HeaderError,"Request authority already specified!"if@request.authority@request.authority=valueelsifkey==METHODraise::Protocol::HTTP2::HeaderError,"Request method already specified!"if@request.method@request.method=valueelsifkey==PATHraise::Protocol::HTTP2::HeaderError,"Request path is empty!"ifvalue.empty?raise::Protocol::HTTP2::HeaderError,"Request path already specified!"if@request.path@request.path=valueelsifkey==PROTOCOLraise::Protocol::HTTP2::HeaderError,"Request protocol already specified!"if@request.protocol@request.protocol=valueelsifkey==CONTENT_LENGTHraise::Protocol::HTTP2::HeaderError,"Request content length already specified!"if@length@length=Integer(value)elsifkey==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 characters in header #{key}!"elseadd_header(key,value)endend@request.headers=@headersunless@request.valid?raise::Protocol::HTTP2::HeaderError,"Request is missing required headers!"else# We only construct the input/body if data is coming.unlessend_stream@request.body=prepare_input(@length)end# We are ready for processing:@connection.requests.enqueue(@request)endreturnheadersend# Called when the stream is closed.# @parameter error [Exception | Nil] The error that caused the close, if any.defclosed(error)@request=nilsuperendend# Initialize the request from an HTTP/2 stream.# @parameter stream [Stream] The HTTP/2 stream for this request.definitialize(stream)super(nil,nil,nil,nil,VERSION,nil,nil,nil,self.public_method(:write_interim_response))@stream=streamendattr:stream# @returns [Connection] The underlying HTTP/2 connection.defconnection@stream.connectionend# @returns [Boolean] Whether the request has the required pseudo-headers.defvalid?@schemeand@methodand(@pathor@method==::Protocol::HTTP::Methods::CONNECT)end# @returns [Boolean] Whether connection hijacking is supported (not available for HTTP/2).defhijack?falseendNO_RESPONSE=[[STATUS,"500"],]# Send a response back to the client via the HTTP/2 stream.# @parameter response [Protocol::HTTP::Response | Nil] The response to send.defsend_response(response)ifresponse.nil?return@stream.send_headers(NO_RESPONSE,::Protocol::HTTP2::END_STREAM)endprotocol_headers=[[STATUS,response.status],]iflength=response.body&.lengthprotocol_headers<<[CONTENT_LENGTH,length]endheaders=::Protocol::HTTP::Headers::Merged.new(protocol_headers,response.headers.header)ifbody=response.bodyand!self.head?# This function informs the headers object that any subsequent headers are going to be trailer. Therefore, it must be called *before* sending the headers, to avoid any race conditions.trailer=response.headers.trailer!@stream.send_headers(headers)@stream.send_body(body,trailer)else# Ensure the response body is closed if we are ending the stream:response.close@stream.send_headers(headers,::Protocol::HTTP2::END_STREAM)endend# Write an interim (1xx) response to the client.# @parameter status [Integer] The interim HTTP status code.# @parameter headers [Hash | Nil] Optional interim response headers.defwrite_interim_response(status,headers=nil)interim_response_headers=[[STATUS,status]]ifheadersinterim_response_headers=::Protocol::HTTP::Headers::Merged.new(interim_response_headers,headers)end@stream.send_headers(interim_response_headers)endendendendendend