actionservice 0.2.102 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog +1 -1
- data/README +48 -14
- data/Rakefile +6 -15
- data/TODO +2 -0
- data/examples/{soap → googlesearch}/README +26 -10
- data/examples/{soap/lib → googlesearch/delegated}/google_search_service.rb +22 -9
- data/examples/{soap/app/controllers → googlesearch/delegated}/search_controller.rb +0 -0
- data/examples/googlesearch/direct/search_controller.rb +106 -0
- data/examples/metaWeblog/README +28 -0
- data/examples/metaWeblog/blog_controller.rb +117 -0
- data/lib/action_service/base.rb +40 -2
- data/lib/action_service/container.rb +87 -18
- data/lib/action_service/exporting.rb +6 -12
- data/lib/action_service/invocation.rb +51 -10
- data/lib/action_service/protocol/abstract.rb +11 -11
- data/lib/action_service/protocol/registry.rb +7 -7
- data/lib/action_service/protocol/soap.rb +18 -10
- data/lib/action_service/protocol/xmlrpc.rb +65 -32
- data/lib/action_service/router/action_controller.rb +24 -10
- data/lib/action_service/router/wsdl.rb +5 -5
- data/lib/action_service/struct.rb +17 -1
- data/lib/action_service/support/class_inheritable_options.rb +1 -1
- data/test/protocol_xmlrpc_test.rb +23 -3
- metadata +12 -9
data/ChangeLog
CHANGED
data/README
CHANGED
@@ -1,18 +1,16 @@
|
|
1
1
|
= Action Service -- Serving APIs on rails
|
2
2
|
|
3
|
-
Action Service provides a way to publish
|
3
|
+
Action Service provides a way to publish interoperable web service APIs with
|
4
4
|
Rails without wasting time delving into protocol details.
|
5
5
|
|
6
6
|
|
7
7
|
== Features
|
8
8
|
|
9
9
|
* SOAP RPC protocol support
|
10
|
-
* Dynamic WSDL generation
|
10
|
+
* Dynamic WSDL generation for exported APIs
|
11
11
|
* XML-RPC protocol support
|
12
|
-
*
|
13
|
-
|
14
|
-
* Using Active Record derivatives in return signatures automatically generates
|
15
|
-
a structured type equivalent that can be sent over the wire
|
12
|
+
* Type signature hints to improve interoperability with static languages
|
13
|
+
* Active Record model class support in signatures
|
16
14
|
|
17
15
|
|
18
16
|
== Integration with Action Pack
|
@@ -21,21 +19,57 @@ Action Service can be integrated in two different dispatching modes, _Direct_ an
|
|
21
19
|
_Delegated_.
|
22
20
|
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
22
|
+
== Direct dispatching
|
23
|
+
|
24
|
+
This is the default mode. In this mode, controller actions can be exported as
|
25
|
+
API methods, and parameters for incoming method calls will be placed in
|
26
|
+
<tt>@params</tt>. The return value of the action will be sent back as the
|
27
|
+
return value to the caller.
|
28
|
+
|
29
|
+
Requests from callers will be sent to <tt>/controller_name/api</tt> using
|
30
|
+
HTTP POST.
|
31
|
+
|
32
|
+
==== Direct dispatching example
|
33
|
+
|
34
|
+
class PersonApiController < ApplicationController
|
35
|
+
def add
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove
|
39
|
+
end
|
40
|
+
|
41
|
+
export :add
|
42
|
+
export :remove
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
== Delegated dispatching
|
47
|
+
|
48
|
+
This mode can be turned on by setting the +service_dispatching_mode+ option.
|
49
|
+
In this mode, a controller is a container for one or more service objects,
|
50
|
+
each implementing a seperate API.
|
51
|
+
|
52
|
+
Requests from callers will be sent to <tt>/controller_name/service_name</tt>
|
53
|
+
using HTTP POST.
|
54
|
+
|
55
|
+
==== Delegated dispatching example
|
56
|
+
|
57
|
+
class ApiController < ApplicationController
|
58
|
+
service_dispatching_mode :delegated
|
59
|
+
|
60
|
+
service :person, PersonApi.new
|
61
|
+
service :customer, CustomerApi.new
|
62
|
+
end
|
30
63
|
|
31
64
|
|
32
65
|
== Dependencies
|
33
66
|
|
34
|
-
Action Service requires that the Action Pack and Active Record
|
67
|
+
Action Service requires that the Action Pack and Active Record are either
|
35
68
|
available to be required immediately or are accessible as GEMs.
|
36
69
|
|
37
70
|
It also requires a version of Ruby that includes SOAP support in the standard
|
38
|
-
library. At least version 1.8.2 final (2004-12-25) of Ruby is recommended
|
71
|
+
library. At least version 1.8.2 final (2004-12-25) of Ruby is recommended, this
|
72
|
+
is the version tested against.
|
39
73
|
|
40
74
|
|
41
75
|
== Download
|
data/Rakefile
CHANGED
@@ -5,10 +5,11 @@ require 'rake/rdoctask'
|
|
5
5
|
require 'rake/packagetask'
|
6
6
|
require 'rake/gempackagetask'
|
7
7
|
require 'rake/contrib/rubyforgepublisher'
|
8
|
+
require 'fileutils'
|
8
9
|
|
9
10
|
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
10
11
|
PKG_NAME = 'actionservice'
|
11
|
-
PKG_VERSION = '0.
|
12
|
+
PKG_VERSION = '0.3.0' + PKG_BUILD
|
12
13
|
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
13
14
|
|
14
15
|
desc "Default Task"
|
@@ -70,20 +71,10 @@ Rake::GemPackageTask.new(spec) do |p|
|
|
70
71
|
end
|
71
72
|
|
72
73
|
|
73
|
-
desc "Publish
|
74
|
-
task :
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
desc "Publish the API documentation"
|
80
|
-
task :pdoc => [:rdoc] do
|
81
|
-
# Rake::SshDirPublisher.new("davidhh@comox.textdrive.com", "public_html/am", "doc").upload
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
|
-
desc "Publish to RubyForge"
|
86
|
-
task :rubyforge do
|
74
|
+
desc "Publish API docs to RubyForge"
|
75
|
+
task :pdoc => [:rdoc] do
|
76
|
+
FileUtils.mkdir_p 'html'
|
77
|
+
FileUtils.mv 'doc', 'html/api'
|
87
78
|
Rake::RubyForgePublisher.new('actionservice', 'ljb').upload
|
88
79
|
end
|
89
80
|
|
data/TODO
CHANGED
@@ -2,24 +2,39 @@
|
|
2
2
|
|
3
3
|
|
4
4
|
This example shows how one would implement an API like Google
|
5
|
-
Search
|
5
|
+
Search that uses lots of structured types.
|
6
6
|
|
7
|
+
There are examples for "Direct" and "Delegated" dispatching
|
8
|
+
modes.
|
7
9
|
|
8
|
-
|
10
|
+
|
11
|
+
= Running
|
9
12
|
|
10
13
|
1. Ensure you have the 'actionservice' Gem installed. You can generate it using
|
11
14
|
this command:
|
12
15
|
|
13
16
|
$ rake package
|
14
17
|
|
15
|
-
2. Copy lib/google_search_service.rb and app/controllers/search_controller.rb
|
16
|
-
into a Rails project.
|
17
18
|
|
18
|
-
|
19
|
+
2. Edit config/environment.rb, and add the following line after the rest of the
|
19
20
|
require_gem statements:
|
20
21
|
|
21
22
|
require_gem 'actionservice'
|
22
23
|
|
24
|
+
|
25
|
+
3. "Direct" example:
|
26
|
+
|
27
|
+
* Copy direct/search_controller.rb to "app/controllers"
|
28
|
+
in a Rails project.
|
29
|
+
|
30
|
+
"Delegated" example:
|
31
|
+
|
32
|
+
* Copy delegated/search_controller.rb to "app/controllers"
|
33
|
+
in a Rails project.
|
34
|
+
* Copy delegated/google_search_service.rb to "lib"
|
35
|
+
in a Rails project.
|
36
|
+
|
37
|
+
|
23
38
|
4. Go to the url http://URL/search/wsdl in a browser, and check that it looks
|
24
39
|
correct.
|
25
40
|
|
@@ -27,7 +42,9 @@ Search, that uses lots of structured types.
|
|
27
42
|
and see how close (or not) the generated version is.
|
28
43
|
|
29
44
|
Note that I used GoogleSearch as the canonical "best practice"
|
30
|
-
interoperable example
|
45
|
+
interoperable example when implementing WSDL/SOAP support, which might
|
46
|
+
explain extreme similarities :)
|
47
|
+
|
31
48
|
|
32
49
|
5. Test that it works with .NET (Mono in this example):
|
33
50
|
|
@@ -79,8 +96,7 @@ Search, that uses lots of structured types.
|
|
79
96
|
|
80
97
|
|
81
98
|
If you had the application running (on the same host you got
|
82
|
-
the WSDL from), you should see something like this
|
83
|
-
with those in lib/google_search_service.rb):
|
99
|
+
the WSDL from), you should see something like this:
|
84
100
|
|
85
101
|
|
86
102
|
documentFiltering: True
|
@@ -108,8 +124,8 @@ Search, that uses lots of structured types.
|
|
108
124
|
|
109
125
|
|
110
126
|
Also, if an API method throws an exception, it will be sent back to the
|
111
|
-
caller in
|
112
|
-
their side with a meaningful error message.
|
127
|
+
caller in the protocol's exception format, so they should get an exception
|
128
|
+
thrown on their side with a meaningful error message.
|
113
129
|
|
114
130
|
If you don't like this behaviour, you can do:
|
115
131
|
|
@@ -80,14 +80,27 @@ class GoogleSearchService < ActionService::Base
|
|
80
80
|
result
|
81
81
|
end
|
82
82
|
|
83
|
-
def category(name, encoding)
|
84
|
-
cat = DirectoryCategory.new
|
85
|
-
cat.fullViewableName = name.dup
|
86
|
-
cat.specialEncoding = encoding.dup
|
87
|
-
cat
|
88
|
-
end
|
89
|
-
|
90
83
|
export :doGetCachedPage, :returns => [String], :expects => [{:key=>String}, {:url=>String}]
|
91
|
-
export :doGetSpellingSuggestion, :returns => [String], :expects => [String, String]
|
92
|
-
|
84
|
+
export :doGetSpellingSuggestion, :returns => [String], :expects => [{:key=>String}, {:phrase=>String}]
|
85
|
+
|
86
|
+
export :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
|
87
|
+
{:key=>String},
|
88
|
+
{:q=>String},
|
89
|
+
{:start=>Integer},
|
90
|
+
{:maxResults=>Integer},
|
91
|
+
{:filter=>TrueClass},
|
92
|
+
{:restrict=>String},
|
93
|
+
{:safeSearch=>TrueClass},
|
94
|
+
{:lr=>String},
|
95
|
+
{:ie=>String},
|
96
|
+
{:oe=>String}
|
97
|
+
]
|
98
|
+
|
99
|
+
private
|
100
|
+
def category(name, encoding)
|
101
|
+
cat = DirectoryCategory.new
|
102
|
+
cat.fullViewableName = name.dup
|
103
|
+
cat.specialEncoding = encoding.dup
|
104
|
+
cat
|
105
|
+
end
|
93
106
|
end
|
File without changes
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class DirectoryCategory < ActionService::Struct
|
2
|
+
member :fullViewableName, String
|
3
|
+
member :specialEncoding, String
|
4
|
+
end
|
5
|
+
|
6
|
+
class ResultElement < ActionService::Struct
|
7
|
+
member :summary, String
|
8
|
+
member :URL, String
|
9
|
+
member :snippet, String
|
10
|
+
member :title, String
|
11
|
+
member :cachedSize, String
|
12
|
+
member :relatedInformationPresent, TrueClass
|
13
|
+
member :hostName, String
|
14
|
+
member :directoryCategory, DirectoryCategory
|
15
|
+
member :directoryTitle, String
|
16
|
+
end
|
17
|
+
|
18
|
+
class GoogleSearchResult < ActionService::Struct
|
19
|
+
member :documentFiltering, TrueClass
|
20
|
+
member :searchComments, String
|
21
|
+
member :estimatedTotalResultsCount, Integer
|
22
|
+
member :estimateIsExact, TrueClass
|
23
|
+
member :resultElements, [ResultElement]
|
24
|
+
member :searchQuery, String
|
25
|
+
member :startIndex, Integer
|
26
|
+
member :endIndex, Integer
|
27
|
+
member :searchTips, String
|
28
|
+
member :directoryCategories, [DirectoryCategory]
|
29
|
+
member :searchTime, Float
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
class SearchController < ApplicationController
|
34
|
+
wsdl_service_name 'GoogleSearch'
|
35
|
+
export_name_mangling false
|
36
|
+
|
37
|
+
def doGetCachedPage
|
38
|
+
"<html><body>i am a cached page. my key was %s, url was %s</body></html>" % [@params['key'], @params['url']]
|
39
|
+
end
|
40
|
+
|
41
|
+
def doSpellingSuggestion
|
42
|
+
"%s: Did you mean '%s'?" % [@params['key'], @params['phrase']]
|
43
|
+
end
|
44
|
+
|
45
|
+
def doGoogleSearch
|
46
|
+
resultElement = ResultElement.new
|
47
|
+
resultElement.summary = "ONlamp.com: Rolling with Ruby on Rails"
|
48
|
+
resultElement.URL = "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html"
|
49
|
+
resultElement.snippet = "Curt Hibbs shows off Ruby on Rails by building a simple application that requires " +
|
50
|
+
"almost no Ruby experience. ... Rolling with Ruby on Rails. ..."
|
51
|
+
resultElement.title = "Teh Railz0r"
|
52
|
+
resultElement.cachedSize = "Almost no lines of code!"
|
53
|
+
resultElement.relatedInformationPresent = true
|
54
|
+
resultElement.hostName = "rubyonrails.com"
|
55
|
+
resultElement.directoryCategory = category("Web Development", "UTF-8")
|
56
|
+
|
57
|
+
result = GoogleSearchResult.new
|
58
|
+
result.documentFiltering = @params['filter']
|
59
|
+
result.searchComments = ""
|
60
|
+
result.estimatedTotalResultsCount = 322000
|
61
|
+
result.estimateIsExact = false
|
62
|
+
result.resultElements = [resultElement]
|
63
|
+
result.searchQuery = "http://www.google.com/search?q=ruby+on+rails"
|
64
|
+
result.startIndex = @params['start']
|
65
|
+
result.endIndex = @params['start'] + @params['maxResults']
|
66
|
+
result.searchTips = "\"on\" is a very common word and was not included in your search [details]"
|
67
|
+
result.searchTime = 0.000001
|
68
|
+
|
69
|
+
# For Mono, we have to clone objects if they're referenced by more than one place, otherwise
|
70
|
+
# the Ruby SOAP collapses them into one instance and uses references all over the
|
71
|
+
# place, confusing Mono.
|
72
|
+
#
|
73
|
+
# This has recently been fixed:
|
74
|
+
# http://bugzilla.ximian.com/show_bug.cgi?id=72265
|
75
|
+
result.directoryCategories = [
|
76
|
+
category("Web Development", "UTF-8"),
|
77
|
+
category("Programming", "US-ASCII"),
|
78
|
+
]
|
79
|
+
|
80
|
+
result
|
81
|
+
end
|
82
|
+
|
83
|
+
export :doGetCachedPage, :returns => [String], :expects => [{:key=>String}, {:url=>String}]
|
84
|
+
export :doGetSpellingSuggestion, :returns => [String], :expects => [{:key=>String}, {:phrase=>String}]
|
85
|
+
|
86
|
+
export :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
|
87
|
+
{:key=>String},
|
88
|
+
{:q=>String},
|
89
|
+
{:start=>Integer},
|
90
|
+
{:maxResults=>Integer},
|
91
|
+
{:filter=>TrueClass},
|
92
|
+
{:restrict=>String},
|
93
|
+
{:safeSearch=>TrueClass},
|
94
|
+
{:lr=>String},
|
95
|
+
{:ie=>String},
|
96
|
+
{:oe=>String}
|
97
|
+
]
|
98
|
+
|
99
|
+
private
|
100
|
+
def category(name, encoding)
|
101
|
+
cat = DirectoryCategory.new
|
102
|
+
cat.fullViewableName = name.dup
|
103
|
+
cat.specialEncoding = encoding.dup
|
104
|
+
cat
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
= metaWeblog example
|
2
|
+
|
3
|
+
|
4
|
+
This example shows how one might begin to go about adding metaWeblog
|
5
|
+
(http://www.xmlrpc.com/metaWeblogApi) API support to a Rails-based
|
6
|
+
blogging application.
|
7
|
+
|
8
|
+
|
9
|
+
= Running
|
10
|
+
|
11
|
+
1. Ensure you have the 'actionservice' Gem installed. You can generate it using
|
12
|
+
this command:
|
13
|
+
|
14
|
+
$ rake package
|
15
|
+
|
16
|
+
|
17
|
+
2. Edit config/environment.rb, and add the following line after the rest of the
|
18
|
+
require_gem statements:
|
19
|
+
|
20
|
+
require_gem 'actionservice'
|
21
|
+
|
22
|
+
|
23
|
+
3. Copy blog_controller.rb to "app/controllers" in a Rails project.
|
24
|
+
|
25
|
+
|
26
|
+
4. Fire up a desktop blogging application (such as BloGTK on Linux),
|
27
|
+
point it at http://localhost:3000/blog/api, and try creating or
|
28
|
+
editing blog posts.
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# structures as defined by the metaWeblog/blogger
|
2
|
+
# specifications.
|
3
|
+
module Blog
|
4
|
+
class Enclosure < ActionService::Struct
|
5
|
+
member :url, String
|
6
|
+
member :length, Integer
|
7
|
+
member :type, String
|
8
|
+
end
|
9
|
+
|
10
|
+
class Source < ActionService::Struct
|
11
|
+
member :url, String
|
12
|
+
member :name, String
|
13
|
+
end
|
14
|
+
|
15
|
+
class Post < ActionService::Struct
|
16
|
+
member :title, String
|
17
|
+
member :link, String
|
18
|
+
member :description, String
|
19
|
+
member :author, String
|
20
|
+
member :category, String
|
21
|
+
member :comments, String
|
22
|
+
member :enclosure, Enclosure
|
23
|
+
member :guid, String
|
24
|
+
member :pubDate, String
|
25
|
+
member :source, Source
|
26
|
+
end
|
27
|
+
|
28
|
+
class Blog < ActionService::Struct
|
29
|
+
member :url, String
|
30
|
+
member :blogid, String
|
31
|
+
member :blogName, String
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
class BlogController < ApplicationController
|
37
|
+
export_name_mangling false
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@postid = 0
|
41
|
+
end
|
42
|
+
|
43
|
+
def newPost
|
44
|
+
$stderr.puts 'Creating post: username=%s password=%s struct=%s' % [
|
45
|
+
@params['username'],
|
46
|
+
@params['password'],
|
47
|
+
@params['struct'].inspect
|
48
|
+
]
|
49
|
+
(@postid += 1).to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def editPost
|
53
|
+
$stderr.puts 'Editing post: username=%s password=%s struct=%s' % [
|
54
|
+
@params['username'],
|
55
|
+
@params['password'],
|
56
|
+
@params['struct'].inspect
|
57
|
+
]
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
def getUsersBlogs
|
62
|
+
$stderr.puts "Returning user %s's blogs" % @params['username']
|
63
|
+
blog = Blog::Blog.new
|
64
|
+
blog.url = 'http://blog.xeraph.org'
|
65
|
+
blog.blogid = 'sttm'
|
66
|
+
blog.blogName = 'slave to the machine'
|
67
|
+
[blog]
|
68
|
+
end
|
69
|
+
|
70
|
+
def getRecentPosts
|
71
|
+
$stderr.puts "Returning recent posts (%d requested)" % @params['numberOfPosts']
|
72
|
+
post1 = Blog::Post.new
|
73
|
+
post1.title = 'first post!'
|
74
|
+
post1.link = 'http://blog.xeraph.org/testOne.html'
|
75
|
+
post1.description = 'this is the first post'
|
76
|
+
post2 = Blog::Post.new
|
77
|
+
post2.title = 'second post!'
|
78
|
+
post2.link = 'http://blog.xeraph.org/testTwo.html'
|
79
|
+
post2.description = 'this is the second post'
|
80
|
+
[post1, post2]
|
81
|
+
end
|
82
|
+
|
83
|
+
export :newPost, :returns => [String], :expects => [
|
84
|
+
{:blogid=>String},
|
85
|
+
{:username=>String},
|
86
|
+
{:password=>String},
|
87
|
+
{:struct=>Blog::Post},
|
88
|
+
{:publish=>TrueClass},
|
89
|
+
]
|
90
|
+
|
91
|
+
export :editPost, :returns => [TrueClass], :expects => [
|
92
|
+
{:postid=>String},
|
93
|
+
{:username=>String},
|
94
|
+
{:password=>String},
|
95
|
+
{:struct=>Blog::Post},
|
96
|
+
{:publish=>TrueClass},
|
97
|
+
]
|
98
|
+
|
99
|
+
export :getPost, :returns => [Blog::Post], :expects => [
|
100
|
+
{:postid=>String},
|
101
|
+
{:username=>String},
|
102
|
+
{:password=>String},
|
103
|
+
]
|
104
|
+
|
105
|
+
export :getUsersBlogs, :returns => [[Blog::Blog]], :expects => [
|
106
|
+
{:appkey=>String},
|
107
|
+
{:username=>String},
|
108
|
+
{:password=>String},
|
109
|
+
]
|
110
|
+
|
111
|
+
export :getRecentPosts, :returns => [[Blog::Post]], :expects => [
|
112
|
+
{:blogid=>String},
|
113
|
+
{:username=>String},
|
114
|
+
{:password=>String},
|
115
|
+
{:numberOfPosts=>Integer},
|
116
|
+
]
|
117
|
+
end
|