Class: HTTPX::Parser::HTTP1

Inherits:
Object
  • Object
show all
Defined in:
lib/httpx/parser/http1.rb,
sig/parser/http1.rbs

Constant Summary collapse

VERSIONS =

Returns:

  • (Array[String])
%w[1.0 1.1].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(observer, max_headers, max_header_value_size) ⇒ HTTP1

Returns a new instance of HTTP1.

Parameters:

  • observer (_HTTP1Events)
  • max_headers (Options::maybe_unbounded)
  • max_header_value_size (Options::maybe_unbounded, nil)


12
13
14
15
16
17
18
19
20
21
# File 'lib/httpx/parser/http1.rb', line 12

def initialize(observer, max_headers, max_header_value_size)
  @observer = observer
  @state = :idle
  @buffer = "".b
  @headers = {}
  @max_headers = max_headers
  @max_header_value_size = max_header_value_size
  @content_length = nil
  @_has_trailers = @upgrade = false
end

Instance Attribute Details

#headersparsed_headers (readonly)

Returns the value of attribute headers.

Returns:

  • (parsed_headers)


10
11
12
# File 'lib/httpx/parser/http1.rb', line 10

def headers
  @headers
end

#http_version[Integer, Integer] (readonly)

Returns the value of attribute http_version.

Returns:

  • ([Integer, Integer])


10
11
12
# File 'lib/httpx/parser/http1.rb', line 10

def http_version
  @http_version
end

#status_codeInteger (readonly)

Returns the value of attribute status_code.

Returns:

  • (Integer)


10
11
12
# File 'lib/httpx/parser/http1.rb', line 10

def status_code
  @status_code
end

Instance Method Details

#<<(chunk) ⇒ void

This method returns an undefined value.

Parameters:

  • chunk (string)


23
24
25
26
# File 'lib/httpx/parser/http1.rb', line 23

def <<(chunk)
  @buffer << chunk
  parse
end

#nextstate(state) ⇒ void

This method returns an undefined value.

Parameters:

  • state (Symbol)


189
190
191
192
193
194
195
196
197
198
199
# File 'lib/httpx/parser/http1.rb', line 189

def nextstate(state)
  @state = state
  case state
  when :headers
    @observer.on_start
  when :complete
    @observer.on_complete
    reset!
    nextstate(:idle) unless @buffer.empty?
  end
end

#no_more_data?Boolean

Returns:

  • (Boolean)


178
179
180
181
182
183
184
185
186
187
# File 'lib/httpx/parser/http1.rb', line 178

def no_more_data?
  if @content_length
    @content_length <= 0
  elsif @buffer.respond_to?(:finished?)
    # @type ivar @buffer: Transcoder::Chunker::Decoder
    @buffer.finished?
  else
    false
  end
end

#parsevoid

This method returns an undefined value.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/httpx/parser/http1.rb', line 46

def parse
  loop do
    state = @state
    case @state
    when :idle
      parse_headline
    when :headers, :trailers
      parse_headers
    when :data
      parse_data
    end
    return if @buffer.empty? || state == @state
  end
end

#parse_datavoid

This method returns an undefined value.



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
# File 'lib/httpx/parser/http1.rb', line 130

def parse_data
  if @buffer.respond_to?(:each)
    # @type ivar @buffer: Transcoder::Chunker::Decoder
    @buffer.each do |chunk|
      @observer.on_data(chunk)
    end
  elsif @content_length
    # @type ivar @buffer: String
    data = @buffer.byteslice(0, @content_length)
    # @type var data: String
    @buffer = @buffer.byteslice(@content_length..-1) || "".b
    @content_length -= data.bytesize
    @observer.on_data(data)
    data.clear
  else
    # @type ivar @buffer: String
    @observer.on_data(@buffer)
    @buffer.clear
  end
  return unless no_more_data?

  @buffer = @buffer.to_s
  if @_has_trailers
    nextstate(:trailers)
  else
    nextstate(:complete)
  end
end

#parse_headersvoid

This method returns an undefined value.



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
# File 'lib/httpx/parser/http1.rb', line 79

def parse_headers
  headers = @headers
  buffer = @buffer

  #: @type var buffer: String

  while (idx = buffer.index("\n"))
    # @type var line: String
    line = buffer.byteslice(0..idx)
    raise Error, "wrong header format" if line.start_with?("\s", "\t")

    line.lstrip!
    buffer = @buffer = buffer.byteslice((idx + 1)..-1)
    if line.empty?
      case @state
      when :headers
        prepare_data(headers)
        @observer.on_headers(headers)
        return unless @state == :headers

        # state might have been reset
        # in the :headers callback
        nextstate(:data)
        headers.clear
      when :trailers
        @observer.on_trailers(headers)
        headers.clear
        nextstate(:complete)
      end
      return
    end
    separator_index = line.index(":")
    raise Error, "wrong header format" unless separator_index

    # @type var key: String
    key = line.byteslice(0..(separator_index - 1))

    key.rstrip! # was lstripped previously!
    # @type var value: String
    value = line.byteslice((separator_index + 1)..-1)
    value.strip!
    raise Error, "wrong header format" if value.nil?

    values = (headers[key.downcase] ||= []) << value

    raise Error, "maximum header value size exceeded" if @max_header_value_size && (values.sum(&:size) > @max_header_value_size)

    raise Error, "maximum number of response headers exceeded" if headers.size > @max_headers
  end
end

#parse_headlinevoid

This method returns an undefined value.

Raises:



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/httpx/parser/http1.rb', line 61

def parse_headline
  #: @type ivar @buffer: String
  idx = @buffer.index("\n")
  return unless idx

  (m = %r{\AHTTP(?:/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?}in.match(@buffer)) ||
    raise(Error, "wrong head line format")
  version, code, _ = m.captures
  raise(Error, "unsupported HTTP version (HTTP/#{version})") unless version && VERSIONS.include?(version)

  @http_version = version.split(".").map(&:to_i)
  @status_code = code.to_i
  raise(Error, "wrong status code (#{@status_code})") unless (100..599).cover?(@status_code)

  @buffer = @buffer.byteslice((idx + 1)..-1)
  nextstate(:headers)
end

#prepare_data(headers) ⇒ void

This method returns an undefined value.

Parameters:

  • headers (parsed_headers)


159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/httpx/parser/http1.rb', line 159

def prepare_data(headers)
  @upgrade = headers.key?("upgrade")

  @_has_trailers = headers.key?("trailer")

  if (tr_encodings = headers["transfer-encoding"])
    tr_encodings.reverse_each do |tr_encoding|
      tr_encoding.split(/ *, */).each do |encoding|
        case encoding
        when "chunked"
          @buffer = Transcoder::Chunker::Decoder.new(@buffer.to_s, @_has_trailers)
        end
      end
    end
  else
    @content_length = headers["content-length"][0].to_i if headers.key?("content-length")
  end
end

#reset!void

This method returns an undefined value.



28
29
30
31
32
33
34
# File 'lib/httpx/parser/http1.rb', line 28

def reset!
  @state = :idle
  @headers = {}
  @content_length = nil
  @_has_trailers = @upgrade = false
  @buffer = @buffer.to_s
end

#upgrade?Boolean

Returns:

  • (Boolean)


36
37
38
# File 'lib/httpx/parser/http1.rb', line 36

def upgrade?
  @upgrade
end

#upgrade_dataString

Returns:

  • (String)


40
41
42
# File 'lib/httpx/parser/http1.rb', line 40

def upgrade_data
  @buffer.to_s
end