1 | =begin
|
---|
2 | = xmlrpc/server.rb
|
---|
3 | Copyright (C) 2001, 2002, 2003, 2005 by Michael Neumann ([email protected])
|
---|
4 |
|
---|
5 | Released under the same term of license as Ruby.
|
---|
6 |
|
---|
7 | = Classes
|
---|
8 | * ((<XMLRPC::BasicServer>))
|
---|
9 | * ((<XMLRPC::CGIServer>))
|
---|
10 | * ((<XMLRPC::ModRubyServer>))
|
---|
11 | * ((<XMLRPC::Server>))
|
---|
12 | * ((<XMLRPC::WEBrickServlet>))
|
---|
13 |
|
---|
14 | = XMLRPC::BasicServer
|
---|
15 | == Description
|
---|
16 | Is the base class for all XML-RPC server-types (CGI, standalone).
|
---|
17 | You can add handler and set a default handler.
|
---|
18 | Do not use this server, as this is/should be an abstract class.
|
---|
19 |
|
---|
20 | === How the method to call is found
|
---|
21 | The arity (number of accepted arguments) of a handler (method or (({Proc})) object) is
|
---|
22 | compared to the given arguments submitted by the client for a RPC ((-Remote Procedure Call-)).
|
---|
23 | A handler is only called if it accepts the number of arguments, otherwise the search
|
---|
24 | for another handler will go on. When at the end no handler was found,
|
---|
25 | the ((<default_handler|XMLRPC::BasicServer#set_default_handler>)) will be called.
|
---|
26 | With this technique it is possible to do overloading by number of parameters, but
|
---|
27 | only for (({Proc})) handler, because you cannot define two methods of the same name in
|
---|
28 | the same class.
|
---|
29 |
|
---|
30 |
|
---|
31 | == Class Methods
|
---|
32 | --- XMLRPC::BasicServer.new( class_delim="." )
|
---|
33 | Creates a new (({XMLRPC::BasicServer})) instance, which should not be
|
---|
34 | done, because (({XMLRPC::BasicServer})) is an abstract class. This
|
---|
35 | method should be called from a subclass indirectly by a (({super})) call
|
---|
36 | in the method (({initialize})). The paramter ((|class_delim|)) is used
|
---|
37 | in ((<add_handler|XMLRPC::BasicServer#add_handler>)) when an object is
|
---|
38 | added as handler, to delimit the object-prefix and the method-name.
|
---|
39 |
|
---|
40 | == Instance Methods
|
---|
41 | --- XMLRPC::BasicServer#add_handler( name, signature=nil, help=nil ) { aBlock }
|
---|
42 | Adds ((|aBlock|)) to the list of handlers, with ((|name|)) as the name of the method.
|
---|
43 | Parameters ((|signature|)) and ((|help|)) are used by the Introspection method if specified,
|
---|
44 | where ((|signature|)) is either an Array containing strings each representing a type of it's
|
---|
45 | signature (the first is the return value) or an Array of Arrays if the method has multiple
|
---|
46 | signatures. Value type-names are "int, boolean, double, string, dateTime.iso8601, base64, array, struct".
|
---|
47 |
|
---|
48 | Parameter ((|help|)) is a String with informations about how to call this method etc.
|
---|
49 |
|
---|
50 | A handler method or code-block can return the types listed at
|
---|
51 | ((<XMLRPC::Client#call|URL:client.html#index:0>)).
|
---|
52 | When a method fails, it can tell it the client by throwing an
|
---|
53 | (({XMLRPC::FaultException})) like in this example:
|
---|
54 | s.add_handler("michael.div") do |a,b|
|
---|
55 | if b == 0
|
---|
56 | raise XMLRPC::FaultException.new(1, "division by zero")
|
---|
57 | else
|
---|
58 | a / b
|
---|
59 | end
|
---|
60 | end
|
---|
61 | The client gets in the case of (({b==0})) an object back of type
|
---|
62 | (({XMLRPC::FaultException})) that has a ((|faultCode|)) and ((|faultString|))
|
---|
63 | field.
|
---|
64 |
|
---|
65 | --- XMLRPC::BasicServer#add_handler( prefix, obj )
|
---|
66 | This is the second form of ((<add_handler|XMLRPC::BasicServer#add_handler>)).
|
---|
67 | To add an object write:
|
---|
68 | server.add_handler("michael", MyHandlerClass.new)
|
---|
69 | All public methods of (({MyHandlerClass})) are accessible to
|
---|
70 | the XML-RPC clients by (('michael."name of method"')). This is
|
---|
71 | where the ((|class_delim|)) in ((<new|XMLRPC::BasicServer.new>))
|
---|
72 | has it's role, a XML-RPC method-name is defined by
|
---|
73 | ((|prefix|)) + ((|class_delim|)) + (('"name of method"')).
|
---|
74 |
|
---|
75 | --- XMLRPC::BasicServer#add_handler( interface, obj )
|
---|
76 | This is the third form of ((<add_handler|XMLRPC::BasicServer#add_handler>)).
|
---|
77 |
|
---|
78 | Use (({XMLRPC::interface})) to generate an ServiceInterface object, which
|
---|
79 | represents an interface (with signature and help text) for a handler class.
|
---|
80 |
|
---|
81 | Parameter ((|interface|)) must be of type (({XMLRPC::ServiceInterface})).
|
---|
82 | Adds all methods of ((|obj|)) which are defined in ((|interface|)) to the
|
---|
83 | server.
|
---|
84 |
|
---|
85 | This is the recommended way of adding services to a server!
|
---|
86 |
|
---|
87 |
|
---|
88 | --- XMLRPC::BasicServer#get_default_handler
|
---|
89 | Returns the default-handler, which is called when no handler for
|
---|
90 | a method-name is found.
|
---|
91 | It is a (({Proc})) object or (({nil})).
|
---|
92 |
|
---|
93 | --- XMLRPC::BasicServer#set_default_handler ( &handler )
|
---|
94 | Sets ((|handler|)) as the default-handler, which is called when
|
---|
95 | no handler for a method-name is found. ((|handler|)) is a code-block.
|
---|
96 | The default-handler is called with the (XML-RPC) method-name as first argument, and
|
---|
97 | the other arguments are the parameters given by the client-call.
|
---|
98 |
|
---|
99 | If no block is specified the default of (({XMLRPC::BasicServer})) is used, which raises a
|
---|
100 | XMLRPC::FaultException saying "method missing".
|
---|
101 |
|
---|
102 |
|
---|
103 | --- XMLRPC::BasicServer#set_writer( writer )
|
---|
104 | Sets the XML writer to use for generating XML output.
|
---|
105 | Should be an instance of a class from module (({XMLRPC::XMLWriter})).
|
---|
106 | If this method is not called, then (({XMLRPC::Config::DEFAULT_WRITER})) is used.
|
---|
107 |
|
---|
108 | --- XMLRPC::BasicServer#set_parser( parser )
|
---|
109 | Sets the XML parser to use for parsing XML documents.
|
---|
110 | Should be an instance of a class from module (({XMLRPC::XMLParser})).
|
---|
111 | If this method is not called, then (({XMLRPC::Config::DEFAULT_PARSER})) is used.
|
---|
112 |
|
---|
113 | --- XMLRPC::BasicServer#add_introspection
|
---|
114 | Adds the introspection handlers "system.listMethods", "system.methodSignature" and "system.methodHelp",
|
---|
115 | where only the first one works.
|
---|
116 |
|
---|
117 | --- XMLRPC::BasicServer#add_multicall
|
---|
118 | Adds the multi-call handler "system.multicall".
|
---|
119 |
|
---|
120 | --- XMLRPC::BasicServer#get_service_hook
|
---|
121 | Returns the service-hook, which is called on each service request (RPC) unless it's (({nil})).
|
---|
122 |
|
---|
123 | --- XMLRPC::BasicServer#set_service_hook ( &handler )
|
---|
124 | A service-hook is called for each service request (RPC).
|
---|
125 | You can use a service-hook for example to wrap existing methods and catch exceptions of them or
|
---|
126 | convert values to values recognized by XMLRPC. You can disable it by passing (({nil})) as parameter
|
---|
127 | ((|handler|)) .
|
---|
128 |
|
---|
129 | The service-hook is called with a (({Proc})) object and with the parameters for this (({Proc})).
|
---|
130 | An example:
|
---|
131 |
|
---|
132 | server.set_service_hook {|obj, *args|
|
---|
133 | begin
|
---|
134 | ret = obj.call(*args) # call the original service-method
|
---|
135 | # could convert the return value
|
---|
136 | resuce
|
---|
137 | # rescue exceptions
|
---|
138 | end
|
---|
139 | }
|
---|
140 |
|
---|
141 | =end
|
---|
142 |
|
---|
143 |
|
---|
144 |
|
---|
145 | require "xmlrpc/parser"
|
---|
146 | require "xmlrpc/create"
|
---|
147 | require "xmlrpc/config"
|
---|
148 | require "xmlrpc/utils" # ParserWriterChooseMixin
|
---|
149 |
|
---|
150 |
|
---|
151 |
|
---|
152 | module XMLRPC
|
---|
153 |
|
---|
154 |
|
---|
155 | class BasicServer
|
---|
156 |
|
---|
157 | include ParserWriterChooseMixin
|
---|
158 | include ParseContentType
|
---|
159 |
|
---|
160 | ERR_METHOD_MISSING = 1
|
---|
161 | ERR_UNCAUGHT_EXCEPTION = 2
|
---|
162 | ERR_MC_WRONG_PARAM = 3
|
---|
163 | ERR_MC_MISSING_PARAMS = 4
|
---|
164 | ERR_MC_MISSING_METHNAME = 5
|
---|
165 | ERR_MC_RECURSIVE_CALL = 6
|
---|
166 | ERR_MC_WRONG_PARAM_PARAMS = 7
|
---|
167 | ERR_MC_EXPECTED_STRUCT = 8
|
---|
168 |
|
---|
169 |
|
---|
170 | def initialize(class_delim=".")
|
---|
171 | @handler = []
|
---|
172 | @default_handler = nil
|
---|
173 | @service_hook = nil
|
---|
174 |
|
---|
175 | @class_delim = class_delim
|
---|
176 | @create = nil
|
---|
177 | @parser = nil
|
---|
178 |
|
---|
179 | add_multicall if Config::ENABLE_MULTICALL
|
---|
180 | add_introspection if Config::ENABLE_INTROSPECTION
|
---|
181 | end
|
---|
182 |
|
---|
183 | def add_handler(prefix, obj_or_signature=nil, help=nil, &block)
|
---|
184 | if block_given?
|
---|
185 | # proc-handler
|
---|
186 | @handler << [prefix, block, obj_or_signature, help]
|
---|
187 | else
|
---|
188 | if prefix.kind_of? String
|
---|
189 | # class-handler
|
---|
190 | raise ArgumentError, "Expected non-nil value" if obj_or_signature.nil?
|
---|
191 | @handler << [prefix + @class_delim, obj_or_signature]
|
---|
192 | elsif prefix.kind_of? XMLRPC::Service::BasicInterface
|
---|
193 | # class-handler with interface
|
---|
194 | # add all methods
|
---|
195 | @handler += prefix.get_methods(obj_or_signature, @class_delim)
|
---|
196 | else
|
---|
197 | raise ArgumentError, "Wrong type for parameter 'prefix'"
|
---|
198 | end
|
---|
199 | end
|
---|
200 | self
|
---|
201 | end
|
---|
202 |
|
---|
203 | def get_service_hook
|
---|
204 | @service_hook
|
---|
205 | end
|
---|
206 |
|
---|
207 | def set_service_hook(&handler)
|
---|
208 | @service_hook = handler
|
---|
209 | self
|
---|
210 | end
|
---|
211 |
|
---|
212 | def get_default_handler
|
---|
213 | @default_handler
|
---|
214 | end
|
---|
215 |
|
---|
216 | def set_default_handler (&handler)
|
---|
217 | @default_handler = handler
|
---|
218 | self
|
---|
219 | end
|
---|
220 |
|
---|
221 | def add_multicall
|
---|
222 | add_handler("system.multicall", %w(array array), "Multicall Extension") do |arrStructs|
|
---|
223 | unless arrStructs.is_a? Array
|
---|
224 | raise XMLRPC::FaultException.new(ERR_MC_WRONG_PARAM, "system.multicall expects an array")
|
---|
225 | end
|
---|
226 |
|
---|
227 | arrStructs.collect {|call|
|
---|
228 | if call.is_a? Hash
|
---|
229 | methodName = call["methodName"]
|
---|
230 | params = call["params"]
|
---|
231 |
|
---|
232 | if params.nil?
|
---|
233 | multicall_fault(ERR_MC_MISSING_PARAMS, "Missing params")
|
---|
234 | elsif methodName.nil?
|
---|
235 | multicall_fault(ERR_MC_MISSING_METHNAME, "Missing methodName")
|
---|
236 | else
|
---|
237 | if methodName == "system.multicall"
|
---|
238 | multicall_fault(ERR_MC_RECURSIVE_CALL, "Recursive system.multicall forbidden")
|
---|
239 | else
|
---|
240 | unless params.is_a? Array
|
---|
241 | multicall_fault(ERR_MC_WRONG_PARAM_PARAMS, "Parameter params have to be an Array")
|
---|
242 | else
|
---|
243 | ok, val = call_method(methodName, *params)
|
---|
244 | if ok
|
---|
245 | # correct return value
|
---|
246 | [val]
|
---|
247 | else
|
---|
248 | # exception
|
---|
249 | multicall_fault(val.faultCode, val.faultString)
|
---|
250 | end
|
---|
251 | end
|
---|
252 | end
|
---|
253 | end
|
---|
254 |
|
---|
255 | else
|
---|
256 | multicall_fault(ERR_MC_EXPECTED_STRUCT, "system.multicall expected struct")
|
---|
257 | end
|
---|
258 | }
|
---|
259 | end # end add_handler
|
---|
260 | self
|
---|
261 | end
|
---|
262 |
|
---|
263 | def add_introspection
|
---|
264 | add_handler("system.listMethods",%w(array), "List methods available on this XML-RPC server") do
|
---|
265 | methods = []
|
---|
266 | @handler.each do |name, obj|
|
---|
267 | if obj.kind_of? Proc
|
---|
268 | methods << name
|
---|
269 | else
|
---|
270 | obj.methods.each {|meth| methods << name + meth}
|
---|
271 | end
|
---|
272 | end
|
---|
273 | methods
|
---|
274 | end
|
---|
275 |
|
---|
276 | add_handler("system.methodSignature", %w(array string), "Returns method signature") do |meth|
|
---|
277 | sigs = []
|
---|
278 | @handler.each do |name, obj, sig|
|
---|
279 | if obj.kind_of? Proc and sig != nil and name == meth
|
---|
280 | if sig[0].kind_of? Array
|
---|
281 | # sig contains multiple signatures, e.g. [["array"], ["array", "string"]]
|
---|
282 | sig.each {|s| sigs << s}
|
---|
283 | else
|
---|
284 | # sig is a single signature, e.g. ["array"]
|
---|
285 | sigs << sig
|
---|
286 | end
|
---|
287 | end
|
---|
288 | end
|
---|
289 | sigs.uniq! || sigs # remove eventually duplicated signatures
|
---|
290 | end
|
---|
291 |
|
---|
292 | add_handler("system.methodHelp", %w(string string), "Returns help on using this method") do |meth|
|
---|
293 | help = nil
|
---|
294 | @handler.each do |name, obj, sig, hlp|
|
---|
295 | if obj.kind_of? Proc and name == meth
|
---|
296 | help = hlp
|
---|
297 | break
|
---|
298 | end
|
---|
299 | end
|
---|
300 | help || ""
|
---|
301 | end
|
---|
302 |
|
---|
303 | self
|
---|
304 | end
|
---|
305 |
|
---|
306 |
|
---|
307 |
|
---|
308 | def process(data)
|
---|
309 | method, params = parser().parseMethodCall(data)
|
---|
310 | handle(method, *params)
|
---|
311 | end
|
---|
312 |
|
---|
313 | private # --------------------------------------------------------------
|
---|
314 |
|
---|
315 | def multicall_fault(nr, str)
|
---|
316 | {"faultCode" => nr, "faultString" => str}
|
---|
317 | end
|
---|
318 |
|
---|
319 | #
|
---|
320 | # method dispatch
|
---|
321 | #
|
---|
322 | def dispatch(methodname, *args)
|
---|
323 | for name, obj in @handler
|
---|
324 | if obj.kind_of? Proc
|
---|
325 | next unless methodname == name
|
---|
326 | else
|
---|
327 | next unless methodname =~ /^#{name}(.+)$/
|
---|
328 | next unless obj.respond_to? $1
|
---|
329 | obj = obj.method($1)
|
---|
330 | end
|
---|
331 |
|
---|
332 | if check_arity(obj, args.size)
|
---|
333 | if @service_hook.nil?
|
---|
334 | return obj.call(*args)
|
---|
335 | else
|
---|
336 | return @service_hook.call(obj, *args)
|
---|
337 | end
|
---|
338 | end
|
---|
339 | end
|
---|
340 |
|
---|
341 | if @default_handler.nil?
|
---|
342 | raise XMLRPC::FaultException.new(ERR_METHOD_MISSING, "Method #{methodname} missing or wrong number of parameters!")
|
---|
343 | else
|
---|
344 | @default_handler.call(methodname, *args)
|
---|
345 | end
|
---|
346 | end
|
---|
347 |
|
---|
348 |
|
---|
349 | #
|
---|
350 | # returns true, if the arity of "obj" matches
|
---|
351 | #
|
---|
352 | def check_arity(obj, n_args)
|
---|
353 | ary = obj.arity
|
---|
354 |
|
---|
355 | if ary >= 0
|
---|
356 | n_args == ary
|
---|
357 | else
|
---|
358 | n_args >= (ary+1).abs
|
---|
359 | end
|
---|
360 | end
|
---|
361 |
|
---|
362 |
|
---|
363 |
|
---|
364 | def call_method(methodname, *args)
|
---|
365 | begin
|
---|
366 | [true, dispatch(methodname, *args)]
|
---|
367 | rescue XMLRPC::FaultException => e
|
---|
368 | [false, e]
|
---|
369 | rescue Exception => e
|
---|
370 | [false, XMLRPC::FaultException.new(ERR_UNCAUGHT_EXCEPTION, "Uncaught exception #{e.message} in method #{methodname}")]
|
---|
371 | end
|
---|
372 | end
|
---|
373 |
|
---|
374 | #
|
---|
375 | #
|
---|
376 | #
|
---|
377 | def handle(methodname, *args)
|
---|
378 | create().methodResponse(*call_method(methodname, *args))
|
---|
379 | end
|
---|
380 |
|
---|
381 |
|
---|
382 | end
|
---|
383 |
|
---|
384 |
|
---|
385 | =begin
|
---|
386 | = XMLRPC::CGIServer
|
---|
387 | == Synopsis
|
---|
388 | require "xmlrpc/server"
|
---|
389 |
|
---|
390 | s = XMLRPC::CGIServer.new
|
---|
391 |
|
---|
392 | s.add_handler("michael.add") do |a,b|
|
---|
393 | a + b
|
---|
394 | end
|
---|
395 |
|
---|
396 | s.add_handler("michael.div") do |a,b|
|
---|
397 | if b == 0
|
---|
398 | raise XMLRPC::FaultException.new(1, "division by zero")
|
---|
399 | else
|
---|
400 | a / b
|
---|
401 | end
|
---|
402 | end
|
---|
403 |
|
---|
404 | s.set_default_handler do |name, *args|
|
---|
405 | raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
|
---|
406 | " or wrong number of parameters!")
|
---|
407 | end
|
---|
408 |
|
---|
409 | s.serve
|
---|
410 |
|
---|
411 | == Description
|
---|
412 | Implements a CGI-based XML-RPC server.
|
---|
413 |
|
---|
414 | == Superclass
|
---|
415 | ((<XMLRPC::BasicServer>))
|
---|
416 |
|
---|
417 | == Class Methods
|
---|
418 | --- XMLRPC::CGIServer.new( *a )
|
---|
419 | Creates a new (({XMLRPC::CGIServer})) instance. All parameters given
|
---|
420 | are by-passed to ((<XMLRPC::BasicServer.new>)). You can only create
|
---|
421 | ((*one*)) (({XMLRPC::CGIServer})) instance, because more than one makes
|
---|
422 | no sense.
|
---|
423 |
|
---|
424 | == Instance Methods
|
---|
425 | --- XMLRPC::CGIServer#serve
|
---|
426 | Call this after you have added all you handlers to the server.
|
---|
427 | This method processes a XML-RPC methodCall and sends the answer
|
---|
428 | back to the client.
|
---|
429 | Make sure that you don't write to standard-output in a handler, or in
|
---|
430 | any other part of your program, this would case a CGI-based server to fail!
|
---|
431 | =end
|
---|
432 |
|
---|
433 | class CGIServer < BasicServer
|
---|
434 | @@obj = nil
|
---|
435 |
|
---|
436 | def CGIServer.new(*a)
|
---|
437 | @@obj = super(*a) if @@obj.nil?
|
---|
438 | @@obj
|
---|
439 | end
|
---|
440 |
|
---|
441 | def initialize(*a)
|
---|
442 | super(*a)
|
---|
443 | end
|
---|
444 |
|
---|
445 | def serve
|
---|
446 | catch(:exit_serve) {
|
---|
447 | length = ENV['CONTENT_LENGTH'].to_i
|
---|
448 |
|
---|
449 | http_error(405, "Method Not Allowed") unless ENV['REQUEST_METHOD'] == "POST"
|
---|
450 | http_error(400, "Bad Request") unless parse_content_type(ENV['CONTENT_TYPE']).first == "text/xml"
|
---|
451 | http_error(411, "Length Required") unless length > 0
|
---|
452 |
|
---|
453 | # TODO: do we need a call to binmode?
|
---|
454 | $stdin.binmode if $stdin.respond_to? :binmode
|
---|
455 | data = $stdin.read(length)
|
---|
456 |
|
---|
457 | http_error(400, "Bad Request") if data.nil? or data.size != length
|
---|
458 |
|
---|
459 | http_write(process(data), "Content-type" => "text/xml; charset=utf-8")
|
---|
460 | }
|
---|
461 | end
|
---|
462 |
|
---|
463 |
|
---|
464 | private
|
---|
465 |
|
---|
466 | def http_error(status, message)
|
---|
467 | err = "#{status} #{message}"
|
---|
468 | msg = <<-"MSGEND"
|
---|
469 | <html>
|
---|
470 | <head>
|
---|
471 | <title>#{err}</title>
|
---|
472 | </head>
|
---|
473 | <body>
|
---|
474 | <h1>#{err}</h1>
|
---|
475 | <p>Unexpected error occured while processing XML-RPC request!</p>
|
---|
476 | </body>
|
---|
477 | </html>
|
---|
478 | MSGEND
|
---|
479 |
|
---|
480 | http_write(msg, "Status" => err, "Content-type" => "text/html")
|
---|
481 | throw :exit_serve # exit from the #serve method
|
---|
482 | end
|
---|
483 |
|
---|
484 | def http_write(body, header)
|
---|
485 | h = {}
|
---|
486 | header.each {|key, value| h[key.to_s.capitalize] = value}
|
---|
487 | h['Status'] ||= "200 OK"
|
---|
488 | h['Content-length'] ||= body.size.to_s
|
---|
489 |
|
---|
490 | str = ""
|
---|
491 | h.each {|key, value| str << "#{key}: #{value}\r\n"}
|
---|
492 | str << "\r\n#{body}"
|
---|
493 |
|
---|
494 | print str
|
---|
495 | end
|
---|
496 |
|
---|
497 | end
|
---|
498 |
|
---|
499 | =begin
|
---|
500 | = XMLRPC::ModRubyServer
|
---|
501 | == Description
|
---|
502 | Implements a XML-RPC server, which works with Apache mod_ruby.
|
---|
503 |
|
---|
504 | Use it in the same way as CGIServer!
|
---|
505 |
|
---|
506 | == Superclass
|
---|
507 | ((<XMLRPC::BasicServer>))
|
---|
508 | =end
|
---|
509 |
|
---|
510 | class ModRubyServer < BasicServer
|
---|
511 |
|
---|
512 | def initialize(*a)
|
---|
513 | @ap = Apache::request
|
---|
514 | super(*a)
|
---|
515 | end
|
---|
516 |
|
---|
517 | def serve
|
---|
518 | catch(:exit_serve) {
|
---|
519 | header = {}
|
---|
520 | @ap.headers_in.each {|key, value| header[key.capitalize] = value}
|
---|
521 |
|
---|
522 | length = header['Content-length'].to_i
|
---|
523 |
|
---|
524 | http_error(405, "Method Not Allowed") unless @ap.request_method == "POST"
|
---|
525 | http_error(400, "Bad Request") unless parse_content_type(header['Content-type']).first == "text/xml"
|
---|
526 | http_error(411, "Length Required") unless length > 0
|
---|
527 |
|
---|
528 | # TODO: do we need a call to binmode?
|
---|
529 | @ap.binmode
|
---|
530 | data = @ap.read(length)
|
---|
531 |
|
---|
532 | http_error(400, "Bad Request") if data.nil? or data.size != length
|
---|
533 |
|
---|
534 | http_write(process(data), 200, "Content-type" => "text/xml; charset=utf-8")
|
---|
535 | }
|
---|
536 | end
|
---|
537 |
|
---|
538 |
|
---|
539 | private
|
---|
540 |
|
---|
541 | def http_error(status, message)
|
---|
542 | err = "#{status} #{message}"
|
---|
543 | msg = <<-"MSGEND"
|
---|
544 | <html>
|
---|
545 | <head>
|
---|
546 | <title>#{err}</title>
|
---|
547 | </head>
|
---|
548 | <body>
|
---|
549 | <h1>#{err}</h1>
|
---|
550 | <p>Unexpected error occured while processing XML-RPC request!</p>
|
---|
551 | </body>
|
---|
552 | </html>
|
---|
553 | MSGEND
|
---|
554 |
|
---|
555 | http_write(msg, status, "Status" => err, "Content-type" => "text/html")
|
---|
556 | throw :exit_serve # exit from the #serve method
|
---|
557 | end
|
---|
558 |
|
---|
559 | def http_write(body, status, header)
|
---|
560 | h = {}
|
---|
561 | header.each {|key, value| h[key.to_s.capitalize] = value}
|
---|
562 | h['Status'] ||= "200 OK"
|
---|
563 | h['Content-length'] ||= body.size.to_s
|
---|
564 |
|
---|
565 | h.each {|key, value| @ap.headers_out[key] = value }
|
---|
566 | @ap.content_type = h["Content-type"]
|
---|
567 | @ap.status = status.to_i
|
---|
568 | @ap.send_http_header
|
---|
569 |
|
---|
570 | @ap.print body
|
---|
571 | end
|
---|
572 |
|
---|
573 | end
|
---|
574 |
|
---|
575 | =begin
|
---|
576 | = XMLRPC::Server
|
---|
577 | == Synopsis
|
---|
578 | require "xmlrpc/server"
|
---|
579 |
|
---|
580 | s = XMLRPC::Server.new(8080)
|
---|
581 |
|
---|
582 | s.add_handler("michael.add") do |a,b|
|
---|
583 | a + b
|
---|
584 | end
|
---|
585 |
|
---|
586 | s.add_handler("michael.div") do |a,b|
|
---|
587 | if b == 0
|
---|
588 | raise XMLRPC::FaultException.new(1, "division by zero")
|
---|
589 | else
|
---|
590 | a / b
|
---|
591 | end
|
---|
592 | end
|
---|
593 |
|
---|
594 | s.set_default_handler do |name, *args|
|
---|
595 | raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
|
---|
596 | " or wrong number of parameters!")
|
---|
597 | end
|
---|
598 |
|
---|
599 | s.serve
|
---|
600 |
|
---|
601 | == Description
|
---|
602 | Implements a standalone XML-RPC server. The method (({serve}))) is left if a SIGHUP is sent to the
|
---|
603 | program.
|
---|
604 |
|
---|
605 | == Superclass
|
---|
606 | ((<XMLRPC::WEBrickServlet>))
|
---|
607 |
|
---|
608 | == Class Methods
|
---|
609 | --- XMLRPC::Server.new( port=8080, host="127.0.0.1", maxConnections=4, stdlog=$stdout, audit=true, debug=true, *a )
|
---|
610 | Creates a new (({XMLRPC::Server})) instance, which is a XML-RPC server listening on
|
---|
611 | port ((|port|)) and accepts requests for the host ((|host|)), which is by default only the localhost.
|
---|
612 | The server is not started, to start it you have to call ((<serve|XMLRPC::Server#serve>)).
|
---|
613 |
|
---|
614 | Parameters ((|audit|)) and ((|debug|)) are obsolete!
|
---|
615 |
|
---|
616 | All additionally given parameters in ((|*a|)) are by-passed to ((<XMLRPC::BasicServer.new>)).
|
---|
617 |
|
---|
618 | == Instance Methods
|
---|
619 | --- XMLRPC::Server#serve
|
---|
620 | Call this after you have added all you handlers to the server.
|
---|
621 | This method starts the server to listen for XML-RPC requests and answer them.
|
---|
622 |
|
---|
623 | --- XMLRPC::Server#shutdown
|
---|
624 | Stops and shuts the server down.
|
---|
625 |
|
---|
626 | =end
|
---|
627 |
|
---|
628 | class WEBrickServlet < BasicServer; end # forward declaration
|
---|
629 |
|
---|
630 | class Server < WEBrickServlet
|
---|
631 |
|
---|
632 | def initialize(port=8080, host="127.0.0.1", maxConnections=4, stdlog=$stdout, audit=true, debug=true, *a)
|
---|
633 | super(*a)
|
---|
634 | require 'webrick'
|
---|
635 | @server = WEBrick::HTTPServer.new(:Port => port, :BindAddress => host, :MaxClients => maxConnections,
|
---|
636 | :Logger => WEBrick::Log.new(stdlog))
|
---|
637 | @server.mount("/", self)
|
---|
638 | end
|
---|
639 |
|
---|
640 | def serve
|
---|
641 | if RUBY_PLATFORM =~ /mingw|mswin32/
|
---|
642 | signal = 1
|
---|
643 | else
|
---|
644 | signal = "HUP"
|
---|
645 | end
|
---|
646 | trap(signal) { @server.shutdown }
|
---|
647 |
|
---|
648 | @server.start
|
---|
649 | end
|
---|
650 |
|
---|
651 | def shutdown
|
---|
652 | @server.shutdown
|
---|
653 | end
|
---|
654 |
|
---|
655 | end
|
---|
656 |
|
---|
657 | =begin
|
---|
658 | = XMLRPC::WEBrickServlet
|
---|
659 | == Synopsis
|
---|
660 |
|
---|
661 | require "webrick"
|
---|
662 | require "xmlrpc/server"
|
---|
663 |
|
---|
664 | s = XMLRPC::WEBrickServlet.new
|
---|
665 | s.add_handler("michael.add") do |a,b|
|
---|
666 | a + b
|
---|
667 | end
|
---|
668 |
|
---|
669 | s.add_handler("michael.div") do |a,b|
|
---|
670 | if b == 0
|
---|
671 | raise XMLRPC::FaultException.new(1, "division by zero")
|
---|
672 | else
|
---|
673 | a / b
|
---|
674 | end
|
---|
675 | end
|
---|
676 |
|
---|
677 | s.set_default_handler do |name, *args|
|
---|
678 | raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
|
---|
679 | " or wrong number of parameters!")
|
---|
680 | end
|
---|
681 |
|
---|
682 | httpserver = WEBrick::HTTPServer.new(:Port => 8080)
|
---|
683 | httpserver.mount("/RPC2", s)
|
---|
684 | trap("HUP") { httpserver.shutdown } # use 1 instead of "HUP" on Windows
|
---|
685 | httpserver.start
|
---|
686 |
|
---|
687 | == Instance Methods
|
---|
688 |
|
---|
689 | --- XMLRPC::WEBrickServlet#set_valid_ip( *ip_addr )
|
---|
690 | Specifies the valid IP addresses that are allowed to connect to the server.
|
---|
691 | Each IP is either a (({String})) or a (({Regexp})).
|
---|
692 |
|
---|
693 | --- XMLRPC::WEBrickServlet#get_valid_ip
|
---|
694 | Return the via method ((<set_valid_ip|XMLRPC::Server#set_valid_ip>)) specified
|
---|
695 | valid IP addresses.
|
---|
696 |
|
---|
697 | == Description
|
---|
698 | Implements a servlet for use with WEBrick, a pure Ruby (HTTP-) server framework.
|
---|
699 |
|
---|
700 | == Superclass
|
---|
701 | ((<XMLRPC::BasicServer>))
|
---|
702 |
|
---|
703 | =end
|
---|
704 |
|
---|
705 | class WEBrickServlet < BasicServer
|
---|
706 | def initialize(*a)
|
---|
707 | super
|
---|
708 | require "webrick/httpstatus"
|
---|
709 | @valid_ip = nil
|
---|
710 | end
|
---|
711 |
|
---|
712 | # deprecated from WEBrick/1.2.2.
|
---|
713 | # but does not break anything.
|
---|
714 | def require_path_info?
|
---|
715 | false
|
---|
716 | end
|
---|
717 |
|
---|
718 | def get_instance(config, *options)
|
---|
719 | # TODO: set config & options
|
---|
720 | self
|
---|
721 | end
|
---|
722 |
|
---|
723 | def set_valid_ip(*ip_addr)
|
---|
724 | if ip_addr.size == 1 and ip_addr[0].nil?
|
---|
725 | @valid_ip = nil
|
---|
726 | else
|
---|
727 | @valid_ip = ip_addr
|
---|
728 | end
|
---|
729 | end
|
---|
730 |
|
---|
731 | def get_valid_ip
|
---|
732 | @valid_ip
|
---|
733 | end
|
---|
734 |
|
---|
735 | def service(request, response)
|
---|
736 |
|
---|
737 | if @valid_ip
|
---|
738 | raise WEBrick::HTTPStatus::Forbidden unless @valid_ip.any? { |ip| request.peeraddr[3] =~ ip }
|
---|
739 | end
|
---|
740 |
|
---|
741 | if request.request_method != "POST"
|
---|
742 | raise WEBrick::HTTPStatus::MethodNotAllowed,
|
---|
743 | "unsupported method `#{request.request_method}'."
|
---|
744 | end
|
---|
745 |
|
---|
746 | if parse_content_type(request['Content-type']).first != "text/xml"
|
---|
747 | raise WEBrick::HTTPStatus::BadRequest
|
---|
748 | end
|
---|
749 |
|
---|
750 | length = (request['Content-length'] || 0).to_i
|
---|
751 |
|
---|
752 | raise WEBrick::HTTPStatus::LengthRequired unless length > 0
|
---|
753 |
|
---|
754 | data = request.body
|
---|
755 |
|
---|
756 | if data.nil? or data.size != length
|
---|
757 | raise WEBrick::HTTPStatus::BadRequest
|
---|
758 | end
|
---|
759 |
|
---|
760 | resp = process(data)
|
---|
761 | if resp.nil? or resp.size <= 0
|
---|
762 | raise WEBrick::HTTPStatus::InternalServerError
|
---|
763 | end
|
---|
764 |
|
---|
765 | response.status = 200
|
---|
766 | response['Content-Length'] = resp.size
|
---|
767 | response['Content-Type'] = "text/xml; charset=utf-8"
|
---|
768 | response.body = resp
|
---|
769 | end
|
---|
770 | end
|
---|
771 |
|
---|
772 |
|
---|
773 | end # module XMLRPC
|
---|
774 |
|
---|
775 |
|
---|
776 | =begin
|
---|
777 | = History
|
---|
778 | $Id: server.rb 11708 2007-02-12 23:01:19Z shyouhei $
|
---|
779 | =end
|
---|
780 |
|
---|