remote-controller 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +19 -0
- data/README.rdoc +41 -0
- data/Rakefile +23 -0
- data/lib/remote-controller.rb +1 -0
- data/lib/remote_controller.rb +11 -0
- data/lib/remote_controller/base.rb +91 -0
- data/lib/remote_controller/cgi_helpers.rb +10 -0
- data/lib/remote_controller/cookies_container.rb +31 -0
- data/lib/remote_controller/multipart.rb +64 -0
- data/lib/remote_controller/multipart/composite_io.rb +111 -0
- data/lib/remote_controller/multipart/multipartable.rb +40 -0
- data/lib/remote_controller/multipart/parts.rb +6 -0
- data/lib/remote_controller/multipart/parts/epilogue_part.rb +36 -0
- data/lib/remote_controller/multipart/parts/file_part.rb +49 -0
- data/lib/remote_controller/multipart/parts/param_part.rb +43 -0
- data/lib/remote_controller/multipart/parts/part.rb +45 -0
- data/lib/remote_controller/version.rb +3 -0
- data/lib/samples/sample1.rb +47 -0
- data/lib/samples/sample1.txt +1 -0
- data/remote-controller.gemspec +20 -0
- data/test/remote_controller_action_args_test.rb +72 -0
- data/test/remote_controller_composite_io_test.rb +76 -0
- data/test/remote_controller_cookies_container_test.rb +62 -0
- data/test/remote_controller_error_handling_test.rb +39 -0
- data/test/remote_controller_test.rb +97 -0
- data/test/remote_controller_test_multipart_test.rb +91 -0
- data/test/test_helper.rb +13 -0
- data/tmp/.gitkeep +0 -0
- metadata +105 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2011 by Evgeny Myasishchev
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
= Remote-controller
|
2
|
+
Library to simplify remote controller actions invocation.
|
3
|
+
|
4
|
+
== Features
|
5
|
+
Can invoke actions of remote controllers. Can send any data including files. Does not supports REST like controllers yet.
|
6
|
+
|
7
|
+
=== Supported HTTP methods
|
8
|
+
* GET
|
9
|
+
* POST
|
10
|
+
* Multipart POST
|
11
|
+
|
12
|
+
== Example
|
13
|
+
|
14
|
+
#Reusable cookies container to persist cookies between different remote controllers of the same remote application
|
15
|
+
cookies_container = RemoteController::CookiesContainer.new
|
16
|
+
|
17
|
+
#Sessions controller is bound to 'http://my-application.com/sessions' url
|
18
|
+
sessions = RemoteController::Base.new('http://my-application.com/sessions')
|
19
|
+
sessions.cookies_container = cookies_container
|
20
|
+
|
21
|
+
# GET: http://my-application.com/sessions/authenticity_token
|
22
|
+
# Response is string containing authenticity token. It will also start a new session.
|
23
|
+
# CookiesContainer will hold session cookies (and all other cookies) automatically
|
24
|
+
authenticity_token = sessions.authenticity_token
|
25
|
+
|
26
|
+
# POST http://my-application.com/sessions/authorize
|
27
|
+
# POST body:
|
28
|
+
# => authenticity_token=authenticity+token&login=admin&password=password
|
29
|
+
# => in case response is not 200 then exception is thrown
|
30
|
+
sessions.authorize(:post, :authenticity_token => authenticity_token, :login => "admin", :password => "password")
|
31
|
+
|
32
|
+
#Reports controller is bound to 'http://my-application.com/reports'
|
33
|
+
reports = RemoteController::Base.new('http://my-application.com/reports')
|
34
|
+
sessions.cookies_container = cookies_container #Reusing cookies container to preserve the same session...
|
35
|
+
|
36
|
+
# POST http://my-application.com/sessions/create
|
37
|
+
# POST body is in multipart POST form. It has two parts:
|
38
|
+
# => name = New report
|
39
|
+
# => attachment = sample1.txt file
|
40
|
+
attachment = RemoteController.file_part(File.expand_path("../sample1.txt", __FILE__), "text/plain")
|
41
|
+
reports.create(:multipart, :name => "New report", :attachment => attachment)
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the remote_controller plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation for the remote_controller plugin.'
|
17
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'Remote-controller'
|
20
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
21
|
+
rdoc.rdoc_files.include('README')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'remote_controller'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module RemoteController
|
2
|
+
require 'remote_controller/multipart'
|
3
|
+
autoload :CookiesContainer, 'remote_controller/cookies_container'
|
4
|
+
autoload :CGIHelpers, 'remote_controller/cgi_helpers'
|
5
|
+
autoload :Base, 'remote_controller/base'
|
6
|
+
autoload :VERSION, 'remote_controller/version'
|
7
|
+
|
8
|
+
def self.file_part(file_path, content_type)
|
9
|
+
RemoteController::Multipart::UploadIO.new(file_path, content_type)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
class RemoteController::Base
|
4
|
+
include RemoteController::CGIHelpers
|
5
|
+
|
6
|
+
class RemoteControllerError < StandardError #:nodoc:
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(url)
|
10
|
+
@url = url
|
11
|
+
@error_handlers = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def cookies_container
|
15
|
+
@cookies_container = @cookies_container || RemoteController::CookiesContainer.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def cookies_container=(container)
|
19
|
+
@cookies_container = container
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_error(&block)
|
23
|
+
raise "No block given" unless block_given?
|
24
|
+
@error_handlers << block
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(symbol, *args)
|
28
|
+
|
29
|
+
#Following stuff is required to send requests to remote controllers
|
30
|
+
action_name = symbol.to_s
|
31
|
+
method = :get
|
32
|
+
parameters = {}
|
33
|
+
|
34
|
+
#Processing arguments
|
35
|
+
if args.length > 0
|
36
|
+
method = args.shift if([:get, :post, :multipart].include?(args[0]))
|
37
|
+
end
|
38
|
+
if args.length > 1
|
39
|
+
raise RemoteControllerError.new("Invalid arguments.")
|
40
|
+
elsif args.length == 1 && (args[0].is_a?(Hash) || args[0].is_a?(String))
|
41
|
+
parameters = args.shift
|
42
|
+
end
|
43
|
+
|
44
|
+
#Now we can send the request
|
45
|
+
send_request(action_name, method, parameters)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def send_request(action_name, method, parameters)
|
50
|
+
|
51
|
+
uri = URI.parse(@url)
|
52
|
+
action_path = "#{uri.path}/#{action_name}"
|
53
|
+
|
54
|
+
request = nil
|
55
|
+
|
56
|
+
case method
|
57
|
+
when :get
|
58
|
+
request = Net::HTTP::Get.new("#{action_path}?#{to_param(parameters)}")
|
59
|
+
when :post
|
60
|
+
request = Net::HTTP::Post.new(action_path)
|
61
|
+
request.body = to_param(parameters)
|
62
|
+
when :multipart
|
63
|
+
request = Net::HTTP::Post::Multipart.new(action_path, parameters)
|
64
|
+
else
|
65
|
+
raise RemoteControllerError.new("Unsupported method")
|
66
|
+
end
|
67
|
+
initialize_request(request)
|
68
|
+
response = Net::HTTP.start(uri.host, uri.port) {|http|
|
69
|
+
http.request(request)
|
70
|
+
}
|
71
|
+
process_headers(response)
|
72
|
+
begin
|
73
|
+
response.value #Will raise error in case response is not 2xx
|
74
|
+
rescue
|
75
|
+
@error_handlers.each { |e| e.call($!) }
|
76
|
+
raise $!
|
77
|
+
end
|
78
|
+
response.body
|
79
|
+
end
|
80
|
+
|
81
|
+
def initialize_request(request)
|
82
|
+
request["cookie"] = cookies_container.to_header unless cookies_container.empty?
|
83
|
+
end
|
84
|
+
|
85
|
+
def process_headers(response)
|
86
|
+
cookies = response["set-cookie"]
|
87
|
+
if(cookies)
|
88
|
+
cookies_container.process(cookies)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module RemoteController::CGIHelpers
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
def to_param(object)
|
5
|
+
return CGI.escape(object.to_s) unless object.is_a? Hash
|
6
|
+
object.collect do |key, value|
|
7
|
+
"#{CGI.escape(key.to_s).gsub(/%(5B|5D)/n) { [$1].pack('H*') }}=#{CGI.escape(value.to_s)}"
|
8
|
+
end.sort * '&'
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'webrick/cookie'
|
2
|
+
|
3
|
+
class RemoteController::CookiesContainer
|
4
|
+
def initialize()
|
5
|
+
@cookies = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def [](name)
|
9
|
+
@cookies[name]
|
10
|
+
end
|
11
|
+
|
12
|
+
def process(set_cookie_string)
|
13
|
+
parsed = WEBrick::Cookie.parse_set_cookies(set_cookie_string)
|
14
|
+
parsed.each do |cookie|
|
15
|
+
@cookies[cookie.name] = cookie
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_header
|
20
|
+
result = ""
|
21
|
+
@cookies.each do |name, cookie|
|
22
|
+
result << "#{name}=#{cookie.value}; "
|
23
|
+
end
|
24
|
+
result = result.strip!
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
def empty?
|
29
|
+
@cookies.size == 0
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#--
|
2
|
+
# (c) Copyright 2007-2008 Nick Sieger.
|
3
|
+
# See the file README.txt included with the distribution for
|
4
|
+
# software license details.
|
5
|
+
#++
|
6
|
+
# (The MIT License)
|
7
|
+
#
|
8
|
+
# Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
11
|
+
# a copy of this software and associated documentation files (the
|
12
|
+
# 'Software'), to deal in the Software without restriction, including
|
13
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
14
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
15
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
16
|
+
# the following conditions:
|
17
|
+
#
|
18
|
+
# The above copyright notice and this permission notice shall be
|
19
|
+
# included in all copies or substantial portions of the Software.
|
20
|
+
#
|
21
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
22
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
23
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
24
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
25
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
26
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
27
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
|
+
|
29
|
+
|
30
|
+
#Example:
|
31
|
+
# url = URI.parse('http://www.example.com/upload')
|
32
|
+
# File.open("./image.jpg") do |jpg|
|
33
|
+
# req = Net::HTTP::Post::Multipart.new url.path,
|
34
|
+
# "file" => UploadIO.new(jpg, "image/jpeg", "image.jpg")
|
35
|
+
# res = Net::HTTP.start(url.host, url.port) do |http|
|
36
|
+
# http.request(req)
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
|
40
|
+
|
41
|
+
require 'net/http'
|
42
|
+
require 'stringio'
|
43
|
+
require 'cgi'
|
44
|
+
|
45
|
+
module RemoteController::Multipart end
|
46
|
+
|
47
|
+
require 'remote_controller/multipart/parts'
|
48
|
+
require 'remote_controller/multipart/composite_io'
|
49
|
+
require 'remote_controller/multipart/multipartable'
|
50
|
+
|
51
|
+
module Net #:nodoc:
|
52
|
+
class HTTP #:nodoc:
|
53
|
+
class Put
|
54
|
+
class Multipart < Put
|
55
|
+
include RemoteController::Multipart::Multipartable
|
56
|
+
end
|
57
|
+
end
|
58
|
+
class Post #:nodoc:
|
59
|
+
class Multipart < Post
|
60
|
+
include RemoteController::Multipart::Multipartable
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#--
|
2
|
+
# (c) Copyright 2007-2008 Nick Sieger.
|
3
|
+
# See the file README.txt included with the distribution for
|
4
|
+
# software license details.
|
5
|
+
#++
|
6
|
+
# (The MIT License)
|
7
|
+
#
|
8
|
+
# Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
11
|
+
# a copy of this software and associated documentation files (the
|
12
|
+
# 'Software'), to deal in the Software without restriction, including
|
13
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
14
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
15
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
16
|
+
# the following conditions:
|
17
|
+
#
|
18
|
+
# The above copyright notice and this permission notice shall be
|
19
|
+
# included in all copies or substantial portions of the Software.
|
20
|
+
#
|
21
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
22
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
23
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
24
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
25
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
26
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
27
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
|
+
|
29
|
+
# Concatenate together multiple IO objects into a single, composite IO object
|
30
|
+
# for purposes of reading as a single stream.
|
31
|
+
#
|
32
|
+
# Usage:
|
33
|
+
#
|
34
|
+
# crio = CompositeReadIO.new(StringIO.new('one'), StringIO.new('two'), StringIO.new('three'))
|
35
|
+
# puts crio.read # => "onetwothree"
|
36
|
+
#
|
37
|
+
class RemoteController::Multipart::CompositeReadIO
|
38
|
+
# Create a new composite-read IO from the arguments, all of which should
|
39
|
+
# respond to #read in a manner consistent with IO.
|
40
|
+
def initialize(*ios)
|
41
|
+
@ios = ios.flatten
|
42
|
+
end
|
43
|
+
|
44
|
+
# Read from the IO object, overlapping across underlying streams as necessary.
|
45
|
+
def read(amount = nil, buf = nil)
|
46
|
+
buffer = buf || ''
|
47
|
+
done = if amount; nil; else ''; end
|
48
|
+
partial_amount = amount
|
49
|
+
|
50
|
+
loop do
|
51
|
+
result = done
|
52
|
+
|
53
|
+
while !@ios.empty? && (result = @ios.first.read(partial_amount)) == done
|
54
|
+
@ios.shift
|
55
|
+
end
|
56
|
+
|
57
|
+
buffer << result if result
|
58
|
+
partial_amount -= result.length if partial_amount && result != done
|
59
|
+
|
60
|
+
break if partial_amount && partial_amount <= 0
|
61
|
+
break if result == done
|
62
|
+
end
|
63
|
+
|
64
|
+
if buffer.length > 0
|
65
|
+
buffer
|
66
|
+
else
|
67
|
+
done
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Convenience methods for dealing with files and IO that are to be uploaded.
|
73
|
+
module RemoteController::Multipart::UploadIO
|
74
|
+
# Create an upload IO suitable for including in the params hash of a
|
75
|
+
# Net::HTTP::Post::Multipart.
|
76
|
+
#
|
77
|
+
# Can take two forms. The first accepts a filename and content type, and
|
78
|
+
# opens the file for reading (to be closed by finalizer). The second accepts
|
79
|
+
# an already-open IO, but also requires a third argument, the filename from
|
80
|
+
# which it was opened.
|
81
|
+
#
|
82
|
+
# UploadIO.new("file.txt", "text/plain")
|
83
|
+
# UploadIO.new(file_io, "text/plain", "file.txt")
|
84
|
+
def self.new(filename_or_io, content_type, filename = nil)
|
85
|
+
io = filename_or_io
|
86
|
+
unless io.respond_to? :read
|
87
|
+
io = File.open(filename_or_io)
|
88
|
+
filename = filename_or_io
|
89
|
+
end
|
90
|
+
convert!(io, content_type, File.basename(filename), filename)
|
91
|
+
io
|
92
|
+
end
|
93
|
+
|
94
|
+
# Enhance an existing IO for including in the params hash of a
|
95
|
+
# Net::HTTP::Post::Multipart by adding #content_type, #original_filename,
|
96
|
+
# and #local_path methods to the object's singleton class.
|
97
|
+
def self.convert!(io, content_type, original_filename, local_path)
|
98
|
+
io.instance_eval(<<-EOS, __FILE__, __LINE__)
|
99
|
+
def content_type
|
100
|
+
"#{content_type}"
|
101
|
+
end
|
102
|
+
def original_filename
|
103
|
+
"#{original_filename}"
|
104
|
+
end
|
105
|
+
def local_path
|
106
|
+
"#{local_path}"
|
107
|
+
end
|
108
|
+
EOS
|
109
|
+
io
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#--
|
2
|
+
# (c) Copyright 2007-2008 Nick Sieger.
|
3
|
+
# See the file README.txt included with the distribution for
|
4
|
+
# software license details.
|
5
|
+
#++
|
6
|
+
# (The MIT License)
|
7
|
+
#
|
8
|
+
# Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
11
|
+
# a copy of this software and associated documentation files (the
|
12
|
+
# 'Software'), to deal in the Software without restriction, including
|
13
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
14
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
15
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
16
|
+
# the following conditions:
|
17
|
+
#
|
18
|
+
# The above copyright notice and this permission notice shall be
|
19
|
+
# included in all copies or substantial portions of the Software.
|
20
|
+
#
|
21
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
22
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
23
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
24
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
25
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
26
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
27
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
|
+
|
29
|
+
module RemoteController::Multipart::Multipartable
|
30
|
+
DEFAULT_BOUNDARY = "-----------1a5ef2de8917334afe03269e42657dea596c90fb"
|
31
|
+
def initialize(path, params, headers={}, boundary = DEFAULT_BOUNDARY)
|
32
|
+
super(path, headers)
|
33
|
+
parts = params.map {|k,v| RemoteController::Multipart::Parts::Part.new(boundary, k, v)}
|
34
|
+
parts << RemoteController::Multipart::Parts::EpiloguePart.new(boundary)
|
35
|
+
ios = parts.map{|p| p.to_io }
|
36
|
+
self.set_content_type("multipart/form-data", { "boundary" => boundary })
|
37
|
+
self.content_length = parts.inject(0) {|sum,i| sum + i.length }
|
38
|
+
self.body_stream = RemoteController::Multipart::CompositeReadIO.new(*ios)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
module RemoteController::Multipart::Parts end
|
2
|
+
|
3
|
+
require 'remote_controller/multipart/parts/part'
|
4
|
+
require 'remote_controller/multipart/parts/param_part'
|
5
|
+
require 'remote_controller/multipart/parts/file_part'
|
6
|
+
require 'remote_controller/multipart/parts/epilogue_part'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#--
|
2
|
+
# (c) Copyright 2007-2008 Nick Sieger.
|
3
|
+
# See the file README.txt included with the distribution for
|
4
|
+
# software license details.
|
5
|
+
#++
|
6
|
+
# (The MIT License)
|
7
|
+
#
|
8
|
+
# Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
11
|
+
# a copy of this software and associated documentation files (the
|
12
|
+
# 'Software'), to deal in the Software without restriction, including
|
13
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
14
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
15
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
16
|
+
# the following conditions:
|
17
|
+
#
|
18
|
+
# The above copyright notice and this permission notice shall be
|
19
|
+
# included in all copies or substantial portions of the Software.
|
20
|
+
#
|
21
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
22
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
23
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
24
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
25
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
26
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
27
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
|
+
|
29
|
+
# Represents the epilogue or closing boundary.
|
30
|
+
class RemoteController::Multipart::Parts::EpiloguePart
|
31
|
+
include RemoteController::Multipart::Parts::Part
|
32
|
+
def initialize(boundary)
|
33
|
+
@part = "--#{boundary}--\r\n"
|
34
|
+
@io = StringIO.new(@part)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#--
|
2
|
+
# (c) Copyright 2007-2008 Nick Sieger.
|
3
|
+
# See the file README.txt included with the distribution for
|
4
|
+
# software license details.
|
5
|
+
#++
|
6
|
+
# (The MIT License)
|
7
|
+
#
|
8
|
+
# Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
11
|
+
# a copy of this software and associated documentation files (the
|
12
|
+
# 'Software'), to deal in the Software without restriction, including
|
13
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
14
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
15
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
16
|
+
# the following conditions:
|
17
|
+
#
|
18
|
+
# The above copyright notice and this permission notice shall be
|
19
|
+
# included in all copies or substantial portions of the Software.
|
20
|
+
#
|
21
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
22
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
23
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
24
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
25
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
26
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
27
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
|
+
|
29
|
+
# Represents a part to be filled from file IO.
|
30
|
+
class RemoteController::Multipart::Parts::FilePart
|
31
|
+
include RemoteController::Multipart::Parts::Part
|
32
|
+
attr_reader :length
|
33
|
+
def initialize(boundary, name, io)
|
34
|
+
file_length = io.respond_to?(:length) ? io.length : File.size(io.path)
|
35
|
+
@head = build_head(boundary, name, io.original_filename, io.content_type, file_length)
|
36
|
+
@length = @head.length + file_length
|
37
|
+
@io = RemoteController::Multipart::CompositeReadIO.new(StringIO.new(@head), io, StringIO.new("\r\n"))
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_head(boundary, name, filename, type, content_len)
|
41
|
+
part = ''
|
42
|
+
part << "--#{boundary}\r\n"
|
43
|
+
part << "Content-Disposition: form-data; name=\"#{name.to_s}\"; filename=\"#{filename}\"\r\n"
|
44
|
+
part << "Content-Length: #{content_len}\r\n"
|
45
|
+
part << "Content-Type: #{type}\r\n"
|
46
|
+
part << "Content-Transfer-Encoding: binary\r\n"
|
47
|
+
part << "\r\n"
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#--
|
2
|
+
# (c) Copyright 2007-2008 Nick Sieger.
|
3
|
+
# See the file README.txt included with the distribution for
|
4
|
+
# software license details.
|
5
|
+
#++
|
6
|
+
# (The MIT License)
|
7
|
+
#
|
8
|
+
# Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
11
|
+
# a copy of this software and associated documentation files (the
|
12
|
+
# 'Software'), to deal in the Software without restriction, including
|
13
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
14
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
15
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
16
|
+
# the following conditions:
|
17
|
+
#
|
18
|
+
# The above copyright notice and this permission notice shall be
|
19
|
+
# included in all copies or substantial portions of the Software.
|
20
|
+
#
|
21
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
22
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
23
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
24
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
25
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
26
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
27
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
|
+
|
29
|
+
class RemoteController::Multipart::Parts::ParamPart
|
30
|
+
include RemoteController::Multipart::Parts::Part
|
31
|
+
def initialize(boundary, name, value)
|
32
|
+
@part = build_part(boundary, name, value)
|
33
|
+
@io = StringIO.new(@part)
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_part(boundary, name, value)
|
37
|
+
part = ''
|
38
|
+
part << "--#{boundary}\r\n"
|
39
|
+
part << "Content-Disposition: form-data; name=\"#{name.to_s}\"\r\n"
|
40
|
+
part << "\r\n"
|
41
|
+
part << "#{value}\r\n"
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#--
|
2
|
+
# (c) Copyright 2007-2008 Nick Sieger.
|
3
|
+
# See the file README.txt included with the distribution for
|
4
|
+
# software license details.
|
5
|
+
#++
|
6
|
+
# (The MIT License)
|
7
|
+
#
|
8
|
+
# Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
11
|
+
# a copy of this software and associated documentation files (the
|
12
|
+
# 'Software'), to deal in the Software without restriction, including
|
13
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
14
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
15
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
16
|
+
# the following conditions:
|
17
|
+
#
|
18
|
+
# The above copyright notice and this permission notice shall be
|
19
|
+
# included in all copies or substantial portions of the Software.
|
20
|
+
#
|
21
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
22
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
23
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
24
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
25
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
26
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
27
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
|
+
|
29
|
+
module RemoteController::Multipart::Parts::Part
|
30
|
+
def self.new(boundary, name, value)
|
31
|
+
if value.respond_to? :content_type
|
32
|
+
RemoteController::Multipart::Parts::FilePart.new(boundary, name, value)
|
33
|
+
else
|
34
|
+
RemoteController::Multipart::Parts::ParamPart.new(boundary, name, value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def length
|
39
|
+
@part.length
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_io
|
43
|
+
@io
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
ENV['BUNDLE_GEMFILE'] = File.expand_path('../../../Gemfile', __FILE__)
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'http-testing'
|
5
|
+
require 'remote-controller'
|
6
|
+
|
7
|
+
address = "http://localhost:30013"
|
8
|
+
context = HttpTesting::Context.start(30013) do |request, response|
|
9
|
+
puts "#{request.request_method} #{address}#{request.path}"
|
10
|
+
puts request.body if request.body
|
11
|
+
puts ""
|
12
|
+
|
13
|
+
if request.path == "/sessions/authenticity_token"
|
14
|
+
response.body = "authenticity token"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
#Reusable cookies container to persist cookies between different remote controllers of the same remote application
|
19
|
+
cookies_container = RemoteController::CookiesContainer.new
|
20
|
+
|
21
|
+
#Sessions controller is bound to 'http://localhost:300013/sessions' url
|
22
|
+
sessions = RemoteController::Base.new(URI::join(address, "/sessions").to_s)
|
23
|
+
sessions.cookies_container = cookies_container
|
24
|
+
|
25
|
+
# GET: http://localhost:300013/sessions/authenticity_token
|
26
|
+
# Response is string containing authenticity token. It will also start a new session.
|
27
|
+
# CookiesContainer will hold session cookies (and all other cookies) automatically
|
28
|
+
authenticity_token = sessions.authenticity_token
|
29
|
+
|
30
|
+
# POST http://localhost:300013/sessions/authorize
|
31
|
+
# POST body:
|
32
|
+
# => authenticity_token=authenticity+token&login=admin&password=password
|
33
|
+
# => in case response is not 200 then exception is thrown
|
34
|
+
sessions.authorize(:post, :authenticity_token => authenticity_token, :login => "admin", :password => "password")
|
35
|
+
|
36
|
+
#Reports controller is bound to: http://localhost:300013/reports
|
37
|
+
reports = RemoteController::Base.new(URI::join(address, "/reports").to_s)
|
38
|
+
sessions.cookies_container = cookies_container #Reusing cookies container to preserve the same session...
|
39
|
+
|
40
|
+
# POST http://localhost:300013/sessions/create
|
41
|
+
# POST body is in multipart post form. It has two parts:
|
42
|
+
# => name = New report
|
43
|
+
# => attachment = sample1.txt file
|
44
|
+
attachment = RemoteController.file_part(File.expand_path("../sample1.txt", __FILE__), "text/plain")
|
45
|
+
reports.create(:multipart, :name => "New report", :attachment => attachment)
|
46
|
+
|
47
|
+
context.wait
|
@@ -0,0 +1 @@
|
|
1
|
+
Txt file to include as multipart HTTP POST
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path('../lib/remote_controller/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'remote-controller'
|
5
|
+
s.version = RemoteController::VERSION
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.authors = ['Evgeny Myasishchev']
|
8
|
+
s.email = ['evgeny.myasishchev@gmail.com']
|
9
|
+
s.homepage = 'http://github.com/evgeny-myasishchev/remote-controller'
|
10
|
+
s.summary = %q{Helps to invoke actions of remote controllers.}
|
11
|
+
s.description = %q{Library to simplify remote controller actions invocation.}
|
12
|
+
|
13
|
+
s.rubyforge_project = 'remote-controller'
|
14
|
+
|
15
|
+
s.add_development_dependency 'http-testing', '>= 0.1.3'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class RemoteControllerActionArgsTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@controller = RemoteController::Base.new("http://localhost:#{@server_port}/test_controller")
|
7
|
+
|
8
|
+
#Preserving send_request method. We'll need to restore it later
|
9
|
+
@origin_method = @controller.method(:send_request)
|
10
|
+
end
|
11
|
+
|
12
|
+
def teardown
|
13
|
+
#Restoring original method. Required to make subsequent tests work
|
14
|
+
@controller.class.send(:define_method, :send_request, @origin_method)
|
15
|
+
end
|
16
|
+
|
17
|
+
#The test have to be in a separate class because it changes :send_requrest method
|
18
|
+
def test_arguments_parsing
|
19
|
+
instance = self
|
20
|
+
|
21
|
+
@controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
|
22
|
+
instance.assert_equal "no_args_get", action_name
|
23
|
+
instance.assert_equal :get, method
|
24
|
+
instance.assert_equal 0, parameters.size
|
25
|
+
end
|
26
|
+
@controller.no_args_get
|
27
|
+
|
28
|
+
@controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
|
29
|
+
instance.assert_equal "no_args_post", action_name
|
30
|
+
instance.assert_equal :post, method
|
31
|
+
instance.assert_equal 0, parameters.size
|
32
|
+
end
|
33
|
+
@controller.no_args_post(:post)
|
34
|
+
|
35
|
+
@controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
|
36
|
+
instance.assert_equal "post_with_args", action_name
|
37
|
+
instance.assert_equal :post, method
|
38
|
+
instance.assert_equal({:id => 10}, parameters)
|
39
|
+
end
|
40
|
+
@controller.post_with_args(:post, {:id => 10})
|
41
|
+
|
42
|
+
@controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
|
43
|
+
instance.assert_equal "post_raw_string", action_name
|
44
|
+
instance.assert_equal :post, method
|
45
|
+
instance.assert_equal("raw_string_data", parameters)
|
46
|
+
end
|
47
|
+
@controller.post_raw_string(:post, "raw_string_data")
|
48
|
+
|
49
|
+
@controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
|
50
|
+
instance.assert_equal "multipart_with_args", action_name
|
51
|
+
instance.assert_equal :multipart, method
|
52
|
+
instance.assert_equal({:id => 10}, parameters)
|
53
|
+
end
|
54
|
+
@controller.multipart_with_args(:multipart, {:id => 10})
|
55
|
+
|
56
|
+
@controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
|
57
|
+
instance.assert_equal "get_with_args", action_name
|
58
|
+
instance.assert_equal :get, method
|
59
|
+
instance.assert_equal({:id => 10}, parameters)
|
60
|
+
end
|
61
|
+
#Default method is get
|
62
|
+
@controller.get_with_args({:id => 10})
|
63
|
+
|
64
|
+
assert_raise RemoteController::Base::RemoteControllerError do
|
65
|
+
@controller.invalid_args_action("invalid arg", "invalid arg again")
|
66
|
+
end
|
67
|
+
|
68
|
+
assert_raise RemoteController::Base::RemoteControllerError do
|
69
|
+
@controller.invalid_args_action(:get, "invalid arg", "invalid arg again")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#++
|
2
|
+
# (The MIT License)
|
3
|
+
#
|
4
|
+
# Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# 'Software'), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
20
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
21
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
22
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
23
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
|
25
|
+
require File.dirname(__FILE__) + '/test_helper'
|
26
|
+
require 'stringio'
|
27
|
+
|
28
|
+
class RemoteControllerCompositeReadIOTest < Test::Unit::TestCase
|
29
|
+
|
30
|
+
include RemoteController::Multipart
|
31
|
+
|
32
|
+
def setup
|
33
|
+
@io = CompositeReadIO.new(CompositeReadIO.new(StringIO.new('the '), StringIO.new('quick ')),
|
34
|
+
StringIO.new('brown '), StringIO.new('fox'))
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_full_read_from_several_ios
|
38
|
+
assert_equal 'the quick brown fox', @io.read
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_partial_read
|
42
|
+
assert_equal 'the quick', @io.read(9)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_partial_read_to_boundary
|
46
|
+
assert_equal 'the quick ', @io.read(10)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_read_with_size_larger_than_available
|
50
|
+
assert_equal 'the quick brown fox', @io.read(32)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_read_into_buffer
|
54
|
+
buf = ''
|
55
|
+
@io.read(nil, buf)
|
56
|
+
assert_equal 'the quick brown fox', buf
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_multiple_reads
|
60
|
+
assert_equal 'the ', @io.read(4)
|
61
|
+
assert_equal 'quic', @io.read(4)
|
62
|
+
assert_equal 'k br', @io.read(4)
|
63
|
+
assert_equal 'own ', @io.read(4)
|
64
|
+
assert_equal 'fox', @io.read(4)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_read_after_end
|
68
|
+
@io.read
|
69
|
+
assert_equal "", @io.read
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_read_after_end_with_amount
|
73
|
+
@io.read(32)
|
74
|
+
assert_equal nil, @io.read(32)
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#--
|
2
|
+
# (c) Copyright 2007-2008 Nick Sieger.
|
3
|
+
# See the file README.txt included with the distribution for
|
4
|
+
# software license details.
|
5
|
+
#++
|
6
|
+
# (The MIT License)
|
7
|
+
#
|
8
|
+
# Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
11
|
+
# a copy of this software and associated documentation files (the
|
12
|
+
# 'Software'), to deal in the Software without restriction, including
|
13
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
14
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
15
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
16
|
+
# the following conditions:
|
17
|
+
#
|
18
|
+
# The above copyright notice and this permission notice shall be
|
19
|
+
# included in all copies or substantial portions of the Software.
|
20
|
+
#
|
21
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
22
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
23
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
24
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
25
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
26
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
27
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
|
+
|
29
|
+
require File.dirname(__FILE__) + '/test_helper'
|
30
|
+
|
31
|
+
class RemoteControllerCookiesContainerTest < Test::Unit::TestCase
|
32
|
+
|
33
|
+
def setup
|
34
|
+
@container = RemoteController::CookiesContainer.new()
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_process
|
38
|
+
cookies_str = "auth_token=tooken; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT, _session=9999; path=/; HttpOnly"
|
39
|
+
@container.process(cookies_str)
|
40
|
+
assert_not_nil @container["_session"]
|
41
|
+
assert_equal "9999", @container["_session"].value
|
42
|
+
|
43
|
+
assert_not_nil @container["auth_token"]
|
44
|
+
assert_equal "tooken", @container["auth_token"].value
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_to_header
|
48
|
+
cookies_str = "auth_token=tooken; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT, _session=9999; path=/; HttpOnly"
|
49
|
+
@container.process(cookies_str)
|
50
|
+
|
51
|
+
assert_equal "_session=9999; auth_token=tooken;", @container.to_header
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_empty?
|
55
|
+
assert @container.empty?
|
56
|
+
|
57
|
+
cookies_str = "_session=9999;"
|
58
|
+
@container.process(cookies_str)
|
59
|
+
|
60
|
+
assert !@container.empty?
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class RemoteControllerErrorHandlingTest < Test::Unit::TestCase
|
4
|
+
BASE_SERVER_PORT = 5982
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@server_port = BASE_SERVER_PORT + rand(100)
|
8
|
+
@context = HttpTesting::Context.new(@server_port)
|
9
|
+
@controller = RemoteController::Base.new("http://localhost:#{@server_port}/test_controller")
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_exception_raised
|
13
|
+
@context.start do |request, response|
|
14
|
+
response.status = 500
|
15
|
+
end
|
16
|
+
assert_raise(Net::HTTPFatalError) { @controller.just_get }
|
17
|
+
@context.wait
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_callback_invoked
|
21
|
+
first_invoked = false
|
22
|
+
second_invoked = false
|
23
|
+
@controller.on_error do |error|
|
24
|
+
first_invoked = true
|
25
|
+
end
|
26
|
+
@controller.on_error do |error|
|
27
|
+
second_invoked = true
|
28
|
+
end
|
29
|
+
@context.start do |request, response|
|
30
|
+
response.status = 500
|
31
|
+
end
|
32
|
+
assert_raise(Net::HTTPFatalError) { @controller.just_get }
|
33
|
+
@context.wait
|
34
|
+
|
35
|
+
assert(first_invoked, "First was not invoked")
|
36
|
+
assert(second_invoked, "Second was not invoked")
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class RemoteControllerTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
BASE_SERVER_PORT = 4982
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@server_port = BASE_SERVER_PORT + rand(100)
|
9
|
+
@context = HttpTesting::Context.new(@server_port)
|
10
|
+
@controller = RemoteController::Base.new("http://localhost:#{@server_port}/test_controller")
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_no_auto_unescape_response
|
14
|
+
expected = "String with spaces and some symbols that to be escaped &= <>>><<>><<>"
|
15
|
+
@context.start do |request, response|
|
16
|
+
response.body = CGI.escape(expected)
|
17
|
+
end
|
18
|
+
actual = @controller.some_post(:post)
|
19
|
+
@context.wait
|
20
|
+
assert_equal(CGI.escape(expected), actual)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_invoke_no_args_get
|
24
|
+
@context.start do |request, response|
|
25
|
+
assert_equal "GET", request.request_method
|
26
|
+
assert_equal "/test_controller/no_args_get", request.path
|
27
|
+
end
|
28
|
+
@controller.no_args_get
|
29
|
+
@context.wait
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_invoke_args_get
|
33
|
+
@context.start do |request, response|
|
34
|
+
assert_equal "GET", request.request_method
|
35
|
+
assert_equal "/test_controller/args_get?arg1=value1&arg2=value2", request.path + "?" + request.query_string
|
36
|
+
end
|
37
|
+
@controller.args_get(:arg1 => "value1", :arg2 => "value2")
|
38
|
+
@context.wait
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_invoke_no_args_post
|
42
|
+
@context.start do |request, response|
|
43
|
+
assert_equal "POST", request.request_method
|
44
|
+
assert_equal "/test_controller/no_args_post", request.path
|
45
|
+
end
|
46
|
+
@controller.no_args_post(:post)
|
47
|
+
@context.wait
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_invoke_args_post
|
51
|
+
@context.start do |request, response|
|
52
|
+
assert_equal "POST", request.request_method
|
53
|
+
assert_equal to_param({:arg1 => "value1", :arg2 => "value2"}), request.body
|
54
|
+
end
|
55
|
+
@controller.args_post(:post, {:arg1 => "value1", :arg2 => "value2"})
|
56
|
+
@context.wait
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_invoke_string_post
|
60
|
+
@context.start do |request, response|
|
61
|
+
assert_equal "POST", request.request_method
|
62
|
+
assert_equal "string_post", request.body
|
63
|
+
end
|
64
|
+
@controller.args_post(:post, "string_post")
|
65
|
+
@context.wait
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_invoke_args_multipart
|
69
|
+
@context.start do |request, response|
|
70
|
+
assert_equal "POST", request.request_method
|
71
|
+
assert_equal ["multipart/form-data; boundary=#{Net::HTTP::Post::Multipart::DEFAULT_BOUNDARY}"], request.header["content-type"]
|
72
|
+
end
|
73
|
+
@controller.args_post(:multipart, {:arg1 => "value1", :arg2 => "value2"})
|
74
|
+
@context.wait
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_cookies_container
|
78
|
+
@context.start do |request, response|
|
79
|
+
response["set-cookie"] = "_session=9999; path=/; HttpOnly"
|
80
|
+
end
|
81
|
+
@controller.action
|
82
|
+
@context.wait
|
83
|
+
|
84
|
+
assert_equal "9999", @controller.cookies_container["_session"].value
|
85
|
+
|
86
|
+
container = @controller.cookies_container
|
87
|
+
|
88
|
+
@controller = RemoteController::Base.new("http://localhost:#{@server_port}/test_controller")
|
89
|
+
@controller.cookies_container = container
|
90
|
+
@context.start do |request, response|
|
91
|
+
assert_equal 1, request.cookies.length
|
92
|
+
assert_equal "_session=9999;", request.cookies[0].to_s
|
93
|
+
end
|
94
|
+
@controller.action
|
95
|
+
@context.wait
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#--
|
2
|
+
# (c) Copyright 2007-2008 Nick Sieger.
|
3
|
+
# See the file README.txt included with the distribution for
|
4
|
+
# software license details.
|
5
|
+
#++
|
6
|
+
# (The MIT License)
|
7
|
+
#
|
8
|
+
# Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
11
|
+
# a copy of this software and associated documentation files (the
|
12
|
+
# 'Software'), to deal in the Software without restriction, including
|
13
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
14
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
15
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
16
|
+
# the following conditions:
|
17
|
+
#
|
18
|
+
# The above copyright notice and this permission notice shall be
|
19
|
+
# included in all copies or substantial portions of the Software.
|
20
|
+
#
|
21
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
22
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
23
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
24
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
25
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
26
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
27
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
|
+
|
29
|
+
require File.dirname(__FILE__) + '/test_helper'
|
30
|
+
|
31
|
+
class RemoteControllerMultiPartTest < Test::Unit::TestCase
|
32
|
+
|
33
|
+
include RemoteController::Multipart
|
34
|
+
|
35
|
+
TEMP_FILE = "temp.txt"
|
36
|
+
|
37
|
+
HTTPPost = Struct.new("HTTPPost", :content_length, :body_stream, :content_type)
|
38
|
+
HTTPPost.module_eval do
|
39
|
+
def set_content_type(type, params = {})
|
40
|
+
self.content_type = type + params.map{|k,v|"; #{k}=#{v}"}.join('')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup
|
45
|
+
@another_temp_file = "#{File.dirname(__FILE__)}/../tmp/temp_file.txt"
|
46
|
+
end
|
47
|
+
|
48
|
+
def teardown
|
49
|
+
File.delete(TEMP_FILE) rescue nil
|
50
|
+
File.delete(@another_temp_file) rescue nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_form_multipart_body
|
54
|
+
File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
|
55
|
+
@io = File.open(TEMP_FILE)
|
56
|
+
UploadIO.convert! @io, "text/plain", TEMP_FILE, TEMP_FILE
|
57
|
+
assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
|
58
|
+
end
|
59
|
+
def test_form_multipart_body_no_text_plain
|
60
|
+
File.open(@another_temp_file, "w") {|f| f << "1234567890"}
|
61
|
+
@io = File.open(@another_temp_file)
|
62
|
+
UploadIO.convert! @io, "application/octet-stream", File.basename(@another_temp_file), File.basename(@another_temp_file)
|
63
|
+
file_part = RemoteController::Multipart::Parts::FilePart.new("--bound", "name", @io)
|
64
|
+
end
|
65
|
+
def test_form_multipart_body_put
|
66
|
+
File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
|
67
|
+
@io = File.open(TEMP_FILE)
|
68
|
+
UploadIO.convert! @io, "text/plain", TEMP_FILE, TEMP_FILE
|
69
|
+
assert_results Net::HTTP::Put::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_form_multipart_body_with_stringio
|
73
|
+
@io = StringIO.new("1234567890")
|
74
|
+
UploadIO.convert! @io, "text/plain", TEMP_FILE, TEMP_FILE
|
75
|
+
assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
|
76
|
+
end
|
77
|
+
|
78
|
+
def assert_results(post)
|
79
|
+
assert post.content_length && post.content_length > 0
|
80
|
+
assert post.body_stream
|
81
|
+
assert_equal "multipart/form-data; boundary=#{Multipartable::DEFAULT_BOUNDARY}", post['content-type']
|
82
|
+
body = post.body_stream.read
|
83
|
+
boundary_regex = Regexp.quote Multipartable::DEFAULT_BOUNDARY
|
84
|
+
assert body =~ /1234567890/
|
85
|
+
# ensure there is at least one boundary
|
86
|
+
assert body =~ /^--#{boundary_regex}\r\n/
|
87
|
+
# ensure there is an epilogue
|
88
|
+
assert body =~ /^--#{boundary_regex}--\r\n/
|
89
|
+
assert body =~ /text\/plain/
|
90
|
+
end
|
91
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__)
|
4
|
+
require 'bundler/setup'
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'cgi'
|
8
|
+
require 'http-testing'
|
9
|
+
require 'remote_controller'
|
10
|
+
|
11
|
+
class Test::Unit::TestCase
|
12
|
+
include RemoteController::CGIHelpers
|
13
|
+
end
|
data/tmp/.gitkeep
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: remote-controller
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Evgeny Myasishchev
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-09-12 00:00:00 +03:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: http-testing
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 1
|
30
|
+
- 3
|
31
|
+
version: 0.1.3
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
description: Library to simplify remote controller actions invocation.
|
35
|
+
email:
|
36
|
+
- evgeny.myasishchev@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- Gemfile
|
46
|
+
- MIT-LICENSE
|
47
|
+
- README.rdoc
|
48
|
+
- Rakefile
|
49
|
+
- lib/remote-controller.rb
|
50
|
+
- lib/remote_controller.rb
|
51
|
+
- lib/remote_controller/base.rb
|
52
|
+
- lib/remote_controller/cgi_helpers.rb
|
53
|
+
- lib/remote_controller/cookies_container.rb
|
54
|
+
- lib/remote_controller/multipart.rb
|
55
|
+
- lib/remote_controller/multipart/composite_io.rb
|
56
|
+
- lib/remote_controller/multipart/multipartable.rb
|
57
|
+
- lib/remote_controller/multipart/parts.rb
|
58
|
+
- lib/remote_controller/multipart/parts/epilogue_part.rb
|
59
|
+
- lib/remote_controller/multipart/parts/file_part.rb
|
60
|
+
- lib/remote_controller/multipart/parts/param_part.rb
|
61
|
+
- lib/remote_controller/multipart/parts/part.rb
|
62
|
+
- lib/remote_controller/version.rb
|
63
|
+
- lib/samples/sample1.rb
|
64
|
+
- lib/samples/sample1.txt
|
65
|
+
- remote-controller.gemspec
|
66
|
+
- test/remote_controller_action_args_test.rb
|
67
|
+
- test/remote_controller_composite_io_test.rb
|
68
|
+
- test/remote_controller_cookies_container_test.rb
|
69
|
+
- test/remote_controller_error_handling_test.rb
|
70
|
+
- test/remote_controller_test.rb
|
71
|
+
- test/remote_controller_test_multipart_test.rb
|
72
|
+
- test/test_helper.rb
|
73
|
+
- tmp/.gitkeep
|
74
|
+
has_rdoc: true
|
75
|
+
homepage: http://github.com/evgeny-myasishchev/remote-controller
|
76
|
+
licenses: []
|
77
|
+
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
version: "0"
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project: remote-controller
|
100
|
+
rubygems_version: 1.3.6
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Helps to invoke actions of remote controllers.
|
104
|
+
test_files: []
|
105
|
+
|