Class: Atmos::Request

Inherits:
Object
  • Object
show all
Defined in:
lib/atmos/request.rb

Overview

:nodoc:

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Request

Returns a new instance of Request.



4
5
6
7
8
9
10
11
12
13
14
15
16
# File 'lib/atmos/request.rb', line 4

def initialize(options = {})
   valid = [:store, :default_tag].freeze
   invalid = options.keys - valid
   raise Atmos::Exceptions::ArgumentException,
         "Unrecognized options: #{invalid.inspect}" if (!invalid.empty?)
   Atmos::LOG.debug("Request.initialize: options: #{options.inspect}")

   @baseurl = options[:store].uri
   @uid     = options[:store].uid
   @secret  = options[:store].secret
   @http    = options[:store].http
   @tag     = options[:default_tag]
end

Instance Method Details

#calculate_signature(verb, secret, url, headers) ⇒ Object



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
# File 'lib/atmos/request.rb', line 187

def calculate_signature(verb, secret, url, headers)        
   headers_copy = headers.clone

   # normalize emc headers
   headers_copy.each do |header, value|
      if (header.start_with?('x-emc-'))
         headers.delete(header)
         headers[header.to_s.strip.downcase] = value.to_s.strip.sub(/\s+/, ' ').sub(/\n/, '')
      end
   end
 
   # string together all emc headers, no newline at end
   emc = ""
   headers.keys.sort.each do |header|
      if (header.start_with?('x-emc-'))
         emc += "#{header}:#{headers[header]}\n"
      end
   end
   emc.chomp!
   
   hashstring = verb.to_s.upcase+"\n"
   ['Content-Type', 'Range', 'Date'].each do |key|
      hashstring += headers[key] if (!headers[key].nil?)
      hashstring += "\n"
   end
          
   hashstring += url.path
   hashstring += '?'+url.query if (!url.query.nil?)
   hashstring += "\n"+ emc
                          
   Atmos::LOG.debug("calculate_signature: hashstring: #{hashstring}")
   Base64.encode64(HMAC::SHA1.digest(Base64.decode64(secret), hashstring)).chomp()
end

#do(actionname, options = {}, &block) ⇒ Object



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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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
# File 'lib/atmos/request.rb', line 56

