Class: Vivarium::HttpDecoder

Inherits:
Object
  • Object
show all
Defined in:
lib/vivarium/http_decoder.rb

Overview

Decodes payloads captured from OpenSSL ‘SSL_write` into a human-readable one-liner. Auto-detects HTTP/1.x request/response lines and HTTP/2 binary frames; HPACK-decompresses HEADERS / CONTINUATION when the `http-2` gem is available, otherwise reports frame types only.

HPACK decompressor state is kept per pid. This is sufficient for the common “one HTTPS connection per process” case; with multiple concurrent TLS connections per pid the HPACK table can diverge and decoding may fail — in that case the decompressor for that pid is reset on the next decode error.

Constant Summary collapse

HTTP1_METHODS =
%w[GET POST PUT PATCH DELETE HEAD OPTIONS TRACE CONNECT].freeze
HTTP2_PREFACE =
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".b
H2_FRAME_HEADER_SIZE =
9
H2_FLAG_END_HEADERS =
0x04
H2_FLAG_PADDED =
0x08
H2_FLAG_PRIORITY =
0x20
FRAME_TYPE_NAMES =
{
  0x0 => "DATA",
  0x1 => "HEADERS",
  0x2 => "PRIORITY",
  0x3 => "RST_STREAM",
  0x4 => "SETTINGS",
  0x5 => "PUSH_PROMISE",
  0x6 => "PING",
  0x7 => "GOAWAY",
  0x8 => "WINDOW_UPDATE",
  0x9 => "CONTINUATION"
}.freeze

Instance Method Summary collapse

Constructor Details

#initializeHttpDecoder

Returns a new instance of HttpDecoder.



40
41
42
43
44
# File 'lib/vivarium/http_decoder.rb', line 40

def initialize
  @hpack_available = load_http2_gem
  @decompressors = {}
  @continuation = {}
end

Instance Method Details

#hpack_available?Boolean

Returns:

  • (Boolean)


46
47
48
# File 'lib/vivarium/http_decoder.rb', line 46

def hpack_available?
  @hpack_available
end

#render(pid:, data:, data_len:) ⇒ Object



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
# File 'lib/vivarium/http_decoder.rb', line 50

def render(pid:, data:, data_len:)
  data = data.to_s.b
  data_len = data_len.to_i

  return "data_len=0" if data_len <= 0
  return "len=#{data_len} <no-capture>" if data.empty?

  if (summary = http1_summary(data))
    kind, line = summary
    return "http/1.x #{kind}: #{line}#{truncation_note(data, data_len)}"
  end

  rest = data
  preface_note = ""
  if rest.start_with?(HTTP2_PREFACE)
    preface_note = "preface "
    rest = rest.byteslice(HTTP2_PREFACE.bytesize..) || "".b
  end

  frames = parse_h2_frames(rest)
  if frames.empty?
    if !preface_note.empty?
      return "h2 preface only#{truncation_note(data, data_len)}"
    end
    return "binary len=#{data_len}#{truncation_note(data, data_len)}"
  end

  rendered = frames.map { |f| render_h2_frame(pid, f) }.join(" | ")
  "h2 #{preface_note}#{rendered}#{truncation_note(data, data_len)}"
end