# frozen_string_literal: true# Released under the MIT License.# Copyright, 2018-2024, 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::RequestclassStream<HTTP2::Streamdefinitialize(*)super@enqueued=false@request=Request.new(self)endattr:requestdefreceive_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)endreturnheadersenddefclosed(error)@request=nilsuperendenddefinitialize(stream)super(nil,nil,nil,nil,VERSION,nil,nil,nil,self.public_method(:write_interim_response))@stream=streamendattr:streamdefconnection@stream.connectionenddefvalid?@schemeand@methodand@pathenddefhijack?falseendNO_RESPONSE=[[STATUS,'500'],]defsend_response(response)ifresponse.nil?return@stream.send_headers(nil,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)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(nil,headers)@stream.send_body(body,trailer)else# Ensure the response body is closed if we are ending the stream:response.close@stream.send_headers(nil,headers,::Protocol::HTTP2::END_STREAM)endenddefwrite_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(nil,interim_response_headers)endendendendendend