Class: Heroics::Link

Inherits:
Object
  • Object
show all
Defined in:
lib/heroics/link.rb

Overview

A link invokes requests with an HTTP server.

Defined Under Namespace

Classes: CachedResponse

Instance Method Summary collapse

Constructor Details

#initialize(url, link_schema, options = {}) ⇒ Link

Instantiate a link.

Parameters:

  • url (String)

    The URL to use when making requests. Include the username and password to use with HTTP basic auth.

  • link_schema (LinkSchema)

    The schema for this link.

  • options (Hash) (defaults to: {})

    Configuration for the link. Possible keys include:

    • default_headers: Optionally, a set of headers to include in every request made by the client. Default is no custom headers.

    • cache: Optionally, a Moneta-compatible cache to store ETags. Default is no caching.



16
17
18
19
20
21
22
23
# File 'lib/heroics/link.rb', line 16

def initialize(url, link_schema, options={})
  @root_url, @path_prefix = unpack_url(url)
  @link_schema = link_schema
  @default_headers = options[:default_headers] || {}
  @cache = options[:cache] || {}
  @rate_throttle = options[:rate_throttle] || Heroics::Configuration.defaults.options[:rate_throttle]
  @status_codes = options[:status_codes] || Heroics::Configuration.defaults.options[:status_codes]
end

Instance Method Details

#run(*parameters) ⇒ String, ...

Make a request to the server.

JSON content received with an ETag is cached. When the server returns a *304 Not Modified* status code content is loaded and returned from the cache. The cache considers headers, in addition to the URL path, when creating keys so that requests to the same path, such as for paginated results, don’t cause cache collisions.

When the server returns a *206 Partial Content* status code the result is assumed to be an array and an enumerator is returned. The enumerator yields results from the response until they’ve been consumed at which point, if additional content is available from the server, it blocks and makes a request to fetch the subsequent page of data. This behaviour continues until the client stops iterating the enumerator or the dataset from the server has been entirely consumed.

Parameters:

  • parameters (Array)

    The list of parameters to inject into the path. A request body can be passed as the final parameter and will always be converted to JSON before being transmitted.

Returns:

  • (String, Object, Enumerator)

    A string for text responses, an object for JSON responses, or an enumerator for list responses.

Raises:

  • (ArgumentError)

    Raised if either too many or too few parameters were provided.



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
# File 'lib/heroics/link.rb', line 48

def run(*parameters)
  path, body = @link_schema.format_path(parameters)
  path = "#{@path_prefix}#{path}" unless @path_prefix == '/'
  headers = @default_headers
  if body
    case @link_schema.method
    when :put, :post, :patch
      headers = headers.merge({'Content-Type' => @link_schema.content_type})
      body = @link_schema.encode(body)
    when :get, :delete
      if body.is_a?(Hash)
        query = body
      else
        query = MultiJson.load(body)
      end
      body = nil
    end
  end

  connection = Excon.new(@root_url, thread_safe_sockets: true)
  response = request_with_cache(connection,
                                method: @link_schema.method, path: path,
                                headers: headers, body: body, query: query,
                                expects: [200, 201, 202, 204, 206])
  content_type = response.headers['Content-Type']
  if content_type && content_type =~ /application\/.*json/
    body = MultiJson.load(response.body)
    if response.status == 206
      next_range = response.headers['Next-Range']
      Enumerator.new do |yielder|
        while true do
          # Yield the results we got in the body.
          body.each do |item|
            yielder << item
          end

          # Only make a request to get the next page if we have a valid
          # next range.
          break unless next_range
          headers = headers.merge({'Range' => next_range})
          response = request_with_cache(connection,
                                        method: @link_schema.method,
                                        path: path, headers: headers,
                                        expects: [200, 201, 206])
          body = MultiJson.load(response.body)
          next_range = response.headers['Next-Range']
        end
      end
    else
      body
    end
  elsif !response.body.empty?
    response.body
  end
end