Class: HTTP::Request::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/http/request/builder.rb,
sig/http.rbs

Overview

Builds HTTP::Request objects from resolved options

Examples:

Build a request from options

options = HTTP::Options.new(headers: {"Accept" => "application/json"})
builder = HTTP::Request::Builder.new(options)
request = builder.build(:get, "https://example.com")

See Also:

Constant Summary collapse

HTTP_OR_HTTPS_RE =

Pattern matching HTTP or HTTPS URI schemes

Returns:

  • (Regexp)
%r{\Ahttps?://}i

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ HTTP::Request::Builder

Initialize a new Request Builder

Examples:

HTTP::Request::Builder.new(HTTP::Options.new)

Parameters:



32
33
34
# File 'lib/http/request/builder.rb', line 32

def initialize(options)
  @options = options
end

Instance Method Details

#build(verb, uri) ⇒ HTTP::Request

Build an HTTP request

Examples:

builder.build(:get, "https://example.com")

Parameters:

  • verb (Symbol)

    the HTTP method

  • uri (#to_s)

    the URI to request

Returns:



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/http/request/builder.rb', line 45

def build(verb, uri)
  uri     = make_request_uri(uri)
  headers = make_request_headers
  body    = make_request_body(headers)

  req = HTTP::Request.new(
    verb:           verb,
    uri:            uri,
    uri_normalizer: @options.feature(:normalize_uri)&.normalizer,
    proxy:          @options.proxy,
    headers:        headers,
    body:           body
  )

  wrap(req)
end

#make_form_data(form) ⇒ HTTP::FormData::Multipart, HTTP::FormData::Urlencoded

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Coerce form data into an HTTP::FormData object

Parameters:

  • form (Object)

Returns:



196
197
198
199
200
201
# File 'lib/http/request/builder.rb', line 196

def make_form_data(form)
  return form if form.is_a? FormData::Multipart
  return form if form.is_a? FormData::Urlencoded

  FormData.create(form)
end

#make_json_body(data, headers) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Encode JSON body and set content type header

Parameters:

Returns:

  • (String)

    the encoded JSON body



187
188
189
190
191
# File 'lib/http/request/builder.rb', line 187

def make_json_body(data, headers)
  body = MimeType[:json].encode data
  headers[Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name.downcase}"
  body
end

#make_request_body(headers) ⇒ String, ...

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Create the request body object to send

Parameters:

Returns:



172
173
174
175
176
177
178
179
180
181
182
# File 'lib/http/request/builder.rb', line 172

def make_request_body(headers)
  if @options.body
    @options.body
  elsif @options.form
    form = make_form_data(@options.form)
    headers[Headers::CONTENT_TYPE] ||= form.content_type
    form
  elsif @options.json
    make_json_body(@options.json, headers)
  end
end

#make_request_headersHTTP::Headers

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Creates request headers

Returns:



159
160
161
162
163
164
165
166
# File 'lib/http/request/builder.rb', line 159

def make_request_headers
  headers = @options.headers

  # Tell the server to keep the conn open
  headers[Headers::CONNECTION] = @options.persistent? ? Connection::KEEP_ALIVE : Connection::CLOSE

  headers
end

#make_request_uri(uri) ⇒ HTTP::URI

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Merges query params if needed

Parameters:

  • uri (#to_s)

    the URI to process

Returns:



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/http/request/builder.rb', line 83

def make_request_uri(uri)
  uri = neutralize_protocol_relative(uri.to_s)

  if @options.base_uri? && uri !~ HTTP_OR_HTTPS_RE
    uri = resolve_against_base(uri)
  elsif @options.persistent? && uri !~ HTTP_OR_HTTPS_RE
    uri = "#{@options.persistent}#{uri}"
  end

  uri = HTTP::URI.parse uri

  merge_query_params!(uri)

  # Some proxies (seen on WEBrick) fail if URL has
  # empty path (e.g. `http://example.com`) while it's RFC-compliant:
  # http://tools.ietf.org/html/rfc1738#section-3.1
  uri.path = "/" if uri.path.empty?

  uri
end

#merge_query_params!(uri) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Merge query parameters into URI

Parameters:



148
149
150
151
152
153
# File 'lib/http/request/builder.rb', line 148

def merge_query_params!(uri)
  return unless @options.params && !@options.params.empty?

  existing = ::URI.decode_www_form(uri.query || "")
  uri.query = ::URI.encode_www_form(existing.concat(@options.params.to_a))
end

#neutralize_protocol_relative(uri) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Neutralize a leading "//" so it resolves as a relative path under base

A "//"-prefixed input is a protocol-relative (network-path) reference per RFC 3986 §4.2 / §5.2. On the base_uri branch it would replace the base authority via URI#join; on the persistent branch naive concatenation produces "scheme://persistent//evil/path" which HTTP::URI.parse normalises into scheme://evil/path. Prepending "./" forces the input to resolve as an ordinary relative path under the configured base.

Parameters:

  • uri (String)

    the input URI

Returns:

  • (String)

    uri unchanged, or "./" + uri when neutralization applies



117
118
119
120
121
122
# File 'lib/http/request/builder.rb', line 117

def neutralize_protocol_relative(uri)
  return uri unless @options.base_uri? || @options.persistent?
  return uri unless uri.start_with?("//")

  "./#{uri}"
end

#resolve_against_base(uri) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Resolve a relative URI against the configured base URI

Ensures the base URI path has a trailing slash so that relative paths are appended rather than replacing the last path segment, per the convention described in RFC 3986 Section 5.

Parameters:

  • uri (String)

    the relative URI to resolve

Returns:

  • (String)

    the resolved absolute URI



133
134
135
136
137
138
139
140
141
142
# File 'lib/http/request/builder.rb', line 133

def resolve_against_base(uri)
  base = @options.base_uri or raise Error, "base_uri is not set"

  unless base.path.end_with?("/")
    base = base.dup
    base.path = "#{base.path}/"
  end

  String(base.join(uri))
end

#wrap(request) ⇒ HTTP::Request

Wrap a request through feature middleware

Examples:

builder.wrap(redirect_request)

Parameters:

Returns:



70
71
72
73
74
# File 'lib/http/request/builder.rb', line 70

def wrap(request)
  @options.features.inject(request) do |req, (_name, feature)|
    feature.wrap_request(req)
  end
end