jeremydurham-serviceproxy 0.0.1 → 0.1.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/README CHANGED
@@ -2,44 +2,26 @@ ServiceProxy
2
2
 
3
3
  ServiceProxy is a lightweight SOAP library for Ruby.
4
4
 
5
- How it works
6
-
7
- Unlike SOAP4R, this library takes a very different approach to building
8
- requests and parsing responses. There is little magic and no code
9
- generation with this library.
10
-
11
- You will need to understand some simple things about SOAP before you
12
- can use this library. Using a tool like SOAPUI or SoapClient, you can
13
- easily get enough information to use this library with existing SOAP
14
- services.
15
-
16
- For each service endpoint you want to connect to, you will need to
17
- subclass ServiceProxy.
18
-
19
- Let's say you want to call the method cool1 on a service:
20
-
21
- First, you create a subclass:
22
-
23
- class SuperCoolService < ServiceProxy
24
-
25
- def build_cool1(options)
26
- # This will generate a simple SOAP envelope. Using the xml block local, you
27
- # can inject XML into the body of the envelope.
28
- soap_envelope(options) do |xml|
29
- # your XML here
30
- end
31
- end
32
-
33
- def parse_cool1(response)
34
- # The response parameter is a simple Net::HTTP response
35
- # here, we use Hpricot to parse it, but you could use
36
- # Nokogiri, REXML, etc
37
- xml = Hpricot.XML(response.body)
38
- xml.at("cool1Result").inner_text
39
- end
40
- end
41
-
42
- Next, you can attempt to call the service:
43
-
44
- service = SuperCoolService.new(url_to_wsdl)
45
- service.cool1
5
+ HOW IT WORKS
6
+
7
+ Loading the library:
8
+
9
+ require 'rubygems'
10
+ require 'service_proxy/base'
11
+
12
+ GENERATING A PROXY
13
+
14
+ ServiceProxy comes with a simple generator to get started. It can be invoked
15
+ as follows:
16
+
17
+ wsdl2proxy [wsdl]
18
+
19
+ This will generate a file named default.rb, in the current directory. The class
20
+ will be named GeneratedService, and will define build and parse methods for all
21
+ of the available service methods.
22
+
23
+ Please refer to the specs for extended usage examples.
24
+
25
+ CONTRIBUTORS
26
+
27
+ Rich Cavanaugh
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'rake/gempackagetask'
3
3
  require 'rubygems/specification'
4
4
  require 'rake/rdoctask'
5
5
  require 'spec/rake/spectask'
6
- require File.join(File.dirname(__FILE__), 'lib', 'service_proxy')
6
+ require File.join(File.dirname(__FILE__), 'lib', 'service_proxy', 'base')
7
7
 
8
8
  NAME = "serviceproxy"
9
9
  AUTHOR = "Jeremy Durham"
@@ -18,7 +18,7 @@ PROJECT_SUMMARY = SUMMARY
18
18
  PROJECT_DESCRIPTION = SUMMARY
19
19
 
20
20
  PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
21
- GEM_VERSION = ServiceProxy::VERSION + PKG_BUILD
21
+ GEM_VERSION = ServiceProxy::Base::VERSION + PKG_BUILD
22
22
  RELEASE_NAME = "REL #{GEM_VERSION}"
23
23
  #
24
24
  # ==== Gemspec and installation
@@ -26,7 +26,7 @@ RELEASE_NAME = "REL #{GEM_VERSION}"
26
26
 
27
27
  spec = Gem::Specification.new do |s|
28
28
  s.name = NAME
29
- s.version = ServiceProxy::VERSION
29
+ s.version = ServiceProxy::Base::VERSION
30
30
  s.platform = Gem::Platform::RUBY
31
31
  s.has_rdoc = true
32
32
  s.extra_rdoc_files = ["README", "LICENSE"]
@@ -36,6 +36,7 @@ spec = Gem::Specification.new do |s|
36
36
  s.email = EMAIL
37
37
  s.homepage = HOMEPAGE
38
38
  s.require_path = 'lib'
39
+ s.executables = ['wsdl2proxy']
39
40
  s.files = %w(LICENSE README Rakefile) + Dir.glob("{lib,spec}/**/*")
40
41
 
