francois-rest-client 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,104 @@
1
+ = REST Client -- simple DSL for accessing REST resources
2
+
3
+ A simple REST 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 'rest_client'
9
+
10
+ RestClient.get 'http://example.com/resource'
11
+ RestClient.get 'https://user:password@example.com/private/resource'
12
+
13
+ RestClient.post 'http://example.com/resource', :param1 => 'one', :nested => { :param2 => 'two' }
14
+
15
+ RestClient.delete 'http://example.com/resource'
16
+
17
+ == Multipart
18
+
19
+ Yeah, that's right! This does multipart sends for you!
20
+
21
+ RestClient.post '/data', :myfile => File.new("/path/to/image.jpg")
22
+
23
+ This does two things for you:
24
+
25
+ * Auto-detects that you have a File value sends it as multipart
26
+ * Auto-detects the mime of the file and sets it in the HEAD of the payload for each entry
27
+
28
+ If you are sending params that do not contain a File object but the payload needs to be multipart then:
29
+
30
+ RestClient.post '/data', :foo => 'bar', :multipart => true
31
+
32
+ == Streaming downloads
33
+
34
+ RestClient.get('http://some/resource/lotsofdata') do |res|
35
+ res.read_body do |chunk|
36
+ .. do something with chunk ..
37
+ end
38
+ end
39
+
40
+ See RestClient module docs for more details.
41
+
42
+ == Usage: ActiveResource-Style
43
+
44
+ resource = RestClient::Resource.new 'http://example.com/resource'
45
+ resource.get
46
+
47
+ private_resource = RestClient::Resource.new 'https://example.com/private/resource', 'user', 'pass'
48
+ private_resource.put File.read('pic.jpg'), :content_type => 'image/jpg'
49
+
50
+ See RestClient::Resource module docs for details.
51
+
52
+ == Usage: Resource Nesting
53
+
54
+ site = RestClient::Resource.new('http://example.com')
55
+ site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
56
+
57
+ See RestClient::Resource docs for details.
58
+
59
+ == Shell
60
+
61
+ The restclient shell command gives an IRB session with RestClient already loaded:
62
+
63
+ $ restclient
64
+ >> RestClient.get 'http://example.com'
65
+
66
+ Specify a URL argument for get/post/put/delete on that resource:
67
+
68
+ $ restclient http://example.com
69
+ >> put '/resource', 'data'
70
+
71
+ Add a user and password for authenticated resources:
72
+
73
+ $ restclient https://example.com user pass
74
+ >> delete '/private/resource'
75
+
76
+ Create ~/.restclient for named sessions:
77
+
78
+ sinatra:
79
+ url: http://localhost:4567
80
+ rack:
81
+ url: http://localhost:9292
82
+ private_site:
83
+ url: http://example.com
84
+ username: user
85
+ password: pass
86
+
87
+ Then invoke:
88
+
89
+ $ restclient private_site
90
+
91
+ == Meta
92
+
93
+ Written by Adam Wiggins (adam at heroku dot com)
94
+
95
+ Major modifications by Blake Mizerany
96
+
97
+ Patches contributed by: Chris Anderson, Greg Borenstein, Ardekantur, Pedro Belo, Rafael Souza, Rick Olson, and Aman Gupta
98
+
99
+ Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
100
+
101
+ http://rest-client.heroku.com
102
+
103
+ http://github.com/adamwiggins/rest-client
104
+
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rake'
2
+
3
+ require 'jeweler'
4
+
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "rest-client"
7
+ s.description = "A simple REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete."
8
+ s.summary = "Simple REST client for Ruby, inspired by microframework syntax for specifying actions."
9
+ s.author = "Adam Wiggins"
10
+ s.email = "adam@heroku.com"
11
+ s.homepage = "http://rest-client.heroku.com/"
12
+ s.rubyforge_project = "rest-client"
13
+ s.has_rdoc = true
14
+ s.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"]
15
+ s.executables = %w(restclient)
16
+ end
17
+
18
+ Jeweler::RubyforgeTasks.new
19
+
20
+ ############################
21
+
22
+ require 'spec/rake/spectask'
23
+
24
+ desc "Run all specs"
25
+ Spec::Rake::SpecTask.new('spec') do |t|
26
+ t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
27
+ t.spec_files = FileList['spec/*_spec.rb']
28
+ end
29
+
30
+ desc "Print specdocs"
31
+ Spec::Rake::SpecTask.new(:doc) do |t|
32
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
33
+ t.spec_files = FileList['spec/*_spec.rb']
34
+ end
35
+
36
+ desc "Run all examples with RCov"
37
+ Spec::Rake::SpecTask.new('rcov') do |t|
38
+ t.spec_files = FileList['spec/*_spec.rb']
39
+ t.rcov = true
40
+ t.rcov_opts = ['--exclude', 'examples']
41
+ end
42
+
43
+ task :default => :spec
44
+
45
+ ############################
46
+
47
+ require 'rake/rdoctask'
48
+
49
+ Rake::RDocTask.new do |t|
50
+ t.rdoc_dir = 'rdoc'
51
+ t.title = "rest-client, fetch RESTful resources effortlessly"
52
+ t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
53
+ t.options << '--charset' << 'utf-8'
54
+ t.rdoc_files.include('README.rdoc')
55
+ t.rdoc_files.include('lib/*.rb')
56
+ end
57
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.1.5
data/bin/restclient ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + "/../lib"
4
+ require 'restclient'
5
+
6
+ require "yaml"
7
+
8
+ def usage(why = nil)
9
+ puts "failed for reason: #{why}" if why
10
+ puts "usage: restclient [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'] + "/.restclient")) 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 ||= RestClient::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 RestClient::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
+ begin
66
+ r.send(s, *args, &b)
67
+ rescue RestClient::RequestFailed => e
68
+ print STDERR, e.response.body
69
+ raise e
70
+ end
71
+ end
72
+
73
+ require 'irb'
74
+ require 'irb/completion'
75
+
76
+ if File.exists? ".irbrc"
77
+ ENV['IRBRC'] = ".irbrc"
78
+ end
79
+
80
+ if File.exists?(rcfile = "~/.restclientrc")
81
+ load(rcfile)
82
+ end
83
+
84
+ ARGV.clear
85
+
86
+ IRB.start
87
+ exit!
@@ -0,0 +1,2 @@
1
+ # This file exists for backward compatbility with require 'rest_client'
2
+ require File.dirname(__FILE__) + '/restclient'
@@ -0,0 +1,88 @@
1
+ module RestClient
2
+ # This is the base RestClient exception class. Rescue it if you want to
3
+ # catch any exception that your request might raise
4
+ class Exception < RuntimeError
5
+ def message(default=nil)
6
+ self.class::ErrorMessage
7
+ end
8
+ end
9
+
10
+ # Base RestClient 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
+
22
+ def http_body
23
+ RestClient::Request.decode(@response['content-encoding'], @response.body) if @response
24
+ end
25
+ end
26
+
27
+ # A redirect was encountered; caught by execute to retry with the new url.
28
+ class Redirect < Exception
29
+ ErrorMessage = "Redirect"
30
+
31
+ attr_accessor :url
32
+ def initialize(url)
33
+ @url = url
34
+ end
35
+ end
36
+
37
+ class NotModified < ExceptionWithResponse
38
+ ErrorMessage = 'NotModified'
39
+ end
40
+
41
+ # Authorization is required to access the resource specified.
42
+ class Unauthorized < ExceptionWithResponse
43
+ ErrorMessage = 'Unauthorized'
44
+ end
45
+
46
+ # No resource was found at the given URL.
47
+ class ResourceNotFound < ExceptionWithResponse
48
+ ErrorMessage = 'Resource not found'
49
+ end
50
+
51
+ # The server broke the connection prior to the request completing. Usually
52
+ # this means it crashed, or sometimes that your network connection was
53
+ # severed before it could complete.
54
+ class ServerBrokeConnection < Exception
55
+ ErrorMessage = 'Server broke connection'
56
+ end
57
+
58
+ # The server took too long to respond.
59
+ class RequestTimeout < Exception
60
+ ErrorMessage = 'Request timed out'
61
+ end
62
+
63
+ # The request failed, meaning the remote HTTP server returned a code other
64
+ # than success, unauthorized, or redirect.
65
+ #
66
+ # The exception message attempts to extract the error from the XML, using
67
+ # format returned by Rails: <errors><error>some message</error></errors>
68
+ #
69
+ # You can get the status code by e.http_code, or see anything about the
70
+ # response via e.response. For example, the entire result body (which is
71
+ # probably an HTML error page) is e.response.body.
72
+ class RequestFailed < ExceptionWithResponse
73
+ def message
74
+ "HTTP status code #{http_code}"
75
+ end
76
+
77
+ def to_s
78
+ message
79
+ end
80
+ end
81
+ end
82
+
83
+ # backwards compatibility
84
+ class RestClient::Request
85
+ Redirect = RestClient::Redirect
86
+ Unauthorized = RestClient::Unauthorized
87
+ RequestFailed = RestClient::RequestFailed
88
+ end
@@ -0,0 +1,43 @@
1
+ module RestClient
2
+ module Mixin
3
+ module Response
4
+ attr_reader :net_http_res
5
+
6
+ # HTTP status code, always 200 since RestClient 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(RestClient::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
@@ -0,0 +1,21 @@
1
+ #
2
+ # Replace the request method in Net::HTTP to sniff the body type
3
+ # and set the stream if appropriate
4
+ #
5
+ # Taken from:
6
+ # http://www.missiondata.com/blog/ruby/29/streaming-data-to-s3-with-ruby/
7
+
8
+ module Net
9
+ class HTTP
10
+ alias __request__ request
11
+
12
+ def request(req, body=nil, &block)
13
+ if body != nil && body.respond_to?(:read)
14
+ req.body_stream = body
15
+ return __request__(req, nil, &block)
16
+ else
17
+ return __request__(req, body, &block)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,206 @@
1
+ require "tempfile"
2
+ require "stringio"
3
+
4
+ module RestClient
5
+ module Payload
6
+ extend self
7
+
8
+ def generate(params)
9
+ if params.is_a?(String)
10
+ Base.new(params)
11
+ elsif params
12
+ if params.delete(:multipart) == true || has_file?(params)
13
+ Multipart.new(params)
14
+ else
15
+ UrlEncoded.new(params)
16
+ end
17
+ else
18
+ nil
19
+ end
20
+ end
21
+
22
+ def has_file?(params)
23
+ params.any? do |_, v|
24
+ case v
25
+ when Hash
26
+ has_file?(v)
27
+ else
28
+ v.respond_to?(:path) && v.respond_to?(:read)
29
+ end
30
+ end
31
+ end
32
+
33
+ class Base
34
+ def initialize(params)
35
+ build_stream(params)
36
+ end
37
+
38
+ def build_stream(params)
39
+ @stream = StringIO.new(params)
40
+ @stream.seek(0)
41
+ end
42
+
43
+ def read(bytes=nil)
44
+ @stream.read(bytes)
45
+ end
46
+ alias :to_s :read
47
+
48
+ def escape(v)
49
+ URI.escape(v.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
50
+ end
51
+
52
+ def headers
53
+ { 'Content-Length' => size.to_s }
54
+ end
55
+
56
+ def size
57
+ @stream.size
58
+ end
59
+ alias :length :size
60
+
61
+ def close
62
+ @stream.close
63
+ end
64
+
65
+ def inspect
66
+ to_s.inspect
67
+ end
68
+ end
69
+
70
+ class UrlEncoded < Base
71
+ def build_stream(params)
72
+ @stream = StringIO.new(params.map do |k,v|
73
+ "#{escape(k)}=#{escape(v)}"
74
+ end.join("&"))
75
+ @stream.seek(0)
76
+ end
77
+
78
+ def headers
79
+ super.merge({ 'Content-Type' => 'application/x-www-form-urlencoded' })
80
+ end
81
+ end
82
+
83
+ class Multipart < Base
84
+ EOL = "\r\n"
85
+
86
+ def build_stream(params)
87
+ b = "--#{boundary}"
88
+
89
+ @stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}")
90
+ @stream.write(b + EOL)
91
+ x = params.to_a
92
+ last_index = x.length - 1
93
+ x.each_with_index do |a, index|
94
+ k, v = *a
95
+ if v.respond_to?(:read) && v.respond_to?(:path)
96
+ create_file_field(@stream, k,v)
97
+ else
98
+ create_regular_field(@stream, k,v)
99
+ end
100
+ @stream.write(EOL + b)
101
+ @stream.write(EOL) unless last_index == index
102
+ end
103
+ @stream.write('--')
104
+ @stream.write(EOL)
105
+ @stream.seek(0)
106
+ end
107
+
108
+ def create_regular_field(s, k, v)
109
+ s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"")
110
+ s.write(EOL)
111
+ s.write(EOL)
112
+ s.write(v)
113
+ end
114
+
115
+ def create_file_field(s, k, v)
116
+ begin
117
+ s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"; filename=\"#{File.basename(v.path)}\"#{EOL}")
118
+ s.write("Content-Type: #{mime_for(v.path)}#{EOL}")
119
+ s.write(EOL)
120
+ while data = v.read(8124)
121
+ s.write(data)
122
+ end
123
+ ensure
124
+ v.close
125
+ end
126
+ end
127
+
128
+ def mime_for(path)
129
+ ext = File.extname(path)[1..-1]
130
+ MIME_TYPES[ext] || 'text/plain'
131
+ end
132
+
133
+ def boundary
134
+ @boundary ||= rand(1_000_000).to_s
135
+ end
136
+
137
+ def headers
138
+ super.merge({'Content-Type' => %Q{multipart/form-data; boundary="#{boundary}"}})
139
+ end
140
+
141
+ def close
142
+ @stream.close
143
+ end
144
+ end
145
+
146
+ # :stopdoc:
147
+ # From WEBrick.
148
+ MIME_TYPES = {
149
+ "ai" => "application/postscript",
150
+ "asc" => "text/plain",
151
+ "avi" => "video/x-msvideo",
152
+ "bin" => "application/octet-stream",
153
+ "bmp" => "image/bmp",
154
+ "class" => "application/octet-stream",
155
+ "cer" => "application/pkix-cert",
156
+ "crl" => "application/pkix-crl",
157
+ "crt" => "application/x-x509-ca-cert",
158
+ "css" => "text/css",
159
+ "dms" => "application/octet-stream",
160
+ "doc" => "application/msword",
161
+ "dvi" => "application/x-dvi",
162
+ "eps" => "application/postscript",
163
+ "etx" => "text/x-setext",
164
+ "exe" => "application/octet-stream",
165
+ "gif" => "image/gif",
166
+ "gz" => "application/x-gzip",
167
+ "htm" => "text/html",
168
+ "html" => "text/html",
169
+ "jpe" => "image/jpeg",
170
+ "jpeg" => "image/jpeg",
171
+ "jpg" => "image/jpeg",
172
+ "js" => "text/javascript",
173
+ "lha" => "application/octet-stream",
174
+ "lzh" => "application/octet-stream",
175
+ "mov" => "video/quicktime",
176
+ "mpe" => "video/mpeg",
177
+ "mpeg" => "video/mpeg",
178
+ "mpg" => "video/mpeg",
179
+ "pbm" => "image/x-portable-bitmap",
180
+ "pdf" => "application/pdf",
181
+ "pgm" => "image/x-portable-graymap",
182
+ "png" => "image/png",
183
+ "pnm" => "image/x-portable-anymap",
184
+ "ppm" => "image/x-portable-pixmap",
185
+ "ppt" => "application/vnd.ms-powerpoint",
186
+ "ps" => "application/postscript",
187
+ "qt" => "video/quicktime",
188
+ "ras" => "image/x-cmu-raster",
189
+ "rb" => "text/plain",
190
+ "rd" => "text/plain",
191
+ "rtf" => "application/rtf",
192
+ "sgm" => "text/sgml",
193
+ "sgml" => "text/sgml",
194
+ "tif" => "image/tiff",
195
+ "tiff" => "image/tiff",
196
+ "txt" => "text/plain",
197
+ "xbm" => "image/x-xbitmap",
198
+ "xls" => "application/vnd.ms-excel",
199
+ "xml" => "text/xml",
200
+ "xpm" => "image/x-xpixmap",
201
+ "xwd" => "image/x-xwindowdump",
202
+ "zip" => "application/zip",
203
+ }
204
+ # :startdoc:
205
+ end
206
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/mixin/response'
2
+
3
+ module RestClient
4
+ # The response from RestClient on a raw request looks like a string, but is
5
+ # actually one of these. 99% of the time you're making a rest call all you
6
+ # care about is the body, but on the occassion you want to fetch the
7
+ # headers you can:
8
+ #
9
+ # RestClient.get('http://example.com').headers[:content_type]
10
+ #
11
+ # In addition, if you do not use the response as a string, you can access
12
+ # a Tempfile object at res.file, which contains the path to the raw
13
+ # downloaded request body.
14
+ class RawResponse
15
+ include RestClient::Mixin::Response
16
+
17
+ attr_reader :file
18
+
19
+ def initialize(tempfile, net_http_res)
20
+ @net_http_res = net_http_res
21
+ @file = tempfile
22
+ end
23
+
24
+ def to_s
25
+ @file.open
26
+ @file.read
27
+ end
28
+
29
+ end
30
+ end