Class: Sunstone::Connection

Inherits:
Object
  • Object
show all
Defined in:
lib/sunstone/connection.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Connection

Initialize a connection a Sunstone API server.

Options:

  • :url - An optional url used to set the protocol, host, port, and api_key

  • :host - The default is to connect to 127.0.0.1.

  • :port - Defaults to 80.

  • :use_ssl - Defaults to false.

  • :api_key - An optional token to send in the ‘Api-Key` header

  • :user_agent - An optional string. Will be joined with other

    User-Agent info.
    


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
48
# File 'lib/sunstone/connection.rb', line 21

def initialize(config)
  if config[:url]
    uri = URI.parse(config.delete(:url))
    config[:api_key] ||= (uri.user ? CGI.unescape(uri.user) : nil)
    config[:host]    ||= uri.host
    config[:port]    ||= uri.port
    config[:use_ssl] ||= (uri.scheme == 'https')
  end

  [:api_key, :host, :port, :use_ssl, :user_agent].each do |key|
    self.instance_variable_set(:"@#{key}", config[key])
  end

  @connection = Net::HTTP.new(host, port || (use_ssl ? 443 : 80))
  @connection.max_retries         = 0
  @connection.open_timeout        = 5
  @connection.read_timeout        = 30
  @connection.write_timeout       = 5
  @connection.ssl_timeout         = 5
  @connection.keep_alive_timeout  = 30
  @connection.use_ssl = use_ssl
  if use_ssl && config[:ca_cert]
    @connection.cert_store = OpenSSL::X509::Store.new
    @connection.cert_store.add_cert(OpenSSL::X509::Certificate.new(File.read(config[:ca_cert])))
  end

  true
end

Instance Attribute Details

#api_keyObject (readonly)

Returns the value of attribute api_key.



7
8
9
# File 'lib/sunstone/connection.rb', line 7

def api_key
  @api_key
end

#hostObject (readonly)

Returns the value of attribute host.



7
8
9
# File 'lib/sunstone/connection.rb', line 7

def host
  @host
end

#portObject (readonly)

Returns the value of attribute port.



7
8
9
# File 'lib/sunstone/connection.rb', line 7

def port
  @port
end

#prefixObject (readonly)

Returns the value of attribute prefix.



7
8
9
# File 'lib/sunstone/connection.rb', line 7

def prefix
  @prefix
end

#use_sslObject (readonly)

Returns the value of attribute use_ssl.



7
8
9
# File 'lib/sunstone/connection.rb', line 7

def use_ssl
  @use_ssl
end

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


67
68
69
# File 'lib/sunstone/connection.rb', line 67

def active?
  @connection.active?
end

#connect!Object



63
64
65
# File 'lib/sunstone/connection.rb', line 63

def connect!
  @connection.start
end

#delete(path, &block) ⇒ Object

Send a DELETE request to path on the Sunstone Server via Sunstone#send_request. See Sunstone#send_request for more details on how the response is handled

Paramaters
  • path - The path on the server to POST to.

  • block - Optional, See Sunstone#send_request

Return Value

See Sunstone#send_request

Examples:

#!ruby
Sunstone.delete('/example') # => #<Net::HTTP::Response>

Sunstone.delete('/404') # => raises Sunstone::Exception::NotFound

Sunstone.delete('/act') do |response|
  # ...
end


362
363
364
365
366
# File 'lib/sunstone/connection.rb', line 362

def delete(path, &block)
  request = Net::HTTP::Delete.new(path)

  send_request(request, nil, &block)
end

#disconnect!Object



76
77
78
# File 'lib/sunstone/connection.rb', line 76

def disconnect!
  @connection.finish if @connection.active?
end

#get(path, params = '', &block) ⇒ Object

Send a GET request to path on the Sunstone Server via Sunstone#send_request. See Sunstone#send_request for more details on how the response is handled.

Paramaters
  • path - The path on the server to GET to.

  • params - Either a String, Hash, or Ruby Object that responds to

    #to_param. Appended on the URL as query params
    
  • block - An optional block to call with the Net::HTTPResponse object.

Return Value

See Sunstone#send_request

Examples:

#!ruby
Sunstone.get('/example') # => #<Net::HTTP::Response>

Sunstone.get('/example', 'query=stuff') # => #<Net::HTTP::Response>

Sunstone.get('/example', {:query => 'stuff'}) # => #<Net::HTTP::Response>

Sunstone.get('/404') # => raises Sunstone::Exception::NotFound

Sunstone.get('/act') do |response|
  # ...
end


263
264
265
266
267
268
# File 'lib/sunstone/connection.rb', line 263

def get(path, params='', &block)
  params ||= ''
  request = Net::HTTP::Get.new(path + '?' + params.to_param)

  send_request(request, nil, &block)
end

#pingObject

Ping the Sunstone. If everything is configured and operating correctly "pong" will be returned. Otherwise and Sunstone::Exception should be thrown.

#!ruby
Sunstone.ping # => "pong"

Sunstone.ping # raises Sunstone::Exception::ServiceUnavailable if a
503 is returned


59
60
61
# File 'lib/sunstone/connection.rb', line 59

def ping
  get('/ping').body
end

#post(path, body = nil, &block) ⇒ Object

Send a POST request to path on the Sunstone Server via Sunstone#send_request. See Sunstone#send_request for more details on how the response is handled.

Paramaters
  • path - The path on the server to POST to.

  • body - Optional, See Sunstone#send_request.

  • block - Optional, See Sunstone#send_request

Return Value

See Sunstone#send_request

Examples:

#!ruby
Sunstone.post('/example') # => #<Net::HTTP::Response>

Sunstone.post('/example', 'body') # => #<Net::HTTP::Response>

Sunstone.post('/example', #<IO Object>) # => #<Net::HTTP::Response>

Sunstone.post('/example', {:example => 'data'}) # => #<Net::HTTP::Response>

Sunstone.post('/404') # => raises Sunstone::Exception::NotFound

Sunstone.post('/act') do |response|
  # ...
end


299
300
301
302
303
# File 'lib/sunstone/connection.rb', line 299

def post(path, body=nil, &block)
  request = Net::HTTP::Post.new(path)

  send_request(request, body, &block)
end

#put(path, body = nil, *valid_response_codes, &block) ⇒ Object

Send a PUT request to path on the Sunstone Server via Sunstone#send_request. See Sunstone#send_request for more details on how the response is handled.

Paramaters
  • path - The path on the server to POST to.

  • body - Optional, See Sunstone#send_request.

  • block - Optional, See Sunstone#send_request

Return Value

See Sunstone#send_request

Examples:

#!ruby
Sunstone.put('/example') # => #<Net::HTTP::Response>

Sunstone.put('/example', 'body') # => #<Net::HTTP::Response>

Sunstone.put('/example', #<IO Object>) # => #<Net::HTTP::Response>

Sunstone.put('/example', {:example => 'data'}) # => #<Net::HTTP::Response>

Sunstone.put('/404') # => raises Sunstone::Exception::NotFound

Sunstone.put('/act') do |response|
  # ...
end


334
335
336
337
338
# File 'lib/sunstone/connection.rb', line 334

def put(path, body=nil, *valid_response_codes, &block)
  request = Net::HTTP::Put.new(path)

  send_request(request, body, &block)
end

#reconnect!Object



71
72
73
74
# File 'lib/sunstone/connection.rb', line 71

def reconnect!
  disconnect!
  connect!
end

#send_request(request, body = nil, &block) ⇒ Object

Sends a Net::HTTPRequest to the server. The headers returned from Sunestone#headers are automatically added to the request. The appropriate error is raised if the response is not in the 200..299 range.

Paramaters
  • request - A Net::HTTPRequest to send to the server

  • body - Optional, a String, IO Object, or a Ruby object which is

    converted into JSON and sent as the body
    
  • block - An optional block to call with the Net::HTTPResponse object.

Return Value

Returns the return value of the &block if given, otherwise the response object (a Net::HTTPResponse)

Examples:

#!ruby
Sunstone.send_request(#<Net::HTTP::Get>) # => #<Net::HTTP::Response>

Sunstone.send_request(#<Net::HTTP::Get @path="/404">) # => raises Sunstone::Exception::NotFound

# this will still raise an exception if the response_code is not valid
# and the block will not be called
Sunstone.send_request(#<Net::HTTP::Get>) do |response|
  # ...
end

# The following example shows how to stream a response:
Sunstone.send_request(#<Net::HTTP::Get>) do |response|
  response.read_body do |chunk|
    io.write(chunk)
  end
end


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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/sunstone/connection.rb', line 130

def send_request(request, body=nil, &block)
  if request.method != 'GET' && Thread.current[:sunstone_transaction_count]
    if Thread.current[:sunstone_transaction_count] == 1 && !Thread.current[:sunstone_request_sent]
      Thread.current[:sunstone_request_sent] = request
    elsif Thread.current[:sunstone_request_sent]
      message = <<~MSG
        Cannot send multiple request in a transaction.
    
        Trying to send:
      MSG
      
      path_and_query = request.path.split('?', 2)
      message << "  #{request.method} #{path_and_query[0]}"
      if path_and_query[1]
        if request['Query-Encoding'] == 'application/msgpack'
          message << " " << MessagePack.unpack(CGI.unescape(path_and_query[1])).inspect
        else
          message << " " << CGI.unescape(path_and_query[1])
        end
      end
        
      message << "\n\nAlready sent:\n"
      path_and_query = Thread.current[:sunstone_request_sent].path.split('?', 2)
      message << "  #{Thread.current[:sunstone_request_sent].method} #{path_and_query[0]}"
      if path_and_query[1]
        if request['Query-Encoding'] == 'application/msgpack'
          message << " " << MessagePack.unpack(CGI.unescape(path_and_query[1]))
        else
          message << " " << CGI.unescape(path_and_query[1])
        end
      end

      raise ActiveRecord::StatementInvalid, message
    else
      log_mess = request.path.split('?', 2)
      message = if request['Query-Encoding'] == 'application/msgpack'
        <<~MSG
          Cannot send multiple request in a transaction.
        
          Trying to send:
            #{request.method} #{log_mess[0]} #{(log_mess[1] && !log_mess[1].empty?) ? MessagePack.unpack(CGI.unescape(log_mess[1])) : '' }
        MSG
      else
          <<~MSG
          Cannot send multiple request in a transaction.
        
          Trying to send:
            #{request.method} #{log_mess[0]} #{(log_mess[1] && !log_mess[1].empty?) ? CGI.unescape(log_mess[1]) : '' }
        MSG
      end
      raise ActiveRecord::StatementInvalid, message
    end
  end
  
  request_uri = url(request.path)
  request_headers.each { |k, v| request[k] = v }
  request['Content-Type'] ||= 'application/json'
  
  if Thread.current[:sunstone_cookie_store]
    request['Cookie'] = Thread.current[:sunstone_cookie_store].cookie_header_for(request_uri)
  end

  if body.is_a?(IO)
    request['Transfer-Encoding'] = 'chunked'
    request.body_stream =  body
  elsif body.is_a?(String)
    request.body = body
  elsif body
    request.body = JSON.generate(body)
  end

  return_value = nil
  begin
    close_connection = false
    @connection.request(request) do |response|
      if response['Deprecation-Notice']
        ::ActiveRecord.deprecator.warn(response['Deprecation-Notice'])
      end

      validate_response_code(response)

      # Get the cookies
      response.each_header do |key, value|
        case key.downcase
        when 'set-cookie'
          if Thread.current[:sunstone_cookie_store]
            Thread.current[:sunstone_cookie_store].set_cookie(request_uri, value)
          end
        when 'connection'
          close_connection = (value == 'close')
        end
      end

      if block_given?
        return_value = yield(response)
      else
        return_value = response
      end
    end
    @connection.finish if close_connection
  end

  return_value
end

#server_configObject



368
369
370
# File 'lib/sunstone/connection.rb', line 368

def server_config
  @server_config ||= JSON.parse(get('/config').body, symbolize_names: true)
end

#url(path = nil) ⇒ Object



91
92
93
# File 'lib/sunstone/connection.rb', line 91

def url(path=nil)
  "http#{use_ssl ? 's' : ''}://#{host}#{port != 80 ? (port == 443 && use_ssl ? '' : ":#{port}") : ''}#{path}"
end

#user_agentObject

Returns the User-Agent of the client. Defaults to: “sunstone-ruby/SUNSTONE_VERSION RUBY_VERSION-pPATCH_LEVEL PLATFORM”



82
83
84
85
86
87
88
89
# File 'lib/sunstone/connection.rb', line 82

def user_agent
  [
    @user_agent,
    "Sunstone/#{Sunstone::VERSION}",
    "Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}",
    RUBY_PLATFORM
  ].compact.join(' ')
end