41
42
  s.add_dependency "nokogiri"
@@ -48,7 +49,7 @@ end
48
49
 
49
50
  desc "install the gem locally"
50
51
  task :install => [:clean, :package] do
51
- sh %{sudo gem install pkg/#{NAME}-#{ServiceProxy::VERSION} --no-update-sources}
52
+ sh %{sudo gem install pkg/#{NAME}-#{ServiceProxy::Base::VERSION} --no-update-sources}
52
53
  end
53
54
 
54
55
  desc "create a gemspec file"
@@ -91,4 +92,4 @@ namespace :spec do
91
92
  end
92
93
 
93
94
  desc 'Default: run unit tests.'
94
- task :default => 'spec'
95
+ task :default => 'spec'
data/bin/wsdl2proxy ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'service_proxy/base'
5
+
6
+ def underscore(camel_cased_word)
7
+ camel_cased_word.to_s.gsub(/::/, '/').
8
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
9
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
10
+ tr("-", "_").
11
+ downcase
12
+ end
13
+
14
+ unless ARGV.size > 0
15
+ puts "Usage: #{$0} [endpoint]"
16
+ exit!
17
+ end
18
+
19
+ template_filename = File.dirname(__FILE__) + '/../lib/templates/proxy.rbt'
20
+ proxy = ServiceProxy::Base.new(ARGV[0])
21
+ output_filename = 'default.rb'
22
+ output_klass_name = 'GeneratedService'
23
+ output = ''
24
+ proxy.service_methods.each do |service_method|
25
+ output << <<-EOS
26
+
27
+ def build_#{underscore(service_method)}(options)
28
+ soap_envelope(options) do |xml|
29
+ end
30
+ end
31
+
32
+ def parse_#{underscore(service_method)}(response)
33
+ Hpricot.XML(response.body)
34
+ end
35
+ EOS
36
+ end
37
+ template = File.read(template_filename)
38
+ template.gsub!(/%name%/, output_klass_name)
39
+ template.gsub!(/%body%/, output)
40
+ File.open(output_filename, 'w') { |f| f.puts template }
41
+ puts "Generated proxy #{output_filename} - You can use the proxy by executing #{output_klass_name}.new('#{ARGV[0]}')"
@@ -0,0 +1,154 @@
1
+ require 'rubygems'
2
+ require 'nokogiri'
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'builder'
6
+ require 'uri'
7
+
8
+ module ServiceProxy
9
+ class Base
10
+ VERSION = '0.1.0'
11
+
12
+ attr_accessor :endpoint, :service_methods, :soap_actions, :service_uri, :http, :service_http, :uri, :debug, :wsdl, :target_namespace, :service_ports
13
+
14
+ def initialize(endpoint)
15
+ self.endpoint = endpoint
16
+ self.setup
17
+ end
18
+
19
+ def call_service(options)
20
+ method = options[:method]
21
+ headers = { 'content-type' => 'text/xml; charset=utf-8', 'SOAPAction' => self.soap_actions[method] }
22
+ body = build_request(method, options)
23
+ response = self.service_http.request_post(self.service_uri.path, body, headers)
24
+ parse_response(method, response)
25
+ end
26
+
27
+ protected
28
+
29
+ def setup
30
+ self.soap_actions = {}
31
+ self.service_methods = []
32
+ setup_uri
33
+ self.http = setup_http(self.uri)
34
+ get_wsdl
35
+ parse_wsdl
36
+ setup_namespace
37
+ end
38
+
39
+ def service_uri
40
+ @service_uri ||= if self.respond_to?(:service_port)
41
+ self.service_port
42
+ else
43
+ self.uri
44
+ end
45
+ end
46
+
47
+ def service_http
48
+ @service_http ||= unless self.service_uri == self.uri
49
+ local_http = self.setup_http(self.service_uri)
50
+ setup_https(local_http) if self.service_uri.scheme == 'https'
51
+ local_http
52
+ else
53
+ self.http
54
+ end
55
+ end
56
+
57
+ def setup_http(local_uri)
58
+ raise ArgumentError, "Endpoint URI must be valid" unless local_uri.scheme
59
+ local_http = Net::HTTP.new(local_uri.host, local_uri.port)
60
+ setup_https(local_http) if local_uri.scheme == 'https'
61
+ local_http.set_debug_output(STDOUT) if self.debug
62
+ local_http.read_timeout = 5
63
+ local_http
64
+ end
65
+
66
+ def setup_https(local_http)
67
+ local_http.use_ssl = true
68
+ local_http.verify_mode = OpenSSL::SSL::VERIFY_NONE
69
+ end
70
+
71
+ private
72
+
73
+ def setup_uri
74
+ self.uri = URI.parse(self.endpoint)
75
+ end
76
+
77
+ def get_wsdl
78
+ response = self.http.get("#{self.uri.path}?#{self.uri.query}")
79
+ self.wsdl = Nokogiri.XML(response.body)
80
+ end
81
+
82
+ def parse_wsdl
83
+ method_list = []
84
+ self.wsdl.xpath('//*[name()="soap:operation"]').each do |operation|
85
+ operation_name = operation.parent.get_attribute('name')
86
+ method_list << operation_name
87
+ self.soap_actions[operation_name] = operation.get_attribute('soapAction')
88
+ end
89
+ raise RuntimeError, "Could not parse WSDL" if method_list.empty?
90
+ self.service_methods = method_list.sort
91
+
92
+ port_list = {}
93
+ self.wsdl.xpath('//wsdl:port', {"xmlns:wsdl" => 'http://schemas.xmlsoap.org/wsdl/'}).each do |port|
94
+ name = underscore(port['name'])
95
+ location = port.xpath('./*[@location]').first['location']
96
+ port_list[name] = location
97
+ end
98
+ self.service_ports = port_list
99
+ end
100
+
101
+ def setup_namespace
102
+ self.target_namespace = self.wsdl.namespaces['xmlns:tns']
103
+ end
104
+
105
+ def build_request(method, options)
106
+ builder = underscore("build_#{method}")
107
+ self.respond_to?(builder) ? self.send(builder, options).target! :
108
+ soap_envelope(options).target!
109
+ end
110
+
111
+ def parse_response(method, response)
112
+ parser = underscore("parse_#{method}")
113
+ self.respond_to?(parser) ? self.send(parser, response) :
114
+ raise(NoMethodError, "You must define the parse method: #{parser}")
115
+ end
116
+
117
+ def soap_envelope(options, &block)
118
+ xsd = 'http://www.w3.org/2001/XMLSchema'
119
+ env = 'http://schemas.xmlsoap.org/soap/envelope/'
120
+ xsi = 'http://www.w3.org/2001/XMLSchema-instance'
121
+ xml = Builder::XmlMarkup.new
122
+ xml.env(:Envelope, 'xmlns:xsd' => xsd, 'xmlns:env' => env, 'xmlns:xsi' => xsi) do
123
+ xml.env(:Body) do
124
+ xml.__send__(options[:method].to_sym, "xmlns" => self.target_namespace) do
125
+ yield xml if block_given?
126
+ end
127
+ end
128
+ end
129
+ xml
130
+ end
131
+
132
+ def method_missing(method, *args)
133
+ method_name = method.to_s
134
+ case method_name
135
+ when /_uri$/
136
+ sp_name = method_name.gsub(/_uri$/, '')
137
+ super unless self.service_ports.has_key?(sp_name)
138
+ URI.parse(self.service_ports[sp_name])
139
+ else
140
+ options = args.pop || {}
141
+ super unless self.service_methods.include?(method_name)
142
+ call_service(options.update(:method => method_name))
143
+ end
144
+ end
145
+
146
+ def underscore(camel_cased_word)
147
+ camel_cased_word.to_s.gsub(/::/, '/').
148
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
149
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
150
+ tr("-", "_").
151
+ downcase
152
+ end
153
+ end
154
+ end
@@ -1,7 +1,7 @@
1
1
  require 'hpricot'
2
2
 
3
3
  # Service Endpoints
4
- class InstantMessageService < ServiceProxy
4
+ class InstantMessageService < ServiceProxy::Base
5
5
 
6
6
  def parse_get_version(response)
7
7
  xml = Hpricot.XML(response.body)
@@ -20,7 +20,7 @@ class InstantMessageService < ServiceProxy
20
20
  end
21
21
  end
22
22
 
23
- class ISBNService < ServiceProxy
23
+ class ISBNService < ServiceProxy::Base
24
24
 
25
25
  def build_is_valid_isbn13(options)
26
26
  soap_envelope(options) do |xml|
@@ -32,9 +32,15 @@ class ISBNService < ServiceProxy
32
32
  xml = Hpricot.XML(response.body)
33
33
  xml.at("m:IsValidISBN13Result").inner_text == 'true' ? true : false
34
34
  end
35
+
36
+ def service_port
37
+ local_uri = URI.parse(self.isbn_service_soap_uri.to_s)
38
+ local_uri.path << "?dummy=1"
39
+ local_uri
40
+ end
35
41
  end
36
42
 
37
- class SHAGeneratorService < ServiceProxy
43
+ class SHAGeneratorService < ServiceProxy::Base
38
44
 
39
45
  def build_gen_ssha(options)
40
46
  soap_envelope(options) do |xml|
@@ -47,4 +53,15 @@ class SHAGeneratorService < ServiceProxy
47
53
  xml = Hpricot.XML(response.body)
48
54
  xml.at("return").inner_text
49
55
  end
56
+ end
57
+
58
+ class InvalidSHAGeneratorService < ServiceProxy::Base
59
+
60
+ def build_gen_ssha(options)
61
+ soap_envelope(options) do |xml|
62
+ xml.text(options[:text])
63
+ xml.hashtype(options[:hash_type])
64
+ end
65
+ end
66
+
50
67
  end
@@ -1,15 +1,15 @@
1
1
  require 'rubygems'
2
2
  require 'spec'
3
- require File.dirname(__FILE__) + '/../lib/service_proxy.rb'
4
- require File.dirname(__FILE__) + '/service_helper.rb'
3
+ require File.dirname(__FILE__) + '/../lib/service_proxy/base'
4
+ require File.dirname(__FILE__) + '/service_helper'
5
5
 
6
6
  describe ServiceProxy do
7
7
  it "should raise on an invalid URI" do
8
- lambda { ServiceProxy.new('bacon') }.should raise_error(ArgumentError)
8
+ lambda { ServiceProxy::Base.new('bacon') }.should raise_error(ArgumentError)
9
9
  end
10
10
 
11
11
  it "should raise on invalid WSDL" do
12
- lambda { ServiceProxy.new('http://www.jeremydurham.com') }.should raise_error(RuntimeError)
12
+ lambda { ServiceProxy::Base.new('http://www.yahoo.com') }.should raise_error(RuntimeError)
13
13
  end
14
14
 
15
15
  describe "connecting to an Instant Message Service" do
@@ -62,4 +62,24 @@ describe ServiceProxy do
62
62
  result.should =~ /^[{SSHA512}]/
63
63
  end
64
64
  end
65
+
66
+ describe "making a service call without a parse method" do
67
+ before do
68
+ @proxy = InvalidSHAGeneratorService.new('https://sec.neurofuzz-software.com/paos/genSSHA-SOAP.php?wsdl')
69
+ end
70
+
71
+ it "should raise a no method error" do
72
+ lambda { result = @proxy.genSSHA(:text => 'hello world', :hash_type => 'sha512') }.should raise_error(NoMethodError)
73
+ end
74
+ end
75
+
76
+ describe "using the #service_port hook" do
77
+ before do
78
+ @proxy = ISBNService.new('http://webservices.daehosting.com/services/isbnservice.wso?WSDL')
79
+ end
80
+
81
+ it "should have the dummy query argument" do
82
+ @proxy.send(:service_uri).path.should match(/\?dummy=1/)
83
+ end
84
+ end
65
85
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jeremydurham-serviceproxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Durham
@@ -32,8 +32,8 @@ dependencies:
32
32
  version:
33
33
  description: Lightweight SOAP library for Ruby
34
34
  email: jeremydurham@gmail.com
35
- executables: []
36
-
35
+ executables:
36
+ - wsdl2proxy
37
37
  extensions: []
38
38
 
39
39
  extra_rdoc_files:
@@ -43,7 +43,7 @@ files:
43
43
  - LICENSE
44
44
  - README
45
45
  - Rakefile
46
- - lib/service_proxy.rb
46
+ - lib/service_proxy/base.rb
47
47
  - spec/service_helper.rb
48
48
  - spec/service_proxy_spec.rb
49
49
  has_rdoc: true
data/lib/service_proxy.rb DELETED
@@ -1,111 +0,0 @@
1
- require 'rubygems'
2
- require 'nokogiri'
3
- require 'net/http'
4
- require 'net/https'
5
- require 'builder'
6
- require 'uri'
7
-
8
- class ServiceProxy
9
- VERSION = '0.0.1'
10
-
11
- attr_accessor :endpoint, :service_methods, :soap_actions, :service_uri, :http, :uri, :debug, :wsdl, :target_namespace
12
-
13
- def initialize(endpoint)
14
- self.endpoint = endpoint
15
- self.setup
16
- end
17
-
18
- def call_service(options)
19
- method = options[:method]
20
- headers = { 'content-type' => 'text/xml; charset=utf-8', 'SOAPAction' => self.soap_actions[method] }
21
- body = build_request(method, options)
22
- response = self.http.request_post(self.uri.path, body, headers)
23
- parse_response(method, response)
24
- end
25
-
26
- protected
27
-
28
- def setup
29
- self.soap_actions = {}
30
- self.service_methods = []
31
- setup_http
32
- get_wsdl
33
- parse_wsdl
34
- setup_namespace
35
- end
36
-
37
- private
38
-
39
- def setup_http
40
- self.uri = URI.parse(self.endpoint)
41
- raise ArgumentError, "Endpoint URI must be valid" unless self.uri.scheme
42
- self.http = Net::HTTP.new(self.uri.host, self.uri.port)
43
- setup_https if self.uri.scheme == 'https'
44
- self.http.set_debug_output(STDOUT) if self.debug
45
- end
46
-
47
- def setup_https
48
- self.http.use_ssl = true
49
- self.http.verify_mode = OpenSSL::SSL::VERIFY_NONE
50
- end
51
-
52
- def get_wsdl
53
- response = self.http.get("#{self.uri.path}?#{self.uri.query}")
54
- self.wsdl = Nokogiri.XML(response.body)
55
- end
56
-
57
- def parse_wsdl
58
- method_list = []
59
- self.wsdl.xpath('//*[name()="soap:operation"]').each do |operation|
60
- operation_name = operation.parent.get_attribute('name')
61
- method_list << operation_name
62
- self.soap_actions[operation_name] = operation.get_attribute('soapAction')
63
- end
64
- raise RuntimeError, "Could not parse WSDL" if method_list.empty?
65
- self.service_methods = method_list.sort
66
- end
67
-
68
- def setup_namespace
69
- self.target_namespace = self.wsdl.namespaces['xmlns:tns']
70
- end
71
-
72
- def build_request(method, options)
73
- builder = underscore("build_#{method}")
74
- self.respond_to?(builder) ? self.send(builder, options).target! : soap_envelope(options).target!
75
- end
76
-
77
- def parse_response(method, response)
78
- parser = underscore("parse_#{method}")
79
- self.respond_to?(parser) ? self.send(parser, response) :
80
- raise(NoMethodError, "You must define the parse method: #{parser}")
81
- end
82
-
83
- def soap_envelope(options, &block)
84
- xsd = 'http://www.w3.org/2001/XMLSchema'
85
- env = 'http://schemas.xmlsoap.org/soap/envelope/'
86
- xsi = 'http://www.w3.org/2001/XMLSchema-instance'
87
- xml = Builder::XmlMarkup.new
88
- xml.env(:Envelope, 'xmlns:xsd' => xsd, 'xmlns:env' => env, 'xmlns:xsi' => xsi) do
89
- xml.env(:Body) do
90
- xml.__send__(options[:method].to_sym, "xmlns" => self.target_namespace) do
91
- yield xml if block_given?
92
- end
93
- end
94
- end
95
- xml
96
- end
97
-
98
- def method_missing(method, *args)
99
- options = args.pop || {}
100
- super unless self.service_methods.include?(method.to_s)
101
- call_service(options.update(:method => method.to_s))
102
- end
103
-
104
- def underscore(camel_cased_word)
105
- camel_cased_word.to_s.gsub(/::/, '/').
106
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
107
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
108
- tr("-", "_").
109
- downcase
110
- end
111
- end