vmc 0.0.8 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/LICENSE +8 -3
  2. data/README.md +83 -0
  3. data/Rakefile +11 -65
  4. data/bin/vmc +3 -2
  5. data/lib/cli/commands/admin.rb +57 -0
  6. data/lib/cli/commands/apps.rb +828 -0
  7. data/lib/cli/commands/base.rb +56 -0
  8. data/lib/cli/commands/misc.rb +99 -0
  9. data/lib/cli/commands/services.rb +84 -0
  10. data/lib/cli/commands/user.rb +60 -0
  11. data/lib/cli/config.rb +109 -0
  12. data/lib/cli/core_ext.rb +119 -0
  13. data/lib/cli/errors.rb +19 -0
  14. data/lib/cli/frameworks.rb +97 -0
  15. data/lib/cli/runner.rb +437 -0
  16. data/lib/cli/services_helper.rb +74 -0
  17. data/lib/cli/usage.rb +94 -0
  18. data/lib/cli/version.rb +5 -0
  19. data/lib/cli/zip_util.rb +61 -0
  20. data/lib/cli.rb +30 -0
  21. data/lib/vmc/client.rb +415 -0
  22. data/lib/vmc/const.rb +19 -0
  23. data/lib/vmc.rb +2 -1589
  24. data/spec/assets/app_info.txt +9 -0
  25. data/spec/assets/app_listings.txt +9 -0
  26. data/spec/assets/bad_create_app.txt +9 -0
  27. data/spec/assets/delete_app.txt +9 -0
  28. data/spec/assets/global_service_listings.txt +9 -0
  29. data/spec/assets/good_create_app.txt +9 -0
  30. data/spec/assets/good_create_service.txt +9 -0
  31. data/spec/assets/info_authenticated.txt +27 -0
  32. data/spec/assets/info_return.txt +15 -0
  33. data/spec/assets/info_return_bad.txt +16 -0
  34. data/spec/assets/login_fail.txt +9 -0
  35. data/spec/assets/login_success.txt +9 -0
  36. data/spec/assets/sample_token.txt +1 -0
  37. data/spec/assets/service_already_exists.txt +9 -0
  38. data/spec/assets/service_listings.txt +9 -0
  39. data/spec/assets/service_not_found.txt +9 -0
  40. data/spec/assets/user_info.txt +9 -0
  41. data/spec/spec_helper.rb +11 -0
  42. data/spec/unit/cli_opts_spec.rb +73 -0
  43. data/spec/unit/client_spec.rb +284 -0
  44. metadata +114 -71
  45. data/README +0 -58
  46. data/lib/parse.rb +0 -719
  47. data/lib/vmc_base.rb +0 -205
  48. data/vendor/gems/httpclient/VERSION +0 -1
  49. data/vendor/gems/httpclient/lib/http-access2/cookie.rb +0 -1
  50. data/vendor/gems/httpclient/lib/http-access2/http.rb +0 -1
  51. data/vendor/gems/httpclient/lib/http-access2.rb +0 -53
  52. data/vendor/gems/httpclient/lib/httpclient/auth.rb +0 -522
  53. data/vendor/gems/httpclient/lib/httpclient/cacert.p7s +0 -1579
  54. data/vendor/gems/httpclient/lib/httpclient/cacert_sha1.p7s +0 -1579
  55. data/vendor/gems/httpclient/lib/httpclient/connection.rb +0 -84
  56. data/vendor/gems/httpclient/lib/httpclient/cookie.rb +0 -562
  57. data/vendor/gems/httpclient/lib/httpclient/http.rb +0 -867
  58. data/vendor/gems/httpclient/lib/httpclient/session.rb +0 -864
  59. data/vendor/gems/httpclient/lib/httpclient/ssl_config.rb +0 -417
  60. data/vendor/gems/httpclient/lib/httpclient/timeout.rb +0 -136
  61. data/vendor/gems/httpclient/lib/httpclient/util.rb +0 -86
  62. data/vendor/gems/httpclient/lib/httpclient.rb +0 -1020
  63. data/vendor/gems/httpclient/lib/tags +0 -908
