xmlrpc-streaming 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.4"
12
+ gem "libxml4r"
13
+ gem "nokogiri"
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ git (1.2.5)
6
+ jeweler (1.6.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ libxml-ruby (2.2.2)
11
+ libxml-ruby (2.2.2-x86-mingw32)
12
+ libxml4r (0.2.6)
13
+ libxml-ruby (>= 1.1.3)
14
+ nokogiri (1.5.0)
15
+ nokogiri (1.5.0-x86-mingw32)
16
+ rake (0.9.2.2)
17
+ rspec (2.3.0)
18
+ rspec-core (~> 2.3.0)
19
+ rspec-expectations (~> 2.3.0)
20
+ rspec-mocks (~> 2.3.0)
21
+ rspec-core (2.3.1)
22
+ rspec-expectations (2.3.0)
23
+ diff-lcs (~> 1.1.2)
24
+ rspec-mocks (2.3.0)
25
+
26
+ PLATFORMS
27
+ ruby
28
+ x86-mingw32
29
+
30
+ DEPENDENCIES
31
+ bundler (~> 1.0.0)
32
+ jeweler (~> 1.6.4)
33
+ libxml4r
34
+ nokogiri
35
+ rspec (~> 2.3.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Sal Scotto
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,35 @@
1
+ = xmlrpc-streaming
2
+
3
+ This package will enhance the built-in XMLRPC client to ruby
4
+ It changes the following:
5
+ * Base64 now accepts an IO object to its constructor
6
+ * The XMLRPC request will now be streamed to the server. This
7
+ will be a little faster and generate much less garbage. It should
8
+ also fix issues where large Base64 objects sent to the server can cause out of memory errors
9
+ * Base64 now has a to_io methods that will give access to the underlying data as a IO object
10
+ note: this is the raw data.
11
+ * set_writer will be ignored if this module has been loaded.
12
+ * the parser will now try the following, nokogiri, libxml and finally fallback to REML
13
+ * This module can be used completely transparently, just require the lib and nothing else needs done
14
+
15
+ == Usage
16
+
17
+ require 'xmlrpc-streaming'
18
+
19
+ .. Normal XMLRPC Code here
20
+
21
+ == Contributing to xmlrpc-streaming
22
+
23
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
24
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
25
+ * Fork the project
26
+ * Start a feature/bugfix branch
27
+ * Commit and push until you are happy with your contribution
28
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
29
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
30
+
31
+ == Copyright
32
+
33
+ Copyright (c) 2011 Sal Scotto. See LICENSE.txt for
34
+ further details.
35
+
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "xmlrpc-streaming"
18
+ gem.homepage = "http://github.com/washu/xmlrpc-streaming"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Xmlrpc client that uses streaming for binary data}
21
+ gem.description = %Q{The built in xmlrpc client doesnt handle large binary data well, this client addresses the problem}
22
+ gem.email = "sal.scotto@gmail.com"
23
+ gem.authors = ["Sal Scotto"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ task :default => :spec
35
+
36
+ require 'rake/rdoctask'
37
+ Rake::RDocTask.new do |rdoc|
38
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
39
+
40
+ rdoc.rdoc_dir = 'rdoc'
41
+ rdoc.title = "xmlrpc-streaming #{version}"
42
+ rdoc.rdoc_files.include('README*')
43
+ rdoc.rdoc_files.include('lib/**/*.rb')
44
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,261 @@
1
+ require 'xmlrpc/parser'
2
+
3
+ module XMLRPC
4
+ module StreamParserMixin2
5
+ attr_reader :params
6
+ attr_reader :method_name
7
+ attr_reader :fault
8
+ attr_accessor :use_streams
9
+ def initialize(*a)
10
+ super(*a)
11
+ @params = []
12
+ @values = []
13
+ @val_stack = []
14
+
15
+ @names = []
16
+ @name = []
17
+
18
+ @structs = []
19
+ @struct = {}
20
+
21
+ @method_name = nil
22
+ @fault = nil
23
+
24
+ @data = nil
25
+ @working_tag = nil
26
+ end
27
+
28
+ def startElement(name, attrs=[])
29
+ @data = nil
30
+ case name
31
+ when "value"
32
+ @value = nil
33
+ when "nil"
34
+ raise "wrong/unknown XML-RPC type 'nil'" unless Config::ENABLE_NIL_PARSER
35
+ @value = :nil
36
+ when "array"
37
+ @val_stack << @values
38
+ @values = []
39
+ when "struct"
40
+ @names << @name
41
+ @name = []
42
+ @structs << @struct
43
+ @struct = {}
44
+ end
45
+ @working_tag = name
46
+ end
47
+
48
+ def endElement(name)
49
+ @data ||= ""
50
+ if name.eql?("base64") and @use_streams
51
+ # Decode the file data into a new temp file and set the response as a stream
52
+ # the caller will get an IO Object as a result. Only do this if we flagged ourselves
53
+ # as 'recevied an io stream'
54
+ elsif name.eql?("base64") and not @use_streams
55
+ @data = Convert.base64(@data)
56
+ end
57
+ case name
58
+ when "string"
59
+ @value = @data
60
+ when "i4", "int"
61
+ @value = Convert.int(@data)
62
+ when "boolean"
63
+ @value = Convert.boolean(@data)
64
+ when "double"
65
+ @value = Convert.double(@data)
66
+ when "dateTime.iso8601"
67
+ @value = Convert.dateTime(@data)
68
+ when "base64"
69
+ @value = @data
70
+ when "value"
71
+ @value = @data if @value.nil?
72
+ @values << (@value == :nil ? nil : @value)
73
+ when "array"
74
+ @value = @values
75
+ @values = @val_stack.pop
76
+ when "struct"
77
+ @value = Convert.struct(@struct)
78
+
79
+ @name = @names.pop
80
+ @struct = @structs.pop
81
+ when "name"
82
+ @name[0] = @data
83
+ when "member"
84
+ @struct[@name[0]] = @values.pop
85
+
86
+ when "param"
87
+ @params << @values[0]
88
+ @values = []
89
+
90
+ when "fault"
91
+ @fault = Convert.fault(@values[0])
92
+
93
+ when "methodName"
94
+ @method_name = @data
95
+ end
96
+
97
+ @data = nil
98
+ end
99
+
100
+ def character(data)
101
+ if @data
102
+ @data << data
103
+ else
104
+ if @working_tag.eql?("base64")
105
+ @data = Tempfile.new('xmlrpc-stream-base64-data')
106
+ @data.binmode
107
+ else
108
+ @data = data
109
+ end
110
+ end
111
+ end
112
+ end # module StreamParserMixin
113
+
114
+ module XMLParser
115
+ class AbstractStreamParser2
116
+ def use_streams=(arg)
117
+ @use_streams = arg
118
+ end
119
+ def parseMethodResponse(str)
120
+ parser = @parser_class.new
121
+ parser.user_streams = @use_streams
122
+ parser.parse(str)
123
+ raise "No valid method response!" if parser.method_name != nil
124
+ if parser.fault != nil
125
+ # is a fault structure
126
+ [false, parser.fault]
127
+ else
128
+ # is a normal return value
129
+ raise "Missing return value!" if parser.params.size == 0
130
+ raise "Too many return values. Only one allowed!" if parser.params.size > 1
131
+ [true, parser.params[0]]
132
+ end
133
+ end
134
+
135
+ def parseMethodCall(str)
136
+ parser = @parser_class.new
137
+ parser.user_streams = @use_streams
138
+ parser.parse(str)
139
+ raise "No valid method call - missing method name!" if parser.method_name.nil?
140
+ [parser.method_name, parser.params]
141
+ end
142
+ end
143
+ end
144
+
145
+ module XMLParser
146
+ class NokogiriStreamParser < AbstractStreamParser2
147
+ def initialize
148
+ require 'nokogiri'
149
+ @parser_class = Class.new(Nokogiri::XML::SAX::Document) do
150
+ include StreamParserMixin2
151
+ alias :cdata_block :character
152
+ alias :characters :character
153
+ alias :end_element :endElement
154
+ def start_element(name,attrs)
155
+ startElement(name)
156
+ end
157
+ def start_element_namespace(name, attrs = nil, prefix = nil, uri = nil, ns = nil)
158
+ startElement(name)
159
+ end
160
+ def end_element_namespace(name, prefix = nil, uri = nil)
161
+ endElement(name)
162
+ end
163
+ def method_missing(*a)
164
+ end
165
+ def parse(str)
166
+ parser = Nokogiri::XML::SAX::Parser.new(self)
167
+ parser.parse(str)
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ module XMLParser
175
+ class LibXmlStreamParser < AbstractStreamParser2
176
+ def initialize()
177
+ require "libxml"
178
+ @parser_class = StreamCallback
179
+ end
180
+ class StreamCallback
181
+ include StreamParserMixin2
182
+ Entities = {
183
+ "lt" => "<",
184
+ "gt" => ">",
185
+ "amp" => "&",
186
+ "quot" => '"',
187
+ "apos" => "'"
188
+ }
189
+
190
+ def on_cdata_block(cdata)
191
+ character(cdata)
192
+ end
193
+
194
+ def on_characters(chars)
195
+ character(chars)
196
+ end
197
+
198
+ def on_reference (name)
199
+ str = Entities[name]
200
+ if str
201
+ character(str)
202
+ else
203
+ raise "Unknown Entity"
204
+ end
205
+ end
206
+
207
+ def on_start_element_ns (name, attributes, prefix, uri, namespaces)
208
+ startElement(name)
209
+ end
210
+
211
+ def on_end_element_ns(name, prefix, uri)
212
+ endElement(name)
213
+ end
214
+
215
+ def method_missing(*a)
216
+ end
217
+
218
+ def parse(str)
219
+ parser = LibXML::XML::SaxParser.io(str)
220
+ parser.extend(LibXML::XML::SaxParser::Callbacks)
221
+ parser.callbacks = self
222
+ parser.parse
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ module XMLParser
229
+ class REXMLStreamParser2 < AbstractStreamParser2
230
+ def initialize()
231
+ require "rexml/document"
232
+ @parser_class = StreamListener
233
+ end
234
+ class StreamListener
235
+ include StreamParserMixin2
236
+
237
+ alias :tag_start :startElement
238
+ alias :tag_end :endElement
239
+ alias :text :character
240
+ alias :cdata :character
241
+
242
+ def method_missing(*a)
243
+ # ignore
244
+ end
245
+
246
+ def parse(str)
247
+ parser = REXML::Document.parse_stream(str, self)
248
+ end
249
+ end
250
+ end
251
+ end
252
+ module XMLParser
253
+ def self.parser_instance(klass)
254
+ begin
255
+ klass.new
256
+ rescue LoadError => e
257
+ puts e
258
+ end
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,168 @@
1
+ #
2
+ # Stream Writer, will write out the XMLRPC data to an IO object
3
+ #
4
+
5
+ module XMLRPC
6
+ class StreamWriter
7
+
8
+ WRITE_BUFFER_SIZE = 33972
9
+ # Create a write with a given IO
10
+ def initialize(io)
11
+ @io = io
12
+ end
13
+ def has_streams?
14
+ @had_a_stream ||= false
15
+ @had_a_stream
16
+ end
17
+ def methodCall(name, *params)
18
+ @io << '<?xml version="1.0" ?><methodCall><methodName>'
19
+ @io << name
20
+ @io << '</methodName><params>'
21
+ params.each do |param|
22
+ @io << "<param>"
23
+ conv2value(param)
24
+ @io << "</param>"
25
+ end
26
+ @io << '</params></methodCall>'
27
+ end
28
+
29
+
30
+ private
31
+
32
+ # escape some text
33
+ def text(txt)
34
+ cleaned = txt.dup
35
+ cleaned.gsub!(/&/, '&amp;')
36
+ cleaned.gsub!(/</, '&lt;')
37
+ cleaned.gsub!(/>/, '&gt;')
38
+ cleaned
39
+ end
40
+
41
+ # write a tag with value tags around it
42
+ def write_tag(tag,value)
43
+ @io << "<value><#{tag}>#{text(value)}</#{tag}></value>"
44
+ end
45
+
46
+ # write teh tag directly without the value tags
47
+ def write_elem(tag, value)
48
+ @io << "<#{tag}>#{text(value)}</#{tag}>"
49
+ end
50
+
51
+ def write_with_children(tag,sub = nil)
52
+ @io << "<value><#{tag}>"
53
+ @io<< "<#{sub}>" if sub
54
+ yield if block_given?
55
+ @io<< "</#{sub}>" if sub
56
+ @io << "</#{tag}></value>"
57
+ end
58
+
59
+ # write base64 data to the output stream
60
+ def write_base64(data_stream)
61
+ write_with_children "base64" do
62
+ while (buf = data_stream.read(WRITE_BUFFER_SIZE)) != nil do
63
+ @io << [buf].pack('m').chop
64
+ end
65
+ end
66
+ end
67
+
68
+ def conv2value(param)
69
+ case param
70
+ when Fixnum, Bignum
71
+ # XML-RPC's int is 32bit int, and Fixnum also may be beyond 32bit
72
+ if Config::ENABLE_BIGINT
73
+ write_tag "i4",param.to_s
74
+ else
75
+ if param >= -(2**31) and param <= (2**31-1)
76
+ write_tag "i4", param.to_s
77
+ else
78
+ raise "Bignum is too big! Must be signed 32-bit integer!"
79
+ end
80
+ end
81
+ when TrueClass, FalseClass
82
+ write_tag "boolean", param ? "1" : "0"
83
+
84
+ when Symbol
85
+ write_tag "string", param.to_s
86
+
87
+ when String
88
+ write_tag "string", param
89
+
90
+ when NilClass
91
+ @io << "<nil/>" if Config::ENABLE_NIL_CREATE
92
+ raise "Wrong type NilClass. Not allowed!" unless Config::ENABLE_NIL_CREATE
93
+
94
+ when Float
95
+ write_tag "double", param.to_s
96
+
97
+ when Struct
98
+ write_with_children "struct" do
99
+ param.members.each do |key|
100
+ value = param[key]
101
+ @io << "<member>"
102
+ write_elem("name",key.to_s)
103
+ con2value(value)
104
+ @io << "</member>"
105
+ end
106
+ end
107
+
108
+ when Hash
109
+ write_with_children "struct" do
110
+ param.each do |key, value|
111
+ @io << "<member>"
112
+ write_elem("name", key.to_s)
113
+ conv2value(value)
114
+ @io << "</member>"
115
+ end
116
+ end
117
+
118
+ when Array
119
+ write_with_children "array","data" do
120
+ param.each do |elem|
121
+ conv2value(elem)
122
+ end
123
+ end
124
+
125
+ when Time, Date, ::DateTime
126
+ write_tag "dateTime.iso8601", param.strftime("%Y%m%dT%H:%M:%S")
127
+
128
+ when XMLRPC::DateTime
129
+ write_tag "dateTime.iso8601",format("%.4d%02d%02dT%02d:%02d:%02d", *param.to_a)
130
+
131
+ when XMLRPC::Base64
132
+ write_base64(param.to_io)
133
+
134
+ when IO, respond_to?(:read)
135
+ @had_a_stream = true
136
+ write_base64(param)
137
+
138
+ else
139
+ if XMLRPC::Config::ENABLE_MARSHALLING and param.class.included_modules.include? XMLRPC::Marshallable
140
+ # convert Ruby object into Hash
141
+ ret = {"___class___" => param.class.name}
142
+ param.instance_variables.each do |v|
143
+ name = v[1..-1]
144
+ val = param.instance_variable_get(v)
145
+
146
+ if val.nil?
147
+ ret[name] = val if XLMRPC::Config::ENABLE_NIL_CREATE
148
+ else
149
+ ret[name] = val
150
+ end
151
+ end
152
+ return conv2value(ret)
153
+ else
154
+ ok, pa = wrong_type(param)
155
+ if ok
156
+ return conv2value(pa)
157
+ else
158
+ raise "Wrong type!"
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ def wrong_type(value)
165
+ false
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,256 @@
1
+ =begin
2
+ = xmlrpc-streaming.rb
3
+ Copyright (C) 2011 by Sal Scotto (sal.scotto@gmail.com)
4
+ Released under the same terms of license as Ruby.
5
+
6
+ == XMLRPC::Client License
7
+ We override several key methods of the original XMLRPC::Client by Michael Neumann
8
+ Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
9
+ Released under the same terms of license as Ruby.
10
+
11
+ == Description
12
+ This class extends a few key methods of the XMLRPC::Client library
13
+ it treats base64 data differently. It allows you pass in any object
14
+ that supports a #read(bytes) method to be used instead of having to provide a
15
+ base64 encoded string for binary data. The problem it is trying to solve
16
+ is the case of sending a large binary blob over xmlrpc, which consumes large
17
+ amounts of ram to not only encoded and represent, but also to decode.
18
+ To use transparently. Please NOTE, we this module overwrite ignores the parser
19
+ settings in the config. We first try Nokogiri, if that isnt available we then
20
+ try LibXML, if that fails we fallback to REML. In any case we only
21
+ use StreamingParsers
22
+
23
+ require 'xmlrpc/client'
24
+ require 'xmlrpc-streaming'
25
+
26
+ == Instance Methods
27
+ --- XMLRPC::Client#set_debug( output_stream)
28
+ Invokes the call with debug output sent to the provided stream
29
+
30
+ == Differences
31
+ Any place you would normally get or send an XMLRPC::Base64 object
32
+ you can instead subsitute an object that supports #read(bytes) in it places
33
+ =end
34
+
35
+
36
+ require 'stringio'
37
+ require 'rbconfig'
38
+ require 'xmlrpc/base64'
39
+ require 'xmlrpc/client'
40
+ require 'tempfile'
41
+ require 'stream_writer'
42
+ require 'stream_parser_mixin'
43
+
44
+ # Add a to_io method to existing base64 class
45
+ module XMLRPC
46
+ class Base64
47
+
48
+ def initialize(str, state = :dec)
49
+ @state = state
50
+ @str = nil
51
+ @stream = false
52
+ case state
53
+ when :enc
54
+ if str.respond_to?(:read)
55
+ @str = str
56
+ @stream = true
57
+ else
58
+ @str = XMLRPC::Base64.decode(str)
59
+ end
60
+ when :dec
61
+ @str = str
62
+ if str.respond_to?(:read)
63
+ @stream = true
64
+ end
65
+ else
66
+ raise ArgumentError, "wrong argument; either :enc or :dec"
67
+ end
68
+ end
69
+
70
+ # Create an IO stream out of the data if it isnt a stream already
71
+ # side effect: will call rewind on the stream if it is rewindable
72
+ def to_io
73
+ if @stream
74
+ if @str.respond_to?(:rewind)
75
+ @str.rewind
76
+ end
77
+ @str
78
+ else
79
+ StringIO.new(@str)
80
+ end
81
+ end
82
+
83
+ # Get the decoded string
84
+ # if theunderlying string is a stream it will rewind before the call
85
+ def decoded
86
+ if @stream
87
+ if @str.respond_to?(:rewind)
88
+ @str.rewind
89
+ end
90
+ @str.read
91
+ else
92
+ @str
93
+ end
94
+ end
95
+
96
+ # Get the encoded string
97
+ # if the underlying string is a stream will call rewind first
98
+ def encoded
99
+ if @stream
100
+ if @str.respond_to?(:rewind)
101
+ @str.rewind
102
+ end
103
+ Base64.encode(@str.read)
104
+ else
105
+ Base64.encode(@str)
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ module XMLRPC
112
+ class Client
113
+
114
+ =begin
115
+ set_debug_stream stream
116
+ will enable HTTP debuggin to the passed in stream
117
+ =end
118
+ def set_debug_stream(stream)
119
+ @debug_stream = stream
120
+ end
121
+
122
+ def call2_async(method, *args)
123
+ data = do_rpc(true,method,*args)
124
+ parser().parseMethodResponse(data)
125
+ end
126
+
127
+ def call2(method, *args)
128
+ data = do_rpc(false,method,*args)
129
+ parser().parseMethodResponse(data)
130
+ end
131
+
132
+ private
133
+
134
+ # Stream our Request over to the server and save the results in a tempfile
135
+ def post_request(client,path,header,request_file,sink)
136
+ # Post via stream
137
+ req = Net::HTTP::Post.new(path,header)
138
+ req.body_stream = request_file
139
+ sink.binmode
140
+ resp = client.request(req) do |res|
141
+ res.read_body do |b|
142
+ sink.write(b)
143
+ end
144
+ sink.rewind
145
+ sink.size
146
+ sink
147
+ end
148
+ resp
149
+ end
150
+
151
+ def do_rpc(async,method,*args)
152
+ header = {
153
+ "User-Agent" => USER_AGENT,
154
+ "Content-Type" => "text/xml; charset=utf-8",
155
+ "Connection" => (async ? "close" : "keep-alive")
156
+ }
157
+ header["Cookie"] = @cookie if @cookie
158
+ header.update(@http_header_extra) if @http_header_extra
159
+ if @auth != nil
160
+ # add authorization header
161
+ header["Authorization"] = @auth
162
+ end
163
+ resp = nil
164
+ @http_last_response = nil
165
+
166
+ # Construct the request data
167
+ request_message = Tempfile.new('xmlrpc-stream-request')
168
+ data = Tempfile.new("xmlrpc-response-body")
169
+ # Use the streamwrite to write the temp file
170
+ writer = StreamWriter.new(request_message)
171
+ writer.methodCall(method,*args)
172
+ libxmlparser = nil
173
+ unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
174
+ libxmlparser = XMLRPC::XMLParser.parser_instance XMLRPC::XMLParser::LibXmlStreamParser
175
+ end
176
+ nokogiri = XMLRPC::XMLParser.parser_instance XMLRPC::XMLParser::NokogiriStreamParser
177
+ if nokogiri
178
+ set_parser(nokogiri)
179
+ elsif libxmlparser
180
+ set_parser(libxmlparser)
181
+ else # couldnt load one of the other parser so use REXML
182
+ set_parser(XMLParser::REXMLStreamParser2.new)
183
+ end
184
+ @parser.use_streams = writer.has_streams?
185
+ request_message.close
186
+ content_length = request_message.size
187
+ request_message.open
188
+ # get the data size of the request
189
+ header["Content-Length"] = content_length.to_s
190
+
191
+ # temp garbage will grow but GC will handle it over the course of
192
+ # the download/upload so you shouldnt get alocation errors with big files
193
+
194
+ resp = nil
195
+ if async
196
+ # use a new HTTP object for each call
197
+ Net::HTTP.version_1_2
198
+ http = Net::HTTP.new(@host, @port, @proxy_host, @proxy_port)
199
+ http.use_ssl = @use_ssl if @use_ssl
200
+ http.read_timeout = @timeout
201
+ http.open_timeout = @timeout
202
+ http.set_debug_output @debug_stream if @debug_stream
203
+ http.start {
204
+ resp = post_request(http,@path,header,request_message,data)
205
+ }
206
+ else
207
+ @http.set_debug_output @debug_stream if @debug_stream
208
+ @http.start if not @http.started?
209
+ resp = post_request(@http,@path,header,request_message,data)
210
+ end
211
+
212
+
213
+ @http_last_response = resp
214
+
215
+ if resp.code == "401"
216
+ # Authorization Required
217
+ data.unlink
218
+ raise "Authorization failed.\nHTTP-Error: #{resp.code} #{resp.message}"
219
+ elsif resp.code[0,1] != "2"
220
+ data.unlink
221
+ raise "HTTP-Error: #{resp.code} #{resp.message}"
222
+ end
223
+
224
+ ct = parse_content_type(resp["Content-Type"]).first
225
+ # Some implmentations return application/xml, for faults so lets allow
226
+ # them for poor implmentations of servers
227
+ if ct !~ /\/xml$/
228
+ data.unlink
229
+ raise "Wrong content-type (received '#{ct}' but expected 'text/xml') *Use set_debug_stream for details"
230
+ end
231
+
232
+ expected = resp["Content-Length"] || "<unknown>"
233
+ if data.nil? or data.size == 0
234
+ s = data.size
235
+ data.unlink
236
+ raise "Wrong size. Was #{s}, should be #{expected} #{data.read}"
237
+ elsif expected != "<unknown>" and expected.to_i != data.size and resp["Transfer-Encoding"].nil?
238
+ s = data.size
239
+ data.unlink
240
+ raise "Wrong size. Was #{s}, should be #{expected} #{data.read}"
241
+ end
242
+
243
+ # Copy any cookies sent
244
+ set_cookies = resp.get_fields("Set-Cookie")
245
+ if set_cookies and !set_cookies.empty?
246
+ require 'webrick/cookie'
247
+ @cookie = set_cookies.collect do |set_cookie|
248
+ cookie = WEBrick::Cookie.parse_set_cookie(set_cookie)
249
+ WEBrick::Cookie.new(cookie.name, cookie.value).to_s
250
+ end.join("; ")
251
+ end
252
+ # Return the TempFile
253
+ return data
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'xmlrpc-streaming'
5
+ # Requires supporting files with custom matchers and macros, etc,
6
+ # in ./support/ and its subdirectories.
7
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
8
+
9
+ RSpec::Matchers.define :answer_to do |method|
10
+ match do |obj|
11
+ obj.respond_to?(method)
12
+ end
13
+ end
14
+
15
+ RSpec.configure do |config|
16
+
17
+ end
@@ -0,0 +1,126 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "XmlrpcStreaming" do
4
+
5
+ it "should create an instance with has values" do
6
+ client = XMLRPC::Client.new2 "http://me@test.com/RPC2"
7
+ client.user.should == "me"
8
+ end
9
+
10
+ it "should send a body async" do
11
+ client = XMLRPC::Client.new2 "http://time.xmlrpc.com/RPC2"
12
+ proxy = client.proxy_async("currentTime")
13
+ t = proxy.getCurrentTime.to_time
14
+ t.should_not == Time.now
15
+ end
16
+ it "should send a body sync" do
17
+ client = XMLRPC::Client.new2 "http://time.xmlrpc.com/RPC2"
18
+ proxy = client.proxy("currentTime")
19
+ proxy.getCurrentTime.to_time.should_not == Time.now
20
+ end
21
+
22
+ it "should add a to_io method to base64 class" do
23
+ base64 = XMLRPC::Base64.new("dkjfkdjhfkdjhfkdhfkjdhkj")
24
+ base64.to_io.should answer_to(:read)
25
+ end
26
+
27
+ it "should add a base64 initializer that handles IO objects" do
28
+ base64 = XMLRPC::Base64.new(File.open(File.expand_path(File.dirname(__FILE__) + '/spec_helper.rb')))
29
+ base64.should_not be_nil
30
+ end
31
+
32
+ it "should set base64 to_io to return an IO object when given one" do
33
+ base64 = XMLRPC::Base64.new(File.open(File.expand_path(File.dirname(__FILE__) + '/spec_helper.rb')))
34
+ base64.to_io.should be_kind_of(IO)
35
+ end
36
+
37
+ it "should call wordpress for testing" do
38
+ client = XMLRPC::Client.new2 "http://salsxmltest.wordpress.com/xmlrpc.php"
39
+ m = client.call "wp.getUsersBlogs", "washu214", "abc123", File.open(File.expand_path(File.dirname(__FILE__) + '/spec_helper.rb'))
40
+ m.should_not be_empty
41
+ end
42
+
43
+ it "should upload a base64 object to wordpress" do
44
+ pending("Create a blog post with a large image")
45
+ #blogid 28060656
46
+ #https://salsxmltest.wordpress.com/xmlrpc.php
47
+ end
48
+
49
+ it "should encode data the same as the original encoder" do
50
+ creater = XMLRPC::Create.new
51
+ io_block = ''
52
+ streamer = XMLRPC::StreamWriter.new io_block
53
+ doc = creater.methodCall "test", [1,2,3], { :key=> 1, :d => 'v', :x => ['a','b'] }
54
+ # Strip the \n off of the document
55
+ doc.chop!
56
+ streamer.methodCall "test", [1,2,3], { :key=> 1, :d => 'v', :x => ['a','b'] }
57
+ io_block.should == doc
58
+ end
59
+
60
+ it "should encode data the same as the original with a time object" do
61
+ creater = XMLRPC::Create.new
62
+ io_block = ''
63
+ streamer = XMLRPC::StreamWriter.new io_block
64
+ doc = creater.methodCall "test", Time.now
65
+ # Strip the \n off of the document
66
+ doc.chop!
67
+ streamer.methodCall "test", Time.now
68
+ io_block.should == doc
69
+ end
70
+
71
+ it "should encode data the same as the original with a Date object" do
72
+ creater = XMLRPC::Create.new
73
+ io_block = ''
74
+ streamer = XMLRPC::StreamWriter.new io_block
75
+ doc = creater.methodCall "test", Date.today
76
+ # Strip the \n off of the document
77
+ doc.chop!
78
+ streamer.methodCall "test", Date.today
79
+ io_block.should == doc
80
+ end
81
+
82
+ it "should encode data the same as the original with a Marshalable object" do
83
+ klass = Class.new do
84
+ include XMLRPC::Marshallable
85
+ attr_accessor :name, :date
86
+ end
87
+ Object.const_set 'Testable', klass
88
+ creater = XMLRPC::Create.new
89
+ io_block = ''
90
+ streamer = XMLRPC::StreamWriter.new io_block
91
+ doc = creater.methodCall "test", Testable.new
92
+ # Strip the \n off of the document
93
+ doc.chop!
94
+ streamer.methodCall "test", Testable.new
95
+ io_block.should == doc
96
+ end
97
+
98
+ it "should encode data the same as the original with a Base64 object" do
99
+ creater = XMLRPC::Create.new
100
+ io_block = ''
101
+ b64 = XMLRPC::Base64.new 'testing junk'
102
+ streamer = XMLRPC::StreamWriter.new io_block
103
+ doc = creater.methodCall "test", b64
104
+ # Strip the \n out as the original will include a \n after a base64 object
105
+ doc.gsub!(/\n/,'')
106
+ streamer.methodCall "test", b64
107
+ io_block.should == doc
108
+ end
109
+
110
+ it "should encode data the same as the original with a Base64 object" do
111
+ creater = XMLRPC::Create.new
112
+ io_block = ''
113
+ b64 = XMLRPC::Base64.new StringIO.new 'testing junk'
114
+ streamer = XMLRPC::StreamWriter.new io_block
115
+ doc = creater.methodCall "test", b64
116
+ # Strip the \n out as the original will include a \n after a base64 object
117
+ doc.gsub!(/\n/,'')
118
+ streamer.methodCall "test", b64
119
+ io_block.should == doc
120
+ end
121
+
122
+ it "should upload a large binary object and not run out of memory" do
123
+ pending("add large file upload test")
124
+ end
125
+
126
+ end
@@ -0,0 +1,65 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{xmlrpc-streaming}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Sal Scotto"]
12
+ s.date = %q{2011-11-15}
13
+ s.description = %q{The built in xmlrpc client doesnt handle large binary data well, this client addresses the problem}
14
+ s.email = %q{sal.scotto@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "lib/stream_parser_mixin.rb",
29
+ "lib/stream_writer.rb",
30
+ "lib/xmlrpc-streaming.rb",
31
+ "spec/spec_helper.rb",
32
+ "spec/xmlrpc-streaming_spec.rb",
33
+ "xmlrpc-streaming.gemspec"
34
+ ]
35
+ s.homepage = %q{http://github.com/washu/xmlrpc-streaming}
36
+ s.licenses = ["MIT"]
37
+ s.require_paths = ["lib"]
38
+ s.rubygems_version = %q{1.7.2}
39
+ s.summary = %q{Xmlrpc client that uses streaming for binary data}
40
+
41
+ if s.respond_to? :specification_version then
42
+ s.specification_version = 3
43
+
44
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
45
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
46
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
47
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
48
+ s.add_development_dependency(%q<libxml4r>, [">= 0"])
49
+ s.add_development_dependency(%q<nokogiri>, [">= 0"])
50
+ else
51
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
52
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
53
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
54
+ s.add_dependency(%q<libxml4r>, [">= 0"])
55
+ s.add_dependency(%q<nokogiri>, [">= 0"])
56
+ end
57
+ else
58
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
59
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
60
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
61
+ s.add_dependency(%q<libxml4r>, [">= 0"])
62
+ s.add_dependency(%q<nokogiri>, [">= 0"])
63
+ end
64
+ end
65
+
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xmlrpc-streaming
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sal Scotto
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-15 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &24667536 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.3.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *24667536
25
+ - !ruby/object:Gem::Dependency
26
+ name: bundler
27
+ requirement: &24667200 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *24667200
36
+ - !ruby/object:Gem::Dependency
37
+ name: jeweler
38
+ requirement: &24666864 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.6.4
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *24666864
47
+ - !ruby/object:Gem::Dependency
48
+ name: libxml4r
49
+ requirement: &24666564 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *24666564
58
+ - !ruby/object:Gem::Dependency
59
+ name: nokogiri
60
+ requirement: &24666264 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *24666264
69
+ description: The built in xmlrpc client doesnt handle large binary data well, this
70
+ client addresses the problem
71
+ email: sal.scotto@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files:
75
+ - LICENSE.txt
76
+ - README.rdoc
77
+ files:
78
+ - .document
79
+ - .rspec
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - LICENSE.txt
83
+ - README.rdoc
84
+ - Rakefile
85
+ - VERSION
86
+ - lib/stream_parser_mixin.rb
87
+ - lib/stream_writer.rb
88
+ - lib/xmlrpc-streaming.rb
89
+ - spec/spec_helper.rb
90
+ - spec/xmlrpc-streaming_spec.rb
91
+ - xmlrpc-streaming.gemspec
92
+ homepage: http://github.com/washu/xmlrpc-streaming
93
+ licenses:
94
+ - MIT
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ segments:
106
+ - 0
107
+ hash: -927155007
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubyforge_project:
116
+ rubygems_version: 1.7.2
117
+ signing_key:
118
+ specification_version: 3
119
+ summary: Xmlrpc client that uses streaming for binary data
120
+ test_files: []