1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
# frozen_string_literal: true
module HTTP2
# HTTP 2.0 server connection class that implements appropriate header
# compression / decompression algorithms and stream management logic.
#
# Your code is responsible for feeding request data to the server object,
# which in turn performs all of the necessary HTTP 2.0 decoding / encoding,
# state management, and the rest. A simple example:
#
# @example
# socket = YourTransport.new
#
# conn = HTTP2::Server.new
# conn.on(:stream) do |stream|
# ...
# end
#
# while bytes = socket.read
# conn << bytes
# end
#
class Server < Connection
attr_reader :origin_set
# Initialize new HTTP 2.0 server object.
def initialize(settings = {})
@stream_id = 2
@state = :waiting_magic
@local_role = :server
@remote_role = :client
@origin_set = []
@origins_sent = true
super
end
# GET / HTTP/1.1
# Host: server.example.com
# Connection: Upgrade, HTTP2-Settings
# Upgrade: h2c
# HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
#
# Requests that contain a payload body MUST be sent in their entirety
# before the client can send HTTP/2 frames. This means that a large
# request can block the use of the connection until it is completely sent.
#
# If concurrency of an initial request with subsequent requests is
# important, an OPTIONS request can be used to perform the upgrade to
# HTTP/2, at the cost of an additional round trip.
#
# HTTP/1.1 101 Switching Protocols
# Connection: Upgrade
# Upgrade: h2c
#
# [ HTTP/2 connection ...
#
# - The first HTTP/2 frame sent by the server MUST be a server
# connection preface (Section 3.5) consisting of a SETTINGS frame.
# - Upon receiving the 101 response, the client MUST send a connection
# preface (Section 3.5), which includes a SETTINGS frame.
#
# The HTTP/1.1 request that is sent prior to upgrade is assigned a stream
# identifier of 1 (see Section 5.1.1) with default priority values
# (Section 5.3.5). Stream 1 is implicitly "half-closed" from the client
# toward the server (see Section 5.1), since the request is completed as
# an HTTP/1.1 request. After commencing the HTTP/2 connection, stream 1
# is used for the response.
#
def upgrade(settings, headers, body)
@h2c_upgrade = :start
# Pretend that we've received the preface
# - puts us into :waiting_connection_preface state
# - emits a SETTINGS frame to the client
receive(CONNECTION_PREFACE_MAGIC)
# Process received HTTP2-Settings payload
buf = "".b
append_str(buf, Base64.urlsafe_decode64(settings.to_s))
@framer.common_header(
{
length: buf.bytesize,
type: :settings,
stream: 0,
flags: []
},
buffer: buf
)
receive(buf)
# Activate stream (id: 1) with on HTTP/1.1 request parameters
stream = activate_stream(id: 1)
emit(:stream, stream)
headers_frame = {
type: :headers,
flags: [:end_headers],
stream: 1,
weight: DEFAULT_WEIGHT,
dependency: 0,
exclusive: false,
payload: headers
}
if body.empty?
headers_frame[:flags] << [:end_stream]
stream << headers_frame
else
stream << headers_frame
stream << { type: :data, stream: 1, payload: body, flags: [:end_stream] }
end
# Mark h2c upgrade as finished
@h2c_upgrade = :finished
# Transition back to :waiting_magic and wait for client's preface
@state = :waiting_magic
end
def activate_stream(**)
super.tap do |stream|
stream.on(:promise, &method(:promise))
end
end
def origin_set=(origins)
@origin_set = Array(origins).map(&:to_s)
@origins_sent = @origin_set.empty?
end
private
def connection_settings(frame)
super
return unless frame[:flags].include?(:ack) && !@origins_sent
send(type: :origin, stream: 0, payload: @origin_set)
end
def verify_pseudo_headers(frame)
_verify_pseudo_headers(frame, REQUEST_MANDATORY_HEADERS)
end
# Handle locally initiated server-push event emitted by the stream.
#
# @param parent [Stream]
# @param headers [Enumerable[String, String]]
# @param flags [Array[Symbol]]
# @param callback [Proc]
def promise(parent, headers, flags)
promise = new_stream(parent: parent)
promise.send(
type: :push_promise,
flags: flags,
stream: parent.id,
promise_stream: promise.id,
payload: headers.to_a
)
yield(promise)
end
end
end
|