@@ -1,1020 +0,0 @@
1
- # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
- #
4
- # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
- # redistribute it and/or modify it under the same terms of Ruby's license;
6
- # either the dual license version in 2003, or any later version.
7
-
8
-
9
- require 'uri'
10
- require 'stringio'
11
- require 'digest/sha1'
12
-
13
- # Extra library
14
- require 'httpclient/util'
15
- require 'httpclient/ssl_config'
16
- require 'httpclient/connection'
17
- require 'httpclient/session'
18
- require 'httpclient/http'
19
- require 'httpclient/auth'
20
- require 'httpclient/cookie'
21
-
22
-
23
- # The HTTPClient class provides several methods for accessing Web resources
24
- # via HTTP.
25
- #
26
- # HTTPClient instance is designed to be MT-safe. You can call a HTTPClient
27
- # instance from several threads without synchronization after setting up an
28
- # instance.
29
- #
30
- # clnt = HTTPClient.new
31
- # clnt.set_cookie_store('/home/nahi/cookie.dat')
32
- # urls.each do |url|
33
- # Thread.new(url) do |u|
34
- # p clnt.head(u).status
35
- # end
36
- # end
37
- #
38
- # == How to use
39
- #
40
- # At first, how to create your client. See initialize for more detail.
41
- #
42
- # 1. Create simple client.
43
- #
44
- # clnt = HTTPClient.new
45
- #
46
- # 2. Accessing resources through HTTP proxy. You can use environment
47
- # variable 'http_proxy' or 'HTTP_PROXY' instead.
48
- #
49
- # clnt = HTTPClient.new('http://myproxy:8080')
50
- #
51
- # === How to retrieve web resources
52
- #
53
- # See get_content.
54
- #
55
- # 1. Get content of specified URL. It returns a String of whole result.
56
- #
57
- # puts clnt.get_content('http://dev.ctor.org/')
58
- #
59
- # 2. Get content as chunks of String. It yields chunks of String.
60
- #
61
- # clnt.get_content('http://dev.ctor.org/') do |chunk|
62
- # puts chunk
63
- # end
64
- #
65
- # === Invoking other HTTP methods
66
- #
67
- # See head, get, post, put, delete, options, propfind, proppatch and trace.
68
- # It returns a HTTP::Message instance as a response.
69
- #
70
- # 1. Do HEAD request.
71
- #
72
- # res = clnt.head(uri)
73
- # p res.header['Last-Modified'][0]
74
- #
75
- # 2. Do GET request with query.
76
- #
77
- # query = { 'keyword' => 'ruby', 'lang' => 'en' }
78
- # res = clnt.get(uri, query)
79
- # p res.status
80
- # p res.contenttype
81
- # p res.header['X-Custom']
82
- # puts res.content
83
- #
84
- # === How to POST
85
- #
86
- # See post.
87
- #
88
- # 1. Do POST a form data.
89
- #
90
- # body = { 'keyword' => 'ruby', 'lang' => 'en' }
91
- # res = clnt.post(uri, body)
92
- #
93
- # 2. Do multipart file upload with POST. No need to set extra header by
94
- # yourself from httpclient/2.1.4.
95
- #
96
- # File.open('/tmp/post_data') do |file|
97
- # body = { 'upload' => file, 'user' => 'nahi' }
98
- # res = clnt.post(uri, body)
99
- # end
100
- #
101
- # === Accessing via SSL
102
- #
103
- # Ruby needs to be compiled with OpenSSL.
104
- #
105
- # 1. Get content of specified URL via SSL.
106
- # Just pass an URL which starts with 'https://'.
107
- #
108
- # https_url = 'https://www.rsa.com'
109
- # clnt.get_content(https_url)
110
- #
111
- # 2. Getting peer certificate from response.
112
- #
113
- # res = clnt.get(https_url)
114
- # p res.peer_cert #=> returns OpenSSL::X509::Certificate
115
- #
116
- # 3. Configuring OpenSSL options. See HTTPClient::SSLConfig for more details.
117
- #
118
- # user_cert_file = 'cert.pem'
119
- # user_key_file = 'privkey.pem'
120
- # clnt.ssl_config.set_client_cert_file(user_cert_file, user_key_file)
121
- # clnt.get_content(https_url)
122
- #
123
- # === Handling Cookies
124
- #
125
- # 1. Using volatile Cookies. Nothing to do. HTTPClient handles Cookies.
126
- #
127
- # clnt = HTTPClient.new
128
- # clnt.get_content(url1) # receives Cookies.
129
- # clnt.get_content(url2) # sends Cookies if needed.
130
- #
131
- # 2. Saving non volatile Cookies to a specified file. Need to set a file at
132
- # first and invoke save method at last.
133
- #
134
- # clnt = HTTPClient.new
135
- # clnt.set_cookie_store('/home/nahi/cookie.dat')
136
- # clnt.get_content(url)
137
- # ...
138
- # clnt.save_cookie_store
139
- #
140
- # 3. Disabling Cookies.
141
- #
142
- # clnt = HTTPClient.new
143
- # clnt.cookie_manager = nil
144
- #
145
- # === Configuring authentication credentials
146
- #
147
- # 1. Authentication with Web server. Supports BasicAuth, DigestAuth, and
148
- # Negotiate/NTLM (requires ruby/ntlm module).
149
- #
150
- # clnt = HTTPClient.new
151
- # domain = 'http://dev.ctor.org/http-access2/'
152
- # user = 'user'
153
- # password = 'user'
154
- # clnt.set_auth(domain, user, password)
155
- # p clnt.get_content('http://dev.ctor.org/http-access2/login').status
156
- #
157
- # 2. Authentication with Proxy server. Supports BasicAuth and NTLM
158
- # (requires win32/sspi)
159
- #
160
- # clnt = HTTPClient.new(proxy)
161
- # user = 'proxy'
162
- # password = 'proxy'
163
- # clnt.set_proxy_auth(user, password)
164
- # p clnt.get_content(url)
165
- #
166
- # === Invoking HTTP methods with custom header
167
- #
168
- # Pass a Hash or an Array for extheader argument.
169
- #
170
- # extheader = { 'Accept' => '*/*' }
171
- # clnt.get_content(uri, query, extheader)
172
- #
173
- # extheader = [['Accept', 'image/jpeg'], ['Accept', 'image/png']]
174
- # clnt.get_content(uri, query, extheader)
175
- #
176
- # === Invoking HTTP methods asynchronously
177
- #
178
- # See head_async, get_async, post_async, put_async, delete_async,
179
- # options_async, propfind_async, proppatch_async, and trace_async.
180
- # It immediately returns a HTTPClient::Connection instance as a returning value.
181
- #
182
- # connection = clnt.post_async(url, body)
183
- # print 'posting.'
184
- # while true
185
- # break if connection.finished?
186
- # print '.'
187
- # sleep 1
188
- # end
189
- # puts '.'
190
- # res = connection.pop
191
- # p res.status
192
- # p res.content.read # res.content is an IO for the res of async method.
193
- #
194
- # === Shortcut methods
195
- #
196
- # You can invoke get_content, get, etc. without creating HTTPClient instance.
197
- #
198
- # ruby -rhttpclient -e 'puts HTTPClient.get_content(ARGV.shift)' http://dev.ctor.org/
199
- # ruby -rhttpclient -e 'p HTTPClient.head(ARGV.shift).header["last-modified"]' http://dev.ctor.org/
200
- #
201
- class HTTPClient
202
- VERSION = '2.1.5'
203
- RUBY_VERSION_STRING = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
204
- /: (\S+) (\S+)/ =~ %q$Id: httpclient.rb 280 2009-06-02 15:44:28Z nahi $
205
- LIB_NAME = "(#{$1}/#{$2}, #{RUBY_VERSION_STRING})"
206
-
207
- include Util
208
-
209
- # Raised for indicating running environment configuration error for example
210
- # accessing via SSL under the ruby which is not compiled with OpenSSL.
211
- class ConfigurationError < StandardError
212
- end
213
-
214
- # Raised for indicating HTTP response error.
215
- class BadResponseError < RuntimeError
216
- # HTTP::Message:: a response
217
- attr_reader :res
218
-
219
- def initialize(msg, res = nil) # :nodoc:
220
- super(msg)
221
- @res = res
222
- end
223
- end
224
-
225
- # Raised for indicating a timeout error.
226
- class TimeoutError < RuntimeError
227
- end
228
-
229
- # Raised for indicating a connection timeout error.
230
- # You can configure connection timeout via HTTPClient#connect_timeout=.
231
- class ConnectTimeoutError < TimeoutError
232
- end
233
-
234
- # Raised for indicating a request sending timeout error.
235
- # You can configure request sending timeout via HTTPClient#send_timeout=.
236
- class SendTimeoutError < TimeoutError
237
- end
238
-
239
- # Raised for indicating a response receiving timeout error.
240
- # You can configure response receiving timeout via
241
- # HTTPClient#receive_timeout=.
242
- class ReceiveTimeoutError < TimeoutError
243
- end
244
-
245
- # Deprecated. just for backward compatibility
246
- class Session
247
- BadResponse = ::HTTPClient::BadResponseError
248
- end
249
-
250
- class << self
251
- %w(get_content post_content head get post put delete options propfind proppatch trace).each do |name|
252
- eval <<-EOD
253
- def #{name}(*arg, &block)
254
- clnt = new
255
- begin
256
- clnt.#{name}(*arg, &block)
257
- ensure
258
- clnt.reset_all
259
- end
260
- end
261
- EOD
262
- end
263
-
264
- private
265
-
266
- def attr_proxy(symbol, assignable = false)
267
- name = symbol.to_s
268
- define_method(name) {
269
- @session_manager.__send__(name)
270
- }
271
- if assignable
272
- aname = name + '='
273
- define_method(aname) { |rhs|
274
- reset_all
275
- @session_manager.__send__(aname, rhs)
276
- }
277
- end
278
- end
279
- end
280
-
281
- # HTTPClient::SSLConfig:: SSL configurator.
282
- attr_reader :ssl_config
283
- # WebAgent::CookieManager:: Cookies configurator.
284
- attr_accessor :cookie_manager
285
- # An array of response HTTP message body String which is used for loop-back
286
- # test. See test/* to see how to use it. If you want to do loop-back test
287
- # of HTTP header, use test_loopback_http_response instead.
288
- attr_reader :test_loopback_response
289
- # An array of request filter which can trap HTTP request/response.
290
- # See HTTPClient::WWWAuth to see how to use it.
291
- attr_reader :request_filter
292
- # HTTPClient::ProxyAuth:: Proxy authentication handler.
293
- attr_reader :proxy_auth
294
- # HTTPClient::WWWAuth:: WWW authentication handler.
295
- attr_reader :www_auth
296
- # How many times get_content and post_content follows HTTP redirect.
297
- # 10 by default.
298
- attr_accessor :follow_redirect_count
299
-
300
- # Set HTTP version as a String:: 'HTTP/1.0' or 'HTTP/1.1'
301
- attr_proxy(:protocol_version, true)
302
- # Connect timeout in sec.
303
- attr_proxy(:connect_timeout, true)
304
- # Request sending timeout in sec.
305
- attr_proxy(:send_timeout, true)
306
- # Response receiving timeout in sec.
307
- attr_proxy(:receive_timeout, true)
308
- # Negotiation retry count for authentication. 5 by default.
309
- attr_proxy(:protocol_retry_count, true)
310
- # if your ruby is older than 2005-09-06, do not set socket_sync = false to
311
- # avoid an SSL socket blocking bug in openssl/buffering.rb.
312
- attr_proxy(:socket_sync, true)
313
- # User-Agent header in HTTP request.
314
- attr_proxy(:agent_name, true)
315
- # From header in HTTP request.
316
- attr_proxy(:from, true)
317
- # An array of response HTTP String (not a HTTP message body) which is used
318
- # for loopback test. See test/* to see how to use it.
319
- attr_proxy(:test_loopback_http_response)
320
-
321
- # Default extheader for PROPFIND request.
322
- PROPFIND_DEFAULT_EXTHEADER = { 'Depth' => '0' }
323
-
324
- # Creates a HTTPClient instance which manages sessions, cookies, etc.
325
- #
326
- # HTTPClient.new takes 3 optional arguments for proxy url string,
327
- # User-Agent String and From header String. User-Agent and From are embedded
328
- # in HTTP request Header if given. No User-Agent and From header added
329
- # without setting it explicitly.
330
- #
331
- # proxy = 'http://myproxy:8080'
332
- # agent_name = 'MyAgent/0.1'
333
- # from = 'from@example.com'
334
- # HTTPClient.new(proxy, agent_name, from)
335
- #
336
- # You can use a keyword argument style Hash. Keys are :proxy, :agent_name
337
- # and :from.
338
- #
339
- # HTTPClient.new(:agent_name = 'MyAgent/0.1')
340
- def initialize(*args)
341
- proxy, agent_name, from = keyword_argument(args, :proxy, :agent_name, :from)
342
- @proxy = nil # assigned later.
343
- @no_proxy = nil
344
- @www_auth = WWWAuth.new
345
- @proxy_auth = ProxyAuth.new
346
- @request_filter = [@proxy_auth, @www_auth]
347
- @debug_dev = nil
348
- @redirect_uri_callback = method(:default_redirect_uri_callback)
349
- @test_loopback_response = []
350
- @session_manager = SessionManager.new(self)
351
- @session_manager.agent_name = agent_name
352
- @session_manager.from = from
353
- @session_manager.ssl_config = @ssl_config = SSLConfig.new(self)
354
- @cookie_manager = WebAgent::CookieManager.new
355
- @follow_redirect_count = 10
356
- load_environment
357
- self.proxy = proxy if proxy
358
- end
359
-
360
- # Returns debug device if exists. See debug_dev=.
361
- def debug_dev
362
- @debug_dev
363
- end
364
-
365
- # Sets debug device. Once debug device is set, all HTTP requests and
366
- # responses are dumped to given device. dev must respond to << for dump.
367
- #
368
- # Calling this method resets all existing sessions.
369
- def debug_dev=(dev)
370
- @debug_dev = dev
371
- reset_all
372
- @session_manager.debug_dev = dev
373
- end
374
-
375
- # Returns URI object of HTTP proxy if exists.
376
- def proxy
377
- @proxy
378
- end
379
-
380
- # Sets HTTP proxy used for HTTP connection. Given proxy can be an URI,
381
- # a String or nil. You can set user/password for proxy authentication like
382
- # HTTPClient#proxy = 'http://user:passwd@myproxy:8080'
383
- #
384
- # You can use environment variable 'http_proxy' or 'HTTP_PROXY' for it.
385
- # You need to use 'cgi_http_proxy' or 'CGI_HTTP_PROXY' instead if you run
386
- # HTTPClient from CGI environment from security reason. (HTTPClient checks
387
- # 'REQUEST_METHOD' environment variable whether it's CGI or not)
388
- #
389
- # Calling this method resets all existing sessions.
390
- def proxy=(proxy)
391
- if proxy.nil?
392
- @proxy = nil
393
- @proxy_auth.reset_challenge
394
- else
395
- @proxy = urify(proxy)
396
- if @proxy.scheme == nil or @proxy.scheme.downcase != 'http' or
397
- @proxy.host == nil or @proxy.port == nil
398
- raise ArgumentError.new("unsupported proxy #{proxy}")
399
- end
400
- @proxy_auth.reset_challenge
401
- if @proxy.user || @proxy.password
402
- @proxy_auth.set_auth(@proxy.user, @proxy.password)
403
- end
404
- end
405
- reset_all
406
- @session_manager.proxy = @proxy
407
- @proxy
408
- end
409
-
410
- # Returns NO_PROXY setting String if given.
411
- def no_proxy
412
- @no_proxy
413
- end
414
-
415
- # Sets NO_PROXY setting String. no_proxy must be a comma separated String.
416
- # Each entry must be 'host' or 'host:port' such as;
417
- # HTTPClient#no_proxy = 'example.com,example.co.jp:443'
418
- #
419
- # 'localhost' is treated as a no_proxy site regardless of explicitly listed.
420
- # HTTPClient checks given URI objects before accessing it.
421
- # 'host' is tail string match. No IP-addr conversion.
422
- #
423
- # You can use environment variable 'no_proxy' or 'NO_PROXY' for it.
424
- #
425
- # Calling this method resets all existing sessions.
426
- def no_proxy=(no_proxy)
427
- @no_proxy = no_proxy
428
- reset_all
429
- end
430
-
431
- # Sets credential for Web server authentication.
432
- # domain:: a String or an URI to specify where HTTPClient should use this
433
- # credential. If you set uri to nil, HTTPClient uses this credential
434
- # wherever a server requires it.
435
- # user:: username String.
436
- # passwd:: password String.
437
- #
438
- # You can set multiple credentials for each uri.
439
- #
440
- # clnt.set_auth('http://www.example.com/foo/', 'foo_user', 'passwd')
441
- # clnt.set_auth('http://www.example.com/bar/', 'bar_user', 'passwd')
442
- #
443
- # Calling this method resets all existing sessions.
444
- def set_auth(domain, user, passwd)
445
- uri = urify(domain)
446
- @www_auth.set_auth(uri, user, passwd)
447
- reset_all
448
- end
449
-
450
- # Deprecated. Use set_auth instead.
451
- def set_basic_auth(domain, user, passwd)
452
- uri = urify(domain)
453
- @www_auth.basic_auth.set(uri, user, passwd)
454
- reset_all
455
- end
456
-
457
- # Sets credential for Proxy authentication.
458
- # user:: username String.
459
- # passwd:: password String.
460
- #
461
- # Calling this method resets all existing sessions.
462
- def set_proxy_auth(user, passwd)
463
- @proxy_auth.set_auth(user, passwd)
464
- reset_all
465
- end
466
-
467
- # Sets the filename where non-volatile Cookies be saved by calling
468
- # save_cookie_store.
469
- # This method tries to load and managing Cookies from the specified file.
470
- #
471
- # Calling this method resets all existing sessions.
472
- def set_cookie_store(filename)
473
- @cookie_manager.cookies_file = filename
474
- @cookie_manager.load_cookies if filename
475
- reset_all
476
- end
477
-
478
- # Try to save Cookies to the file specified in set_cookie_store. Unexpected
479
- # error will be raised if you don't call set_cookie_store first.
480
- # (interface mismatch between WebAgent::CookieManager implementation)
481
- def save_cookie_store
482
- @cookie_manager.save_cookies
483
- end
484
-
485
- # Sets callback proc when HTTP redirect status is returned for get_content
486
- # and post_content. default_redirect_uri_callback is used by default.
487
- #
488
- # If you need strict implementation which does not allow relative URI
489
- # redirection, set strict_redirect_uri_callback instead.
490
- #
491
- # clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
492
- #
493
- def redirect_uri_callback=(redirect_uri_callback)
494
- @redirect_uri_callback = redirect_uri_callback
495
- end
496
-
497
- # Retrieves a web resource.
498
- #
499
- # uri:: a String or an URI object which represents an URL of web resource.
500
- # query:: a Hash or an Array of query part of URL.
501
- # e.g. { "a" => "b" } => 'http://host/part?a=b'.
502
- # Give an array to pass multiple value like
503
- # [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'.
504
- # extheader:: a Hash or an Array of extra headers. e.g.
505
- # { 'Accept' => '*/*' } or
506
- # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
507
- # &block:: Give a block to get chunked message-body of response like
508
- # get_content(uri) { |chunked_body| ... }.
509
- # Size of each chunk may not be the same.
510
- #
511
- # get_content follows HTTP redirect status (see HTTP::Status.redirect?)
512
- # internally and try to retrieve content from redirected URL. See
513
- # redirect_uri_callback= how HTTP redirection is handled.
514
- #
515
- # If you need to get full HTTP response including HTTP status and headers,
516
- # use get method. get returns HTTP::Message as a response and you need to
517
- # follow HTTP redirect by yourself if you need.
518
- def get_content(uri, query = nil, extheader = {}, &block)
519
- follow_redirect(:get, uri, query, nil, extheader, &block).content
520
- end
521
-
522
- # Posts a content.
523
- #
524
- # uri:: a String or an URI object which represents an URL of web resource.
525
- # body:: a Hash or an Array of body part.
526
- # e.g. { "a" => "b" } => 'a=b'.
527
- # Give an array to pass multiple value like
528
- # [["a", "b"], ["a", "c"]] => 'a=b&a=c'.
529
- # When you pass a File as a value, it will be posted as a
530
- # multipart/form-data. e.g. { 'upload' => file }
531
- # extheader:: a Hash or an Array of extra headers. e.g.
532
- # { 'Accept' => '*/*' } or
533
- # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
534
- # &block:: Give a block to get chunked message-body of response like
535
- # post_content(uri) { |chunked_body| ... }.
536
- # Size of each chunk may not be the same.
537
- #
538
- # post_content follows HTTP redirect status (see HTTP::Status.redirect?)
539
- # internally and try to post the content to redirected URL. See
540
- # redirect_uri_callback= how HTTP redirection is handled.
541
- #
542
- # If you need to get full HTTP response including HTTP status and headers,
543
- # use post method.
544
- def post_content(uri, body = nil, extheader = {}, &block)
545
- follow_redirect(:post, uri, nil, body, extheader, &block).content
546
- end
547
-
548
- # A method for redirect uri callback. How to use:
549
- # clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
550
- # This callback does not allow relative redirect such as
551
- # Location: ../foo/
552
- # in HTTP header. (raises BadResponseError instead)
553
- def strict_redirect_uri_callback(uri, res)
554
- newuri = URI.parse(res.header['location'][0])
555
- if https?(uri) && !https?(newuri)
556
- raise BadResponseError.new("redirecting to non-https resource")
557
- end
558
- unless newuri.is_a?(URI::HTTP)
559
- raise BadResponseError.new("unexpected location: #{newuri}", res)
560
- end
561
- puts "redirect to: #{newuri}" if $DEBUG
562
- newuri
563
- end
564
-
565
- # A default method for redirect uri callback. This method is used by
566
- # HTTPClient instance by default.
567
- # This callback allows relative redirect such as
568
- # Location: ../foo/
569
- # in HTTP header.
570
- def default_redirect_uri_callback(uri, res)
571
- newuri = URI.parse(res.header['location'][0])
572
- if https?(uri) && !https?(newuri)
573
- raise BadResponseError.new("redirecting to non-https resource")
574
- end
575
- unless newuri.is_a?(URI::HTTP)
576
- newuri = uri + newuri
577
- STDERR.puts("could be a relative URI in location header which is not recommended")
578
- STDERR.puts("'The field value consists of a single absolute URI' in HTTP spec")
579
- end
580
- puts "redirect to: #{newuri}" if $DEBUG
581
- newuri
582
- end
583
-
584
- # Sends HEAD request to the specified URL. See request for arguments.
585
- def head(uri, query = nil, extheader = {})
586
- request(:head, uri, query, nil, extheader)
587
- end
588
-
589
- # Sends GET request to the specified URL. See request for arguments.
590
- def get(uri, query = nil, extheader = {}, &block)
591
- request(:get, uri, query, nil, extheader, &block)
592
- end
593
-
594
- # Sends POST request to the specified URL. See request for arguments.
595
- def post(uri, body = '', extheader = {}, &block)
596
- request(:post, uri, nil, body, extheader, &block)
597
- end
598
-
599
- # Sends PUT request to the specified URL. See request for arguments.
600
- def put(uri, body = '', extheader = {}, &block)
601
- request(:put, uri, nil, body, extheader, &block)
602
- end
603
-
604
- # Sends DELETE request to the specified URL. See request for arguments.
605
- def delete(uri, extheader = {}, &block)
606
- request(:delete, uri, nil, nil, extheader, &block)
607
- end
608
-
609
- # Sends OPTIONS request to the specified URL. See request for arguments.
610
- def options(uri, extheader = {}, &block)
611
- request(:options, uri, nil, nil, extheader, &block)
612
- end
613
-
614
- # Sends PROPFIND request to the specified URL. See request for arguments.
615
- def propfind(uri, extheader = PROPFIND_DEFAULT_EXTHEADER, &block)
616
- request(:propfind, uri, nil, nil, extheader, &block)
617
- end
618
-
619
- # Sends PROPPATCH request to the specified URL. See request for arguments.
620
- def proppatch(uri, body = nil, extheader = {}, &block)
621
- request(:proppatch, uri, nil, body, extheader, &block)
622
- end
623
-
624
- # Sends TRACE request to the specified URL. See request for arguments.
625
- def trace(uri, query = nil, body = nil, extheader = {}, &block)
626
- request('TRACE', uri, query, body, extheader, &block)
627
- end
628
-
629
- # Sends a request to the specified URL.
630
- #
631
- # method:: HTTP method to be sent. method.to_s.upcase is used.
632
- # uri:: a String or an URI object which represents an URL of web resource.
633
- # query:: a Hash or an Array of query part of URL.
634
- # e.g. { "a" => "b" } => 'http://host/part?a=b'
635
- # Give an array to pass multiple value like
636
- # [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'
637
- # body:: a Hash or an Array of body part.
638
- # e.g. { "a" => "b" } => 'a=b'.
639
- # Give an array to pass multiple value like
640
- # [["a", "b"], ["a", "c"]] => 'a=b&a=c'.
641
- # When the given method is 'POST' and the given body contains a file
642
- # as a value, it will be posted as a multipart/form-data.
643
- # e.g. { 'upload' => file }
644
- # See HTTP::Message.file? for actual condition of 'a file'.
645
- # extheader:: a Hash or an Array of extra headers. e.g.
646
- # { 'Accept' => '*/*' } or
647
- # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
648
- # &block:: Give a block to get chunked message-body of response like
649
- # get(uri) { |chunked_body| ... }.
650
- # Size of each chunk may not be the same.
651
- #
652
- # You can also pass a String as a body. HTTPClient just sends a String as
653
- # a HTTP request message body.
654
- #
655
- # When you pass an IO as a body, HTTPClient sends it as a HTTP request with
656
- # chunked encoding (Transfer-Encoding: chunked in HTTP header). Bear in mind
657
- # that some server application does not support chunked request. At least
658
- # cgi.rb does not support it.
659
- def request(method, uri, query = nil, body = nil, extheader = {}, &block)
660
- uri = urify(uri)
661
- if block
662
- filtered_block = proc { |res, str|
663
- block.call(str)
664
- }
665
- end
666
- do_request(method, uri, query, body, extheader, &filtered_block)
667
- end
668
-
669
- # Sends HEAD request in async style. See request_async for arguments.
670
- # It immediately returns a HTTPClient::Connection instance as a result.
671
- def head_async(uri, query = nil, extheader = {})
672
- request_async(:head, uri, query, nil, extheader)
673
- end
674
-
675
- # Sends GET request in async style. See request_async for arguments.
676
- # It immediately returns a HTTPClient::Connection instance as a result.
677
- def get_async(uri, query = nil, extheader = {})
678
- request_async(:get, uri, query, nil, extheader)
679
- end
680
-
681
- # Sends POST request in async style. See request_async for arguments.
682
- # It immediately returns a HTTPClient::Connection instance as a result.
683
- def post_async(uri, body = nil, extheader = {})
684
- request_async(:post, uri, nil, body, extheader)
685
- end
686
-
687
- # Sends PUT request in async style. See request_async for arguments.
688
- # It immediately returns a HTTPClient::Connection instance as a result.
689
- def put_async(uri, body = nil, extheader = {})
690
- request_async(:put, uri, nil, body, extheader)
691
- end
692
-
693
- # Sends DELETE request in async style. See request_async for arguments.
694
- # It immediately returns a HTTPClient::Connection instance as a result.
695
- def delete_async(uri, extheader = {})
696
- request_async(:delete, uri, nil, nil, extheader)
697
- end
698
-
699
- # Sends OPTIONS request in async style. See request_async for arguments.
700
- # It immediately returns a HTTPClient::Connection instance as a result.
701
- def options_async(uri, extheader = {})
702
- request_async(:options, uri, nil, nil, extheader)
703
- end
704
-
705
- # Sends PROPFIND request in async style. See request_async for arguments.
706
- # It immediately returns a HTTPClient::Connection instance as a result.
707
- def propfind_async(uri, extheader = PROPFIND_DEFAULT_EXTHEADER)
708
- request_async(:propfind, uri, nil, nil, extheader)
709
- end
710
-
711
- # Sends PROPPATCH request in async style. See request_async for arguments.
712
- # It immediately returns a HTTPClient::Connection instance as a result.
713
- def proppatch_async(uri, body = nil, extheader = {})
714
- request_async(:proppatch, uri, nil, body, extheader)
715
- end
716
-
717
- # Sends TRACE request in async style. See request_async for arguments.
718
- # It immediately returns a HTTPClient::Connection instance as a result.
719
- def trace_async(uri, query = nil, body = nil, extheader = {})
720
- request_async(:trace, uri, query, body, extheader)
721
- end
722
-
723
- # Sends a request in async style. request method creates new Thread for
724
- # HTTP connection and returns a HTTPClient::Connection instance immediately.
725
- #
726
- # Arguments definition is the same as request.
727
- def request_async(method, uri, query = nil, body = nil, extheader = {})
728
- uri = urify(uri)
729
- do_request_async(method, uri, query, body, extheader)
730
- end
731
-
732
- # Resets internal session for the given URL. Keep-alive connection for the
733
- # site (host-port pair) is disconnected if exists.
734
- def reset(uri)
735
- uri = urify(uri)
736
- @session_manager.reset(uri)
737
- end
738
-
739
- # Resets all of internal sessions. Keep-alive connections are disconnected.
740
- def reset_all
741
- @session_manager.reset_all
742
- end
743
-
744
- private
745
-
746
- class RetryableResponse < StandardError # :nodoc:
747
- end
748
-
749
- class KeepAliveDisconnected < StandardError # :nodoc:
750
- end
751
-
752
- def do_request(method, uri, query, body, extheader, &block)
753
- conn = Connection.new
754
- res = nil
755
- if HTTP::Message.file?(body)
756
- pos = body.pos rescue nil
757
- end
758
- retry_count = @session_manager.protocol_retry_count
759
- proxy = no_proxy?(uri) ? nil : @proxy
760
- while retry_count > 0
761
- body.pos = pos if pos
762
- req = create_request(method, uri, query, body, extheader)
763
- begin
764
- protect_keep_alive_disconnected do
765
- do_get_block(req, proxy, conn, &block)
766
- end
767
- res = conn.pop
768
- break
769
- rescue RetryableResponse
770
- res = conn.pop
771
- retry_count -= 1
772
- end
773
- end
774
- res
775
- end
776
-
777
- def do_request_async(method, uri, query, body, extheader)
778
- conn = Connection.new
779
- t = Thread.new(conn) { |tconn|
780
- if HTTP::Message.file?(body)
781
- pos = body.pos rescue nil
782
- end
783
- retry_count = @session_manager.protocol_retry_count
784
- proxy = no_proxy?(uri) ? nil : @proxy
785
- while retry_count > 0
786
- body.pos = pos if pos
787
- req = create_request(method, uri, query, body, extheader)
788
- begin
789
- protect_keep_alive_disconnected do
790
- do_get_stream(req, proxy, tconn)
791
- end
792
- break
793
- rescue RetryableResponse
794
- retry_count -= 1
795
- end
796
- end
797
- }
798
- conn.async_thread = t
799
- conn
800
- end
801
-
802
- def load_environment
803
- # http_proxy
804
- if getenv('REQUEST_METHOD')
805
- # HTTP_PROXY conflicts with the environment variable usage in CGI where
806
- # HTTP_* is used for HTTP header information. Unlike open-uri, we
807
- # simply ignore http_proxy in CGI env and use cgi_http_proxy instead.
808
- self.proxy = getenv('cgi_http_proxy')
809
- else
810
- self.proxy = getenv('http_proxy')
811
- end
812
- # no_proxy
813
- self.no_proxy = getenv('no_proxy')
814
- end
815
-
816
- def getenv(name)
817
- ENV[name.downcase] || ENV[name.upcase]
818
- end
819
-
820
- def follow_redirect(method, uri, query, body, extheader, &block)
821
- uri = urify(uri)
822
- if block
823
- filtered_block = proc { |r, str|
824
- block.call(str) if HTTP::Status.successful?(r.status)
825
- }
826
- end
827
- if HTTP::Message.file?(body)
828
- pos = body.pos rescue nil
829
- end
830
- retry_number = 0
831
- while retry_number < @follow_redirect_count
832
- body.pos = pos if pos
833
- res = do_request(method, uri, query, body, extheader, &filtered_block)
834
- if HTTP::Status.successful?(res.status)
835
- return res
836
- elsif HTTP::Status.redirect?(res.status)
837
- uri = urify(@redirect_uri_callback.call(uri, res))
838
- retry_number += 1
839
- else
840
- raise BadResponseError.new("unexpected response: #{res.header.inspect}", res)
841
- end
842
- end
843
- raise BadResponseError.new("retry count exceeded", res)
844
- end
845
-
846
- def protect_keep_alive_disconnected
847
- begin
848
- yield
849
- rescue KeepAliveDisconnected
850
- yield
851
- end
852
- end
853
-
854
- def create_request(method, uri, query, body, extheader)
855
- method = method.to_s.upcase
856
- if extheader.is_a?(Hash)
857
- extheader = extheader.to_a
858
- else
859
- extheader = extheader.dup
860
- end
861
- boundary = nil
862
- if body
863
- dummy, content_type = extheader.find { |key, value|
864
- key.downcase == 'content-type'
865
- }
866
- if content_type
867
- if /\Amultipart/ =~ content_type
868
- if content_type =~ /boundary=(.+)\z/
869
- boundary = $1
870
- else
871
- boundary = create_boundary
872
- content_type = "#{content_type}; boundary=#{boundary}"
873
- extheader = override_header(extheader, 'Content-Type', content_type)
874
- end
875
- end
876
- elsif method == 'POST'
877
- if file_in_form_data?(body)
878
- boundary = create_boundary
879
- content_type = "multipart/form-data; boundary=#{boundary}"
880
- else
881
- content_type = 'application/x-www-form-urlencoded'
882
- end
883
- extheader << ['Content-Type', content_type]
884
- end
885
- end
886
- req = HTTP::Message.new_request(method, uri, query, body, boundary)
887
- extheader.each do |key, value|
888
- req.header.add(key, value)
889
- end
890
- if @cookie_manager && cookie = @cookie_manager.find(uri)
891
- req.header.add('Cookie', cookie)
892
- end
893
- req
894
- end
895
-
896
- def create_boundary
897
- Digest::SHA1.hexdigest(Time.now.to_s)
898
- end
899
-
900
- def file_in_form_data?(body)
901
- HTTP::Message.multiparam_query?(body) &&
902
- body.any? { |k, v| HTTP::Message.file?(v) }
903
- end
904
-
905
- def override_header(extheader, key, value)
906
- result = []
907
- extheader.each do |k, v|
908
- if k.downcase == key.downcase
909
- result << [key, value]
910
- else
911
- result << [k, v]
912
- end
913
- end
914
- result
915
- end
916
-
917
- NO_PROXY_HOSTS = ['localhost']
918
-
919
- def no_proxy?(uri)
920
- if !@proxy or NO_PROXY_HOSTS.include?(uri.host)
921
- return true
922
- end
923
- unless @no_proxy
924
- return false
925
- end
926
- @no_proxy.scan(/([^:,]+)(?::(\d+))?/) do |host, port|
927
- if /(\A|\.)#{Regexp.quote(host)}\z/i =~ uri.host &&
928
- (!port || uri.port == port.to_i)
929
- return true
930
- end
931
- end
932
- false
933
- end
934
-
935
- def https?(uri)
936
- uri.scheme.downcase == 'https'
937
- end
938
-
939
- # !! CAUTION !!
940
- # Method 'do_get*' runs under MT conditon. Be careful to change.
941
- def do_get_block(req, proxy, conn, &block)
942
- @request_filter.each do |filter|
943
- filter.filter_request(req)
944
- end
945
- if str = @test_loopback_response.shift
946
- dump_dummy_request_response(req.body.dump, str) if @debug_dev
947
- conn.push(HTTP::Message.new_response(str))
948
- return
949
- end
950
- content = block ? nil : ''
951
- res = HTTP::Message.new_response(content)
952
- @debug_dev << "= Request\n\n" if @debug_dev
953
- sess = @session_manager.query(req, proxy)
954
- res.peer_cert = sess.ssl_peer_cert
955
- @debug_dev << "\n\n= Response\n\n" if @debug_dev
956
- do_get_header(req, res, sess)
957
- conn.push(res)
958
- sess.get_body do |part|
959
- if block
960
- block.call(res, part)
961
- else
962
- content << part
963
- end
964
- end
965
- @session_manager.keep(sess) unless sess.closed?
966
- commands = @request_filter.collect { |filter|
967
- filter.filter_response(req, res)
968
- }
969
- if commands.find { |command| command == :retry }
970
- raise RetryableResponse.new
971
- end
972
- end
973
-
974
- def do_get_stream(req, proxy, conn)
975
- @request_filter.each do |filter|
976
- filter.filter_request(req)
977
- end
978
- if str = @test_loopback_response.shift
979
- dump_dummy_request_response(req.body.dump, str) if @debug_dev
980
- conn.push(HTTP::Message.new_response(StringIO.new(str)))
981
- return
982
- end
983
- piper, pipew = IO.pipe
984
- res = HTTP::Message.new_response(piper)
985
- @debug_dev << "= Request\n\n" if @debug_dev
986
- sess = @session_manager.query(req, proxy)
987
- res.peer_cert = sess.ssl_peer_cert
988
- @debug_dev << "\n\n= Response\n\n" if @debug_dev
989
- do_get_header(req, res, sess)
990
- conn.push(res)
991
- sess.get_body do |part|
992
- pipew.syswrite(part)
993
- end
994
- pipew.close
995
- @session_manager.keep(sess) unless sess.closed?
996
- commands = @request_filter.collect { |filter|
997
- filter.filter_response(req, res)
998
- }
999
- # ignore commands (not retryable in async mode)
1000
- end
1001
-
1002
- def do_get_header(req, res, sess)
1003
- res.version, res.status, res.reason, headers = sess.get_header
1004
- headers.each do |key, value|
1005
- res.header.add(key, value)
1006
- end
1007
- if @cookie_manager
1008
- res.header['set-cookie'].each do |cookie|
1009
- @cookie_manager.parse(cookie, req.header.request_uri)
1010
- end
1011
- end
1012
- end
1013
-
1014
- def dump_dummy_request_response(req, res)
1015
- @debug_dev << "= Dummy Request\n\n"
1016
- @debug_dev << req
1017
- @debug_dev << "\n\n= Dummy Response\n\n"
1018
- @debug_dev << res
1019
- end
1020
- end