Module: Syntropy::HTTP::ProtocolMethods

Defined in:
lib/syntropy/http/io_extensions.rb

Overview

HTTP protocol extensions for UringMachine::IO

Constant Summary collapse

RE_REQUEST_LINE =
/^(get|head|options|trace|put|delete|post|patch|connect)\s+([^\s]+)\s+HTTP\/([019\.]{1,3})/i
RE_RESPONSE_LINE =
/^HTTP\/1\.1\s+(\d{3})(\s+.+)?$/i
RE_HEADER_LINE =
/^([a-z0-9\-]+):\s+(.+)/i
MAX_REQUEST_LINE_LEN =

16KB

1 << 14
MAX_RESPONSE_LINE_LEN =

256

1 << 8
MAX_HEADER_LINE_LEN =

8KB

1 << 13
MAX_CHUNK_SIZE_LEN =
16

Instance Method Summary collapse

Instance Method Details

#http_read_body(headers) ⇒ String

Reads an HTTP request/response body.

Parameters:

  • headers (Hash)

    request/response headers

Returns:

  • (String)

    body



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/syntropy/http/io_extensions.rb', line 86

def http_read_body(headers)
  content_length = headers['content-length']
  if content_length
    content_length = content_length.to_i
    return nil if content_length == 0

    chunk = read(content_length.to_i)
    return chunk
  end

  chunked_encoding = headers['transfer-encoding']&.downcase == 'chunked'
  if chunked_encoding
    buf = +''
    while (chunk = http_read_cte_chunk(nil))
      buf << chunk
    end
    return buf
  end

  nil
end

#http_read_body_chunk(headers) ⇒ String?

Reads an HTTP body chunk. If no chunks remain to be read, returns nil.

Parameters:

  • headers (Hash)

    request/response headers

Returns:

  • (String, nil)

    body chunk



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/syntropy/http/io_extensions.rb', line 132

def http_read_body_chunk(headers)
  content_length = headers['content-length']
  if content_length
    content_length = content_length.to_i
    return nil if content_length == 0

    chunk = read(content_length.to_i)
    return chunk
  end

  chunked_encoding = headers['transfer-encoding']&.downcase == 'chunked'
  return http_read_cte_chunk(nil) if chunked_encoding

  nil
end

#http_read_request_headersHash

Reads HTTP request headers.

Returns:

  • (Hash)

    request headers

Raises:



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
# File 'lib/syntropy/http/io_extensions.rb', line 21

def http_read_request_headers
  line = read_line(MAX_REQUEST_LINE_LEN)
  return nil if !line

  m = line.match(RE_REQUEST_LINE)
  raise ProtocolError, 'Invalid request line' if !m

  http_version = m[3]
  raise UnsupportedHTTPVersionError, 'HTTP version not supported' if http_version != '1.1'

  headers = {
    ':method'   => m[1].downcase,
    ':path'     => m[2]
  }

  loop do
    line = read_line(MAX_HEADER_LINE_LEN)
    break if line.nil? || line.empty?

    m = line.match(RE_HEADER_LINE)
    raise ProtocolError, "Invalid header: #{line[0..2047].inspect}" if !m

    headers[m[1].downcase] = m[2]
  end

  headers
end

#http_read_response_headersHash

Reads HTTP response headers.

Returns:

  • (Hash)

    response headers

Raises:



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
# File 'lib/syntropy/http/io_extensions.rb', line 52

def http_read_response_headers
  line = read_line(MAX_RESPONSE_LINE_LEN)
  return nil if !line

  m = line.match(RE_RESPONSE_LINE)
  raise ProtocolError, 'Invalid response line' if !m

  headers = {
    ':status'   => m[1].to_i
  }

  loop do
    line = read_line(MAX_HEADER_LINE_LEN)
    break if line.nil? || line.empty?

    m = line.match(RE_HEADER_LINE)
    raise ProtocolError, "Invalid header: #{line[0..2047].inspect}" if !m

    k = m[1].downcase
    if (h = headers[k])
      (h = headers[k] = [h]) if !h.is_a?(Array)
      h << m[2]
    else
      headers[k] = m[2]
    end
  end

  headers
end

#http_skip_body(headers) ⇒ void

This method returns an undefined value.

Skips an HTTP reuqest/response body.

Parameters:

  • headers (Hash)

    request/response headers



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/syntropy/http/io_extensions.rb', line 112

def http_skip_body(headers)
  content_length = headers['content-length']
  if content_length
    content_length = content_length.to_i
    return if content_length == 0

    return skip(content_length.to_i)
  end

  chunked_encoding = headers['transfer-encoding']&.downcase == 'chunked'
  if chunked_encoding
    while http_skip_cte_chunk
    end
  end
end

#http_write_request_headers(headers) ⇒ void

This method returns an undefined value.

Writes HTTP request headers.

Parameters:

  • headers (Hash)

    request headers



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/syntropy/http/io_extensions.rb', line 152

def http_write_request_headers(headers)
  method = headers[':method'] || (raise BadRequestError)
  path = headers[':path'] || (raise BadRequestError)

  lines = ["#{method} #{path} HTTP/1.1\r\n"]
  headers.each do |k, v|
    next if k =~ /^\:/

    if v.is_a?(Array)
      v.each { lines << "#{k}: #{it}\r\n" }
    else
      lines << "#{k}: #{v}\r\n"
    end
  end
  lines << "\r\n"
  write(*lines)
end