def do(actionname, options = {}, &block)
   verbs = [:head, :get, :put, :post, :delete].freeze
   Atmos::LOG.info("do #{actionname} options: #{options.inspect}")
   raise Atmos::Exceptions::InternalLibraryException,
         "Invalid REST action: #{actionname}" if (REST[actionname].nil?)
   
   action = REST[actionname]
   uri    = get_uri(action, options)         
   url    = URI::join(@baseurl.to_s, uri.to_s)
   verb   = action[:verb]         

   request = (verbs.include?(verb)) ? Net::HTTP.const_get(verb.to_s.capitalize).new(uri) : nil
   raise Atmos::Exceptions::AtmosException,
         "Couldn't create Net::HTTP request object for #{verb}" if (request.nil?)
   
   headers = {}
   headers['Date'] = Time.now().httpdate()
   headers['x-emc-date'] = Time.now().httpdate()
   headers['x-emc-uid'] = @uid

   if (!action[:required_headers].nil?)
      action[:required_headers].each do |header|
         case header
         when 'Content-Type' then
            headers[header] = (!options[:mimetype].nil?) ? options[:mimetype] : 'binary/octet-stream'
         when 'Content-Length' then
            headers[header] = (!options[:length].nil?) ? options[:length] : 0
         when 'x-emc-tags' then
            headers[header] = (!options[header].nil?) ? options[header] : ""
         else
            if (options[header])                  
               headers[header] = options[header]
            else
               raise "Value not supplied for required header: '#{header}'"
            end
         end
      end
   end
   
   if (!action[:optional_headers].nil?)
      action[:optional_headers].each do |header|
         if (header.eql?('Range') && options.has_key?('Range') && !options['Range'].nil?)
            r = options['Range']
            options.delete('Range')
            #headers[header] = (r.kind_of?(Range)) ? "Bytes=#{r.first}-#{r.last}" : "Bytes=#{r.to_s}"
            if (r.kind_of?(Range)) 
               headers[header] = "Bytes=#{r.first}-#{r.last}"
               Atmos::LOG.info("Request.initialize: given Range object: #{headers[header]}")
            else 
               Atmos::LOG.info("Request.initialize: given range string: #{headers[header]}")
               headers[header] =  "Bytes=#{r.to_s}"
            end
            if ((!options[:data].nil?) and ((r.end-r.begin+1) != options[:data].length))
               raise Atmos::Exceptions::ArgumentException, "The range length (#{r.end - r.begin + 1}) doesn't match the data length (#{options[:data].length})."
            end
         end
         key = (action[:header2sym] && action[:header2sym][header]) ? action[:header2sym][header] : header
            
         if (!options[key].nil?)
            if (options[key].kind_of?(Hash))
               headers[header] = ""
               options[key].each do |key,val|
                  headers[header] += "#{key}=#{val}, "
               end
               headers[header].chop!
               headers[header].chop!
            elsif (options[key].kind_of?(Array))
               headers[header] = ""
               options[key].each do |val|
                  headers[header] += "#{val}, "
               end
               headers[header].chop!
               headers[header].chop!
            elsif (options[key].respond_to?(:to_s))
               headers[header] = options[key]
            else
               raise "value for optional header '#{header}' id bad type: #{options[header].class}"
            end
         end
      end
   end

   if (!@tag.nil? && !action[:optional_headers].nil? && action[:optional_headers].include?('x-emc-listable-meta'))
      if (headers.has_key?('x-emc-listable-meta'))
         headers['x-emc-listable-meta'] += ", #{@tag}=atmos-ruby-default"
      else 
         headers['x-emc-listable-meta'] = "#{@tag}=atmos-ruby-default"
      end
   end

   headers['x-emc-signature'] = calculate_signature(action[:verb], @secret, url, headers)
   
   if (options[:data])
      Atmos::LOG.info("there is data: [#{options[:data]}]")
      if (options[:data].respond_to?(:read))
         request.body_stream = options[:data]         
      else
         request.body = options[:data]
      end
      headers['Content-Length'] = 
         options[:data].respond_to?(:lstat) ? options[:data].stat.size : options[:data].size  
      Atmos::LOG.info("set Content-Length to: #{headers['Content-Length']}\n")

   end
   
   headers.each do |key,val|
      request[key] = val
   end
            
  @http.set_debug_output($stdout) if (Atmos::LOG.level == Log4r::DEBUG)

   
   #
   # so this is weird.  because some of these atmos request may be
   # insanely long, we need to allow a mechanism for our API layer
   # to read progressively, not only all at once
   #
   if (block)
      @http.request(request) do |response|
         block.call(response)
      end
      return
   end
   
   response = @http.request(request)
   Atmos::Parser::response_check_action_error(actionname, response)
   
   Atmos::Response.new(response, actionname)
end

#get_uri(action, options) ⇒ Object

If there is no namespace option provided, we’re using the object interface, otherwise we’re using the namespace interface. Determine which it is, so we know which URI to use.

If aoid is nil, we’re creating a new object regardless.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/atmos/request.rb', line 26

def get_uri(action, options)
   
   if (options.has_key?(:namespace) && action.has_key?(:namespace_uri))

      # only two actions need namespace, so if the action doesn't have the 
      # :namespace_url key, we use the regular :object_url.
      ns = (options[:namespace].start_with?('/')) ? options[:namespace][1..-1] : options[:namespace]
      uri = action[:namespace_uri].sub(/:namespace/, ns)
      
   else
      
      uri = action[:object_uri]

      # only need the following check/substution if the uri requires an id
      if (!uri.index(':id').nil?)

         if (options.has_key?(:id) && !options[:id].nil?)
            uri = uri.sub(/:id/, options[:id])
         else
            raise Atmos::Exceptions::ArgumentException, "An id is required for this action.  This is an internal error."
         end
      end
   end

   Atmos::LOG.info("uri: #{uri}")
   uri
end