francois-rest-client 1.1.5

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 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