Question

I'm green when it comes to Ruby. Right now I'm mucking about with a script which connects to the Terremark eCloud API Explorer. I'm trying to use the httpclient gem, but I'm a bit confused as to how I'm supposed to construct my client.

#!/usr/bin/ruby

require "httpclient"
require 'base64'
require 'hmac-sha1'
require 'openssl'

# Method definitions
def get_date
  # Get the time and date in the necessary format
  result = Time.now.strftime('%a, %d %b %Y %H:%M:%S GMT')
end

def get_signature(action,date,headers,resource,user,pass)

  string_to_sign = "#{action}

  #{date}
  #{headers}
  #{resource}\n"

  return Base64.encode64(OpenSSL::HMAC.digest('sha1', "#{user}:#{pass}", "#{string_to_sign}"))
end

# Initial variables
date = get_date
domain = "https://services.enterprisecloud.terremark.com"
password = 'password'
query = {}
tmrk_headers = Hash.new
tmrk_headers['x-tmrk-date: '] = date
tmrk_headers['x-tmrk-version: '] = '2013-06-01'

uri = '/cloudapi/spec/networks/environments/1'
url = "#{domain}#{uri}"
username = 'user@terremark.com'
verb = 'GET'

signature = get_signature(verb,date,tmrk_headers,uri,username,password)

tmrk_headers['Authorization: '] = "Basic \"#{signature}\""

puts signature

client = HTTPClient.new
client.get_content(url,query,tmrk_headers)

EDIT: This is no longer valid as I've moved beyond this error with some help:

Right now I'm not concerned about seeing what is returned from the connection. I'm just looking to create an error-free run. For instance, if I run the script without the client.get_content line it will return to a prompt without issue (giving me the impression that everything ran cleanly, if not uselessly).

How am I supposed to construct this? The httpclient documentation uses the example with external headers:

extheader = [['Accept', 'image/jpeg'], ['Accept', 'image/png']]
clnt.get_content(uri, query, extheader)

I'm making the assumption that the query is the URI that I've defined.

In all reality, it isn't set up right in the first place. I need to be able to include the string in the auth_header variable in the string to be signed but the signature is actually part of the variable. I've obviously created a hole in that regard.


Any assistance with this will be more than appreciated.

EDIT2: Removed strace pastebin. Adding Ruby backtrace:

/home/msnyder/.rvm/gems/ruby-2.1.1/gems/httpclient-2.3.4.1/lib/httpclient.rb:1023:in `create_request': undefined method `each' for #<String:0x0000000207d1e8> (NoMethodError)
    from /home/msnyder/.rvm/gems/ruby-2.1.1/gems/httpclient-2.3.4.1/lib/httpclient.rb:884:in `do_request'
    from /home/msnyder/.rvm/gems/ruby-2.1.1/gems/httpclient-2.3.4.1/lib/httpclient.rb:959:in `follow_redirect'
    from /home/msnyder/.rvm/gems/ruby-2.1.1/gems/httpclient-2.3.4.1/lib/httpclient.rb:594:in `get_content'
    from ./test.rb:42:in `<main>'

EDIT3: Updated script; adding further backtrace after making necessary script modifications:

/

home/msnyder/.rvm/gems/ruby-2.1.1/gems/httpclient-2.3.4.1/lib/httpclient.rb:975:in `success_content': unexpected response: #<HTTP::Message::Headers:0x00000001dddc58 @http_version="1.1", @body_size=0, @chunked=false, @request_method="GET", @request_uri=#<URI::HTTPS:0x00000001ddecc0 URL:https://services.enterprisecloud.terremark.com/cloudapi/spec/networks/environments/1>, @request_query={}, @request_absolute_uri=nil, @status_code=400, @reason_phrase="Bad Request", @body_type=nil, @body_charset=nil, @body_date=nil, @body_encoding=#<Encoding:US-ASCII>, @is_request=false, @header_item=[["Content-Type", "text/html; charset=us-ascii"], ["Server", "Microsoft-HTTPAPI/2.0"], ["Date", "Thu, 27 Mar 2014 23:12:53 GMT"], ["Connection", "close"], ["Content-Length", "339"]], @dumped=false> (HTTPClient::BadResponseError)
    from /home/msnyder/.rvm/gems/ruby-2.1.1/gems/httpclient-2.3.4.1/lib/httpclient.rb:594:in `get_content'
    from ./test.rb:52:in `<main>'
Was it helpful?

Solution

The issue that you're having as stated by your backtrace

/home/msnyder/.rvm/gems/ruby-2.1.1/gems/httpclient-2.3.4.1/lib/httpclient.rb:1023:in `create_request': undefined method `each' for #<String:0x0000000207d1e8> (NoMethodError)
    from /home/msnyder/.rvm/gems/ruby-2.1.1/gems/httpclient-2.3.4.1/lib/httpclient.rb:884:in `do_request'
    from /home/msnyder/.rvm/gems/ruby-2.1.1/gems/httpclient-2.3.4.1/lib/httpclient.rb:959:in `follow_redirect'
    from /home/msnyder/.rvm/gems/ruby-2.1.1/gems/httpclient-2.3.4.1/lib/httpclient.rb:594:in `get_content'
    from ./test.rb:42:in `<main>'

is that it seems like you're passing a String object to one of the arguments in get_content where it expects an object that responds to the method each.

From looking at the documentation of httpclient#get_content http://www.ruby-doc.org/gems/docs/h/httpclient-xaop-2.1.6/HTTPClient.html#method-i-get_content

It expects the second parameter to be a Hash or Array of arguments

From your code sample and showing only the relevant parts

uri = '/cloudapi/spec/networks/environments/1'
url = "https://services.enterprisecloud.terremark.com"
tmrk_headers = "x-tmrk-date:\"#{date}\"\nx-tmrk-version:2014-01-01"

auth_header = "Authorization: CloudApi AccessKey=\"#{access_key}\" SignatureType=\"HmacSHA1\" Signature=\"#{signature}\""
full_header = "#{tmrk_headers}\n#{auth_header}"

client = HTTPClient.new
client.get_content(url,uri,full_header)

There are two things that I see wrong with your code.

  1. You're passing in a String value for the query. Specifically, you're passing in uri which has a value of what I'm assuming is the path that you want to hit.

  2. For the extra headers parameter, you're passing in a String value which is in the full_header

What you need to do in order to fix this is pass in the full url for the first parameter.

This means it should look something like this:

url = "https://services.enterprisecloud.terremark.com/cloudapi/spec/networks/environments/1"
query = {} # if you have any parameters to pass in they should be here.
headers = {
  "x-tmrk-date" => date, "x-tmrk-version" => "2014-01-01", 
  "Authorization" => "CloudApi AccessKey=#{access_key} SignatureType=HmacSHA1 Signature=#{signature}"
}

client = HTTPClient.new
client.get_content(url, query, headers)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top