sevenwire-http_client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +159 -0
- data/Rakefile +48 -0
- data/VERSION.yml +4 -0
- data/bin/http_client +82 -0
- data/lib/http_client.rb +93 -0
- data/lib/http_client/exceptions.rb +39 -0
- data/lib/http_client/mixin/response.rb +43 -0
- data/lib/http_client/raw_response.rb +30 -0
- data/lib/http_client/request.rb +207 -0
- data/lib/http_client/resource.rb +146 -0
- data/lib/http_client/response.rb +20 -0
- data/spec/base.rb +4 -0
- data/spec/exceptions_spec.rb +12 -0
- data/spec/http_client_spec.rb +53 -0
- data/spec/mixin/response_spec.rb +46 -0
- data/spec/raw_response_spec.rb +17 -0
- data/spec/request_spec.rb +431 -0
- data/spec/resource_spec.rb +75 -0
- data/spec/response_spec.rb +16 -0
- metadata +88 -0
data/README.rdoc
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
= HTTP Client -- simple DSL for accessing HTTP resources
|
2
|
+
|
3
|
+
A simple HTTP client for Ruby, inspired by the Sinatra's microframework style
|
4
|
+
of specifying actions: get, put, post, delete.
|
5
|
+
|
6
|
+
== Usage: Raw URL
|
7
|
+
|
8
|
+
require 'http_client'
|
9
|
+
|
10
|
+
HttpClient.get 'http://example.com/resource'
|
11
|
+
HttpClient.get 'https://user:password@example.com/private/resource'
|
12
|
+
|
13
|
+
HttpClient.post 'http://example.com/resource', :param1 => 'one', :nested => { :param2 => 'two' }
|
14
|
+
|
15
|
+
HttpClient.delete 'http://example.com/resource'
|
16
|
+
|
17
|
+
See HttpClient module docs for details.
|
18
|
+
|
19
|
+
== Usage: ActiveResource-Style
|
20
|
+
|
21
|
+
resource = HttpClient::Resource.new 'http://example.com/resource'
|
22
|
+
resource.get
|
23
|
+
|
24
|
+
private_resource = HttpClient::Resource.new 'https://example.com/private/resource', :user => 'adam', :password => 'secret', :timeout => 20, :open_timeout => 5
|
25
|
+
private_resource.put File.read('pic.jpg'), :content_type => 'image/jpg'
|
26
|
+
|
27
|
+
See HttpClient::Resource module docs for details.
|
28
|
+
|
29
|
+
== Usage: Resource Nesting
|
30
|
+
|
31
|
+
site = HttpClient::Resource.new('http://example.com')
|
32
|
+
site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
|
33
|
+
|
34
|
+
See HttpClient::Resource docs for details.
|
35
|
+
|
36
|
+
== Shell
|
37
|
+
|
38
|
+
The http_client shell command gives an IRB session with HttpClient already loaded:
|
39
|
+
|
40
|
+
$ http_client
|
41
|
+
>> HttpClient.get 'http://example.com'
|
42
|
+
|
43
|
+
Specify a URL argument for get/post/put/delete on that resource:
|
44
|
+
|
45
|
+
$ http_client http://example.com
|
46
|
+
>> put '/resource', 'data'
|
47
|
+
|
48
|
+
Add a user and password for authenticated resources:
|
49
|
+
|
50
|
+
$ http_client https://example.com user pass
|
51
|
+
>> delete '/private/resource'
|
52
|
+
|
53
|
+
Create ~/.http_client for named sessions:
|
54
|
+
|
55
|
+
sinatra:
|
56
|
+
url: http://localhost:4567
|
57
|
+
rack:
|
58
|
+
url: http://localhost:9292
|
59
|
+
private_site:
|
60
|
+
url: http://example.com
|
61
|
+
username: user
|
62
|
+
password: pass
|
63
|
+
|
64
|
+
Then invoke:
|
65
|
+
|
66
|
+
$ http_client private_site
|
67
|
+
|
68
|
+
Use as a one-off, curl-style:
|
69
|
+
|
70
|
+
$ http_client get http://example.com/resource > output_body
|
71
|
+
|
72
|
+
$ http_client put http://example.com/resource < input_body
|
73
|
+
|
74
|
+
== Logging
|
75
|
+
|
76
|
+
Write calls to a log filename (can also be "stdout" or "stderr"):
|
77
|
+
|
78
|
+
HttpClient.log = '/tmp/http_client.log'
|
79
|
+
|
80
|
+
Or set an environment variable to avoid modifying the code:
|
81
|
+
|
82
|
+
$ HTTPCLIENT_LOG=stdout path/to/my/program
|
83
|
+
|
84
|
+
Either produces logs like this:
|
85
|
+
|
86
|
+
HttpClient.get "http://some/resource"
|
87
|
+
# => 200 OK | text/html 250 bytes
|
88
|
+
HttpClient.put "http://some/resource", "payload"
|
89
|
+
# => 401 Unauthorized | application/xml 340 bytes
|
90
|
+
|
91
|
+
Note that these logs are valid Ruby, so you can paste them into the http_client
|
92
|
+
shell or a script to replay your sequence of http calls.
|
93
|
+
|
94
|
+
== Proxy
|
95
|
+
|
96
|
+
All calls to HttpClient, including Resources, will use the proxy specified by
|
97
|
+
HttpClient.proxy:
|
98
|
+
|
99
|
+
HttpClient.proxy = "http://proxy.example.com/"
|
100
|
+
HttpClient.get "http://some/resource"
|
101
|
+
# => response from some/resource as proxied through proxy.example.com
|
102
|
+
|
103
|
+
Often the proxy url is set in an environment variable, so you can do this to
|
104
|
+
use whatever proxy the system is configured to use:
|
105
|
+
|
106
|
+
HttpClient.proxy = ENV['http_proxy']
|
107
|
+
|
108
|
+
== Cookies
|
109
|
+
|
110
|
+
Request and Response objects know about HTTP cookies, and will automatically
|
111
|
+
extract and set headers for them as needed:
|
112
|
+
|
113
|
+
response = HttpClient.get 'http://example.com/action_which_sets_session_id'
|
114
|
+
response.cookies
|
115
|
+
# => {"_applicatioN_session_id" => "1234"}
|
116
|
+
|
117
|
+
response2 = HttpClient.post(
|
118
|
+
'http://localhost:3000/',
|
119
|
+
{:param1 => "foo"},
|
120
|
+
{:cookies => {:session_id => "1234"}}
|
121
|
+
)
|
122
|
+
# ...response body
|
123
|
+
|
124
|
+
== SSL Client Certificates
|
125
|
+
|
126
|
+
HttpClient::Resource.new(
|
127
|
+
'https://example.com',
|
128
|
+
:ssl_client_cert => OpenSSL::X509::Certificate.new(File.read("cert.pem")),
|
129
|
+
:ssl_client_key => OpenSSL::PKey::RSA.new(File.read("key.pem"), "passphrase, if any"),
|
130
|
+
:ssl_ca_file => "ca_certificate.pem",
|
131
|
+
:verify_ssl => OpenSSL::SSL::VERIFY_PEER
|
132
|
+
).get
|
133
|
+
|
134
|
+
Self-signed certificates can be generated with the openssl command-line tool.
|
135
|
+
|
136
|
+
== Meta
|
137
|
+
|
138
|
+
This library began its life as RestClient. Due to some changes and the desire
|
139
|
+
to not conflict with the original library, Sevenwire forked and renamed it.
|
140
|
+
|
141
|
+
RestClient was written by Adam Wiggins (adam at heroku dot com)
|
142
|
+
|
143
|
+
RestClient Patches contributed by: Chris Anderson, Greg Borenstein, Ardekantur, Pedro
|
144
|
+
Belo, Rafael Souza, Rick Olson, Aman Gupta, Blake Mizerany, Brian Donovan, Ivan
|
145
|
+
Makfinsky, Marc-André Cournoyer, Coda Hale, Tetsuo Watanabe, Dusty Doris,
|
146
|
+
Lennon Day-Reynolds, James Edward Gray II, Cyril Rohr, Juan Alvarez, and Adam
|
147
|
+
Jacob
|
148
|
+
|
149
|
+
Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
|
150
|
+
|
151
|
+
RestClient can be found at:
|
152
|
+
|
153
|
+
http://rest-client.heroku.com
|
154
|
+
|
155
|
+
http://github.com/adamwiggins/rest-client
|
156
|
+
|
157
|
+
HttpClient can be found at:
|
158
|
+
|
159
|
+
http://github.com/sevenwire/http-client
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "http_client"
|
8
|
+
gem.summary = %Q{Simple HTTP/REST client for Ruby, inspired by microframework syntax for specifying actions. Forked from RestClient http://rest-client.heroku.com see README for reasons}
|
9
|
+
gem.email = "nate@sevenwire.com"
|
10
|
+
gem.homepage = "http://github.com/sevenwire/http_client"
|
11
|
+
gem.authors = ["Adam Wiggins", "Nate Sutton"]
|
12
|
+
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'spec/rake/spectask'
|
20
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
21
|
+
spec.libs << 'lib' << 'spec'
|
22
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
23
|
+
end
|
24
|
+
|
25
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
26
|
+
spec.libs << 'lib' << 'spec'
|
27
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
28
|
+
spec.rcov = true
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
task :default => :spec
|
33
|
+
|
34
|
+
require 'rake/rdoctask'
|
35
|
+
Rake::RDocTask.new do |rdoc|
|
36
|
+
if File.exist?('VERSION.yml')
|
37
|
+
config = YAML.load(File.read('VERSION.yml'))
|
38
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
39
|
+
else
|
40
|
+
version = ""
|
41
|
+
end
|
42
|
+
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "http-client #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
48
|
+
|
data/VERSION.yml
ADDED
data/bin/http_client
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
4
|
+
require 'http_client'
|
5
|
+
|
6
|
+
require "yaml"
|
7
|
+
|
8
|
+
def usage(why = nil)
|
9
|
+
puts "failed for reason: #{why}" if why
|
10
|
+
puts "usage: http_client [get|put|post|delete] url|name [username] [password]"
|
11
|
+
puts " The verb is optional, if you leave it off you'll get an interactive shell."
|
12
|
+
puts " put and post both take the input body on stdin."
|
13
|
+
exit(1)
|
14
|
+
end
|
15
|
+
|
16
|
+
if %w(get put post delete).include? ARGV.first
|
17
|
+
@verb = ARGV.shift
|
18
|
+
else
|
19
|
+
@verb = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
@url = ARGV.shift || 'http://localhost:4567'
|
23
|
+
|
24
|
+
config = YAML.load(File.read(ENV['HOME'] + "/.http_client")) rescue {}
|
25
|
+
|
26
|
+
@url, @username, @password = if c = config[@url]
|
27
|
+
[c['url'], c['username'], c['password']]
|
28
|
+
else
|
29
|
+
[@url, *ARGV]
|
30
|
+
end
|
31
|
+
|
32
|
+
usage("invalid url '#{@url}") unless @url =~ /^https?/
|
33
|
+
usage("too few args") unless ARGV.size < 3
|
34
|
+
|
35
|
+
def r
|
36
|
+
@r ||= HttpClient::Resource.new(@url, @username, @password)
|
37
|
+
end
|
38
|
+
|
39
|
+
r # force rc to load
|
40
|
+
|
41
|
+
if @verb
|
42
|
+
begin
|
43
|
+
if %w(put post).include? @verb
|
44
|
+
puts r.send(@verb, STDIN.read)
|
45
|
+
else
|
46
|
+
puts r.send(@verb)
|
47
|
+
end
|
48
|
+
exit 0
|
49
|
+
rescue HttpClient::Exception => e
|
50
|
+
puts e.response.body if e.respond_to? :response
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
%w(get post put delete).each do |m|
|
56
|
+
eval <<-end_eval
|
57
|
+
def #{m}(path, *args, &b)
|
58
|
+
r[path].#{m}(*args, &b)
|
59
|
+
end
|
60
|
+
end_eval
|
61
|
+
end
|
62
|
+
|
63
|
+
def method_missing(s, *args, &b)
|
64
|
+
super unless r.respond_to?(s)
|
65
|
+
r.send(s, *args, &b)
|
66
|
+
end
|
67
|
+
|
68
|
+
require 'irb'
|
69
|
+
require 'irb/completion'
|
70
|
+
|
71
|
+
if File.exists? ".irbrc"
|
72
|
+
ENV['IRBRC'] = ".irbrc"
|
73
|
+
end
|
74
|
+
|
75
|
+
if File.exists?(rcfile = "~/.http_clientrc")
|
76
|
+
load(rcfile)
|
77
|
+
end
|
78
|
+
|
79
|
+
ARGV.clear
|
80
|
+
|
81
|
+
IRB.start
|
82
|
+
exit!
|
data/lib/http_client.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/https'
|
3
|
+
require 'zlib'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
require File.dirname(__FILE__) + '/http_client/request'
|
7
|
+
require File.dirname(__FILE__) + '/http_client/mixin/response'
|
8
|
+
require File.dirname(__FILE__) + '/http_client/response'
|
9
|
+
require File.dirname(__FILE__) + '/http_client/raw_response'
|
10
|
+
require File.dirname(__FILE__) + '/http_client/resource'
|
11
|
+
require File.dirname(__FILE__) + '/http_client/exceptions'
|
12
|
+
|
13
|
+
# This module's static methods are the entry point for using the HTTP client.
|
14
|
+
#
|
15
|
+
# # GET
|
16
|
+
# xml = HttpClient.get 'http://example.com/resource'
|
17
|
+
# jpg = HttpClient.get 'http://example.com/resource', :accept => 'image/jpg'
|
18
|
+
#
|
19
|
+
# # authentication and SSL
|
20
|
+
# HttpClient.get 'https://user:password@example.com/private/resource'
|
21
|
+
#
|
22
|
+
# # POST or PUT with a hash sends parameters as a urlencoded form body
|
23
|
+
# HttpClient.post 'http://example.com/resource', :param1 => 'one'
|
24
|
+
#
|
25
|
+
# # nest hash parameters
|
26
|
+
# HttpClient.post 'http://example.com/resource', :nested => { :param1 => 'one' }
|
27
|
+
#
|
28
|
+
# # POST and PUT with raw payloads
|
29
|
+
# HttpClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
|
30
|
+
# HttpClient.post 'http://example.com/resource.xml', xml_doc
|
31
|
+
# HttpClient.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'
|
32
|
+
#
|
33
|
+
# # DELETE
|
34
|
+
# HttpClient.delete 'http://example.com/resource'
|
35
|
+
#
|
36
|
+
# # retreive the response http code and headers
|
37
|
+
# res = HttpClient.get 'http://example.com/some.jpg'
|
38
|
+
# res.code # => 200
|
39
|
+
# res.headers[:content_type] # => 'image/jpg'
|
40
|
+
#
|
41
|
+
# # HEAD
|
42
|
+
# HttpClient.head('http://example.com').headers
|
43
|
+
#
|
44
|
+
# To use with a proxy, just set HttpClient.proxy to the proper http proxy:
|
45
|
+
#
|
46
|
+
# HttpClient.proxy = "http://proxy.example.com/"
|
47
|
+
#
|
48
|
+
# Or inherit the proxy from the environment:
|
49
|
+
#
|
50
|
+
# HttpClient.proxy = ENV['http_proxy']
|
51
|
+
#
|
52
|
+
# For live tests of HttpClient, try using http://http-test.heroku.com, which echoes back information about the http call:
|
53
|
+
#
|
54
|
+
# >> HttpClient.put 'http://http-test.heroku.com/resource', :foo => 'baz'
|
55
|
+
# => "PUT http://http-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}"
|
56
|
+
#
|
57
|
+
module HttpClient
|
58
|
+
def self.get(url, headers={})
|
59
|
+
Request.execute(:method => :get, :url => url, :headers => headers)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.post(url, payload, headers={})
|
63
|
+
Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.put(url, payload, headers={})
|
67
|
+
Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.delete(url, headers={})
|
71
|
+
Request.execute(:method => :delete, :url => url, :headers => headers)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.head(url, headers={})
|
75
|
+
Request.execute(:method => :head, :url => url, :headers => headers)
|
76
|
+
end
|
77
|
+
|
78
|
+
class << self
|
79
|
+
attr_accessor :proxy
|
80
|
+
end
|
81
|
+
|
82
|
+
# Print log of HttpClient calls. Value can be stdout, stderr, or a filename.
|
83
|
+
# You can also configure logging by the environment variable HTTPCLIENT_LOG.
|
84
|
+
def self.log=(log)
|
85
|
+
@@log = log
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.log # :nodoc:
|
89
|
+
return ENV['HTTPCLIENT_LOG'] if ENV['HTTPCLIENT_LOG']
|
90
|
+
return @@log if defined? @@log
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module HttpClient
|
2
|
+
# This is the base HttpClient exception class. Rescue it if you want to
|
3
|
+
# catch any exception that your request might raise
|
4
|
+
class Exception < StandardError
|
5
|
+
def message(default=nil)
|
6
|
+
self.class::ErrorMessage
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Base HttpClient exception when there's a response available
|
11
|
+
class ExceptionWithResponse < Exception
|
12
|
+
attr_accessor :response
|
13
|
+
|
14
|
+
def initialize(response=nil)
|
15
|
+
@response = response
|
16
|
+
end
|
17
|
+
|
18
|
+
def http_code
|
19
|
+
@response.code.to_i if @response
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# The server broke the connection prior to the request completing. Usually
|
24
|
+
# this means it crashed, or sometimes that your network connection was
|
25
|
+
# severed before it could complete.
|
26
|
+
class ServerBrokeConnection < Exception
|
27
|
+
ErrorMessage = 'Server broke connection'
|
28
|
+
end
|
29
|
+
|
30
|
+
# The server took too long to respond.
|
31
|
+
class RequestTimeout < Exception
|
32
|
+
ErrorMessage = 'Request timed out'
|
33
|
+
end
|
34
|
+
|
35
|
+
# The server refused the connection
|
36
|
+
class ConnectionRefused < Exception
|
37
|
+
ErrorMessage = 'Server refused connection'
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module HttpClient
|
2
|
+
module Mixin
|
3
|
+
module Response
|
4
|
+
attr_reader :net_http_res
|
5
|
+
|
6
|
+
# HTTP status code, always 200 since HttpClient throws exceptions for
|
7
|
+
# other codes.
|
8
|
+
def code
|
9
|
+
@code ||= @net_http_res.code.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
# A hash of the headers, beautified with symbols and underscores.
|
13
|
+
# e.g. "Content-type" will become :content_type.
|
14
|
+
def headers
|
15
|
+
@headers ||= self.class.beautify_headers(@net_http_res.to_hash)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Hash of cookies extracted from response headers
|
19
|
+
def cookies
|
20
|
+
@cookies ||= (self.headers[:set_cookie] || "").split('; ').inject({}) do |out, raw_c|
|
21
|
+
key, val = raw_c.split('=')
|
22
|
+
unless %w(expires domain path secure).member?(key)
|
23
|
+
out[key] = val
|
24
|
+
end
|
25
|
+
out
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.included(receiver)
|
30
|
+
receiver.extend(HttpClient::Mixin::Response::ClassMethods)
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
def beautify_headers(headers)
|
35
|
+
headers.inject({}) do |out, (key, value)|
|
36
|
+
out[key.gsub(/-/, '_').to_sym] = value.first
|
37
|
+
out
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|