kenhirakawa-astrotrain 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +26 -0
- data/LICENSE +20 -0
- data/README +47 -0
- data/Rakefile +124 -0
- data/VERSION +1 -0
- data/astrotrain.gemspec +141 -0
- data/config/sample.rb +12 -0
- data/lib/astrotrain.rb +56 -0
- data/lib/astrotrain/api.rb +52 -0
- data/lib/astrotrain/logged_mail.rb +48 -0
- data/lib/astrotrain/mapping.rb +162 -0
- data/lib/astrotrain/mapping/http_post.rb +18 -0
- data/lib/astrotrain/mapping/jabber.rb +28 -0
- data/lib/astrotrain/mapping/transport.rb +55 -0
- data/lib/astrotrain/message.rb +342 -0
- data/lib/astrotrain/tmail.rb +58 -0
- data/lib/astrotrain/worker.rb +65 -0
- data/lib/vendor/rest-client/README.rdoc +104 -0
- data/lib/vendor/rest-client/Rakefile +84 -0
- data/lib/vendor/rest-client/bin/restclient +65 -0
- data/lib/vendor/rest-client/foo.diff +66 -0
- data/lib/vendor/rest-client/lib/rest_client.rb +188 -0
- data/lib/vendor/rest-client/lib/rest_client/net_http_ext.rb +23 -0
- data/lib/vendor/rest-client/lib/rest_client/payload.rb +185 -0
- data/lib/vendor/rest-client/lib/rest_client/request_errors.rb +75 -0
- data/lib/vendor/rest-client/lib/rest_client/resource.rb +103 -0
- data/lib/vendor/rest-client/rest-client.gemspec +18 -0
- data/lib/vendor/rest-client/spec/base.rb +5 -0
- data/lib/vendor/rest-client/spec/master_shake.jpg +0 -0
- data/lib/vendor/rest-client/spec/payload_spec.rb +71 -0
- data/lib/vendor/rest-client/spec/request_errors_spec.rb +44 -0
- data/lib/vendor/rest-client/spec/resource_spec.rb +52 -0
- data/lib/vendor/rest-client/spec/rest_client_spec.rb +219 -0
- data/test/api_test.rb +32 -0
- data/test/fixtures/apple_multipart.txt +100 -0
- data/test/fixtures/bad_content_type.txt +27 -0
- data/test/fixtures/basic.txt +14 -0
- data/test/fixtures/custom.txt +15 -0
- data/test/fixtures/fwd.txt +0 -0
- data/test/fixtures/gb2312_encoding.txt +16 -0
- data/test/fixtures/gb2312_encoding_invalid.txt +15 -0
- data/test/fixtures/html.txt +10 -0
- data/test/fixtures/html_multipart.txt +16 -0
- data/test/fixtures/iso-8859-1.txt +13 -0
- data/test/fixtures/mapped.txt +13 -0
- data/test/fixtures/multipart.txt +213 -0
- data/test/fixtures/multipart2.txt +213 -0
- data/test/fixtures/multiple.txt +13 -0
- data/test/fixtures/multiple_delivered_to.txt +14 -0
- data/test/fixtures/multiple_with_body_recipients.txt +15 -0
- data/test/fixtures/reply.txt +16 -0
- data/test/fixtures/undisclosed.txt +14 -0
- data/test/fixtures/utf-8.txt +13 -0
- data/test/logged_mail_test.rb +67 -0
- data/test/mapping_test.rb +129 -0
- data/test/message_test.rb +492 -0
- data/test/test_helper.rb +56 -0
- data/test/transport_test.rb +113 -0
- metadata +330 -0
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/https'
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + '/rest_client/resource'
|
5
|
+
require File.dirname(__FILE__) + '/rest_client/request_errors'
|
6
|
+
require File.dirname(__FILE__) + '/rest_client/payload'
|
7
|
+
require File.dirname(__FILE__) + '/rest_client/net_http_ext'
|
8
|
+
|
9
|
+
# This module's static methods are the entry point for using the REST client.
|
10
|
+
#
|
11
|
+
# # GET
|
12
|
+
# xml = RestClient.get 'http://example.com/resource'
|
13
|
+
# jpg = RestClient.get 'http://example.com/resource', :accept => 'image/jpg'
|
14
|
+
#
|
15
|
+
# # authentication and SSL
|
16
|
+
# RestClient.get 'https://user:password@example.com/private/resource'
|
17
|
+
#
|
18
|
+
# # POST or PUT with a hash sends parameters as a urlencoded form body
|
19
|
+
# RestClient.post 'http://example.com/resource', :param1 => 'one'
|
20
|
+
#
|
21
|
+
# # nest hash parameters
|
22
|
+
# RestClient.post 'http://example.com/resource', :nested => { :param1 => 'one' }
|
23
|
+
#
|
24
|
+
# # POST and PUT with raw payloads
|
25
|
+
# RestClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
|
26
|
+
# RestClient.post 'http://example.com/resource.xml', xml_doc
|
27
|
+
# RestClient.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'
|
28
|
+
#
|
29
|
+
# # DELETE
|
30
|
+
# RestClient.delete 'http://example.com/resource'
|
31
|
+
#
|
32
|
+
# For live tests of RestClient, try using http://rest-test.heroku.com, which echoes back information about the rest call:
|
33
|
+
#
|
34
|
+
# >> RestClient.put 'http://rest-test.heroku.com/resource', :foo => 'baz'
|
35
|
+
# => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}"
|
36
|
+
#
|
37
|
+
module RestClient
|
38
|
+
def self.get(url, headers={}, &b)
|
39
|
+
Request.execute(:method => :get,
|
40
|
+
:url => url,
|
41
|
+
:headers => headers, &b)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.post(url, payload, headers={}, &b)
|
45
|
+
Request.execute(:method => :post,
|
46
|
+
:url => url,
|
47
|
+
:payload => payload,
|
48
|
+
:headers => headers, &b)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.put(url, payload, headers={}, &b)
|
52
|
+
Request.execute(:method => :put,
|
53
|
+
:url => url,
|
54
|
+
:payload => payload,
|
55
|
+
:headers => headers, &b)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.delete(url, headers={}, &b)
|
59
|
+
Request.execute(:method => :delete,
|
60
|
+
:url => url,
|
61
|
+
:headers => headers, &b)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Internal class used to build and execute the request.
|
65
|
+
class Request
|
66
|
+
attr_reader :method, :url, :headers, :user, :password
|
67
|
+
|
68
|
+
def self.execute(args, &b)
|
69
|
+
new(args).execute(&b)
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(args)
|
73
|
+
@method = args[:method] or raise ArgumentError, "must pass :method"
|
74
|
+
@url = args[:url] or raise ArgumentError, "must pass :url"
|
75
|
+
@payload = Payload.generate(args[:payload] || '')
|
76
|
+
@headers = args[:headers] || {}
|
77
|
+
@user = args[:user]
|
78
|
+
@password = args[:password]
|
79
|
+
end
|
80
|
+
|
81
|
+
def execute(&b)
|
82
|
+
execute_inner(&b)
|
83
|
+
rescue Redirect => e
|
84
|
+
@url = e.url
|
85
|
+
execute(&b)
|
86
|
+
end
|
87
|
+
|
88
|
+
def execute_inner(&b)
|
89
|
+
uri = parse_url_with_auth(url)
|
90
|
+
transmit(uri, net_http_class(method).new(uri.request_uri, make_headers(headers)), payload, &b)
|
91
|
+
end
|
92
|
+
|
93
|
+
def make_headers(user_headers)
|
94
|
+
final = {}
|
95
|
+
merged = default_headers.merge(user_headers)
|
96
|
+
merged.keys.each do |key|
|
97
|
+
final[key.to_s.gsub(/_/, '-').capitalize] = merged[key]
|
98
|
+
end
|
99
|
+
final
|
100
|
+
end
|
101
|
+
|
102
|
+
def net_http_class(method)
|
103
|
+
Net::HTTP.const_get(method.to_s.capitalize)
|
104
|
+
end
|
105
|
+
|
106
|
+
def parse_url(url)
|
107
|
+
url = "http://#{url}" unless url.match(/^http/)
|
108
|
+
URI.parse(url)
|
109
|
+
end
|
110
|
+
|
111
|
+
def parse_url_with_auth(url)
|
112
|
+
uri = parse_url(url)
|
113
|
+
@user = uri.user if uri.user
|
114
|
+
@password = uri.password if uri.password
|
115
|
+
uri
|
116
|
+
end
|
117
|
+
|
118
|
+
def process_payload(p=nil, parent_key=nil)
|
119
|
+
unless p.is_a?(Hash)
|
120
|
+
p
|
121
|
+
else
|
122
|
+
@headers[:content_type] ||= 'application/x-www-form-urlencoded'
|
123
|
+
p.keys.map do |k|
|
124
|
+
key = parent_key ? "#{parent_key}[#{k}]" : k
|
125
|
+
if p[k].is_a? Hash
|
126
|
+
process_payload(p[k], key)
|
127
|
+
else
|
128
|
+
value = URI.escape(p[k].to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
129
|
+
"#{key}=#{value}"
|
130
|
+
end
|
131
|
+
end.join("&")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def transmit(uri, req, payload, &b)
|
136
|
+
setup_credentials(req)
|
137
|
+
|
138
|
+
net = Net::HTTP.new(uri.host, uri.port)
|
139
|
+
net.use_ssl = uri.is_a?(URI::HTTPS)
|
140
|
+
net.start do |http|
|
141
|
+
## Ok. I know this is weird but it's a hack for now
|
142
|
+
## this lets process_result determine if it should read the body
|
143
|
+
## into memory or not
|
144
|
+
process_result(http.request(req, payload || "", &b), &b)
|
145
|
+
end
|
146
|
+
rescue EOFError
|
147
|
+
raise RestClient::ServerBrokeConnection
|
148
|
+
rescue Timeout::Error
|
149
|
+
raise RestClient::RequestTimeout
|
150
|
+
ensure
|
151
|
+
payload.close
|
152
|
+
end
|
153
|
+
|
154
|
+
def setup_credentials(req)
|
155
|
+
req.basic_auth(user, password) if user
|
156
|
+
end
|
157
|
+
|
158
|
+
def process_result(res, &b)
|
159
|
+
if %w(200 201 202).include? res.code
|
160
|
+
return res.body unless b
|
161
|
+
elsif %w(301 302 303).include? res.code
|
162
|
+
url = res.header['Location']
|
163
|
+
|
164
|
+
if url !~ /^http/
|
165
|
+
uri = URI.parse(@url)
|
166
|
+
uri.path = "/#{url}".squeeze('/')
|
167
|
+
url = uri.to_s
|
168
|
+
end
|
169
|
+
|
170
|
+
raise Redirect, url
|
171
|
+
elsif res.code == "401"
|
172
|
+
raise Unauthorized
|
173
|
+
elsif res.code == "404"
|
174
|
+
raise ResourceNotFound
|
175
|
+
else
|
176
|
+
raise RequestFailed, res
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def payload
|
181
|
+
@payload
|
182
|
+
end
|
183
|
+
|
184
|
+
def default_headers
|
185
|
+
@payload.headers.merge({ :accept => 'application/xml' })
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,23 @@
|
|
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
|
+
Net::HTTP.ssl_context_accessor(:tmp_dh_callback)
|
9
|
+
|
10
|
+
module Net
|
11
|
+
class HTTP
|
12
|
+
alias __request__ request
|
13
|
+
|
14
|
+
def request(req, body=nil, &block)
|
15
|
+
if body != nil && body.respond_to?(:read)
|
16
|
+
req.body_stream = body
|
17
|
+
return __request__(req, nil, &block)
|
18
|
+
else
|
19
|
+
return __request__(req, body, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,185 @@
|
|
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.delete(:multipart) == true ||
|
12
|
+
params.any? { |_,v| v.respond_to?(:path) && v.respond_to?(:read) }
|
13
|
+
Multipart.new(params)
|
14
|
+
else
|
15
|
+
UrlEncoded.new(params)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Base
|
20
|
+
def initialize(params)
|
21
|
+
build_stream(params)
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_stream(params)
|
25
|
+
@stream = StringIO.new(params)
|
26
|
+
@stream.seek(0)
|
27
|
+
end
|
28
|
+
|
29
|
+
def read(bytes=nil)
|
30
|
+
@stream.read(bytes)
|
31
|
+
end
|
32
|
+
alias :to_s :read
|
33
|
+
|
34
|
+
def escape(v)
|
35
|
+
URI.escape(v.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
36
|
+
end
|
37
|
+
|
38
|
+
def headers
|
39
|
+
{ 'Content-Length' => size.to_s }
|
40
|
+
end
|
41
|
+
|
42
|
+
def size
|
43
|
+
@stream.size
|
44
|
+
end
|
45
|
+
alias :length :size
|
46
|
+
|
47
|
+
def close
|
48
|
+
@stream.close
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class UrlEncoded < Base
|
53
|
+
def build_stream(params)
|
54
|
+
@stream = StringIO.new(params.map do |k,v|
|
55
|
+
"#{escape(k)}=#{escape(v)}"
|
56
|
+
end.join("&"))
|
57
|
+
@stream.seek(0)
|
58
|
+
end
|
59
|
+
|
60
|
+
def headers
|
61
|
+
super.merge({ 'Content-Type' => 'application/x-www-form-urlencoded' })
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Multipart < Base
|
66
|
+
EOL = "\r\n"
|
67
|
+
|
68
|
+
def build_stream(params)
|
69
|
+
b = "--#{boundary}"
|
70
|
+
|
71
|
+
@stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}")
|
72
|
+
@stream.write(b)
|
73
|
+
params.each do |k,v|
|
74
|
+
@stream.write(EOL)
|
75
|
+
if v.respond_to?(:read) && v.respond_to?(:path)
|
76
|
+
create_file_field(@stream, k,v)
|
77
|
+
else
|
78
|
+
create_regular_field(@stream, k,v)
|
79
|
+
end
|
80
|
+
@stream.write(EOL + b)
|
81
|
+
end
|
82
|
+
@stream.write('--')
|
83
|
+
@stream.write(EOL)
|
84
|
+
@stream.seek(0)
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_regular_field(s, k, v)
|
88
|
+
s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"")
|
89
|
+
s.write(EOL)
|
90
|
+
s.write(EOL)
|
91
|
+
s.write(v)
|
92
|
+
end
|
93
|
+
|
94
|
+
def create_file_field(s, k, v)
|
95
|
+
begin
|
96
|
+
s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"; filename=\"#{v.path}\"#{EOL}")
|
97
|
+
s.write("Content-Type: #{mime_for(v.path)}#{EOL}")
|
98
|
+
s.write(EOL)
|
99
|
+
while data = v.read(8124)
|
100
|
+
s.write(data)
|
101
|
+
end
|
102
|
+
ensure
|
103
|
+
v.close
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def mime_for(path)
|
108
|
+
ext = File.extname(path)[1..-1]
|
109
|
+
MIME_TYPES[ext] || 'text/plain'
|
110
|
+
end
|
111
|
+
|
112
|
+
def boundary
|
113
|
+
@boundary ||= rand(1_000_000).to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
def headers
|
117
|
+
super.merge({'Content-Type' => %Q{multipart/form-data; boundary="#{boundary}"}})
|
118
|
+
end
|
119
|
+
|
120
|
+
def close
|
121
|
+
@stream.close
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# :stopdoc:
|
126
|
+
# From WEBrick.
|
127
|
+
MIME_TYPES = {
|
128
|
+
"ai" => "application/postscript",
|
129
|
+
"asc" => "text/plain",
|
130
|
+
"avi" => "video/x-msvideo",
|
131
|
+
"bin" => "application/octet-stream",
|
132
|
+
"bmp" => "image/bmp",
|
133
|
+
"class" => "application/octet-stream",
|
134
|
+
"cer" => "application/pkix-cert",
|
135
|
+
"crl" => "application/pkix-crl",
|
136
|
+
"crt" => "application/x-x509-ca-cert",
|
137
|
+
"css" => "text/css",
|
138
|
+
"dms" => "application/octet-stream",
|
139
|
+
"doc" => "application/msword",
|
140
|
+
"dvi" => "application/x-dvi",
|
141
|
+
"eps" => "application/postscript",
|
142
|
+
"etx" => "text/x-setext",
|
143
|
+
"exe" => "application/octet-stream",
|
144
|
+
"gif" => "image/gif",
|
145
|
+
"gz" => "application/x-gzip",
|
146
|
+
"htm" => "text/html",
|
147
|
+
"html" => "text/html",
|
148
|
+
"jpe" => "image/jpeg",
|
149
|
+
"jpeg" => "image/jpeg",
|
150
|
+
"jpg" => "image/jpeg",
|
151
|
+
"js" => "text/javascript",
|
152
|
+
"lha" => "application/octet-stream",
|
153
|
+
"lzh" => "application/octet-stream",
|
154
|
+
"mov" => "video/quicktime",
|
155
|
+
"mpe" => "video/mpeg",
|
156
|
+
"mpeg" => "video/mpeg",
|
157
|
+
"mpg" => "video/mpeg",
|
158
|
+
"pbm" => "image/x-portable-bitmap",
|
159
|
+
"pdf" => "application/pdf",
|
160
|
+
"pgm" => "image/x-portable-graymap",
|
161
|
+
"png" => "image/png",
|
162
|
+
"pnm" => "image/x-portable-anymap",
|
163
|
+
"ppm" => "image/x-portable-pixmap",
|
164
|
+
"ppt" => "application/vnd.ms-powerpoint",
|
165
|
+
"ps" => "application/postscript",
|
166
|
+
"qt" => "video/quicktime",
|
167
|
+
"ras" => "image/x-cmu-raster",
|
168
|
+
"rb" => "text/plain",
|
169
|
+
"rd" => "text/plain",
|
170
|
+
"rtf" => "application/rtf",
|
171
|
+
"sgm" => "text/sgml",
|
172
|
+
"sgml" => "text/sgml",
|
173
|
+
"tif" => "image/tiff",
|
174
|
+
"tiff" => "image/tiff",
|
175
|
+
"txt" => "text/plain",
|
176
|
+
"xbm" => "image/x-xbitmap",
|
177
|
+
"xls" => "application/vnd.ms-excel",
|
178
|
+
"xml" => "text/xml",
|
179
|
+
"xpm" => "image/x-xpixmap",
|
180
|
+
"xwd" => "image/x-xwindowdump",
|
181
|
+
"zip" => "application/zip",
|
182
|
+
}
|
183
|
+
# :startdoc:
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,75 @@
|
|
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
|
+
# A redirect was encountered; caught by execute to retry with the new url.
|
11
|
+
class Redirect < Exception
|
12
|
+
ErrorMessage = "Redirect"
|
13
|
+
|
14
|
+
attr_accessor :url
|
15
|
+
def initialize(url)
|
16
|
+
@url = url
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Authorization is required to access the resource specified.
|
21
|
+
class Unauthorized < Exception
|
22
|
+
ErrorMessage = 'Unauthorized'
|
23
|
+
end
|
24
|
+
|
25
|
+
# No resource was found at the given URL.
|
26
|
+
class ResourceNotFound < Exception
|
27
|
+
ErrorMessage = 'Resource not found'
|
28
|
+
end
|
29
|
+
|
30
|
+
# The server broke the connection prior to the request completing.
|
31
|
+
class ServerBrokeConnection < Exception
|
32
|
+
ErrorMessage = 'Server broke connection'
|
33
|
+
end
|
34
|
+
|
35
|
+
# The server took too long to respond.
|
36
|
+
class RequestTimeout < Exception
|
37
|
+
ErrorMessage = 'Request timed out'
|
38
|
+
end
|
39
|
+
|
40
|
+
# The request failed, meaning the remote HTTP server returned a code other
|
41
|
+
# than success, unauthorized, or redirect.
|
42
|
+
#
|
43
|
+
# The exception message attempts to extract the error from the XML, using
|
44
|
+
# format returned by Rails: <errors><error>some message</error></errors>
|
45
|
+
#
|
46
|
+
# You can get the status code by e.http_code, or see anything about the
|
47
|
+
# response via e.response. For example, the entire result body (which is
|
48
|
+
# probably an HTML error page) is e.response.body.
|
49
|
+
class RequestFailed < Exception
|
50
|
+
attr_accessor :response
|
51
|
+
|
52
|
+
def initialize(response=nil)
|
53
|
+
@response = response
|
54
|
+
end
|
55
|
+
|
56
|
+
def http_code
|
57
|
+
@response.code.to_i if @response
|
58
|
+
end
|
59
|
+
|
60
|
+
def message
|
61
|
+
"HTTP status code #{http_code}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
message
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# backwards compatibility
|
71
|
+
class RestClient::Request
|
72
|
+
Redirect = RestClient::Redirect
|
73
|
+
Unauthorized = RestClient::Unauthorized
|
74
|
+
RequestFailed = RestClient::RequestFailed
|
75
|
+
end
|