Skip to content

Commit df5c2c3

Browse files
committed
Add XMLRPC::RackServer
This is a simple Rack middleware that acts as an XMLRPC server. This can be used in any Rack compatible webserver.
1 parent e18b4cb commit df5c2c3

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ group :development do
66
gem "bundler"
77
gem "libxml-ruby", platforms: [:ruby, :jruby]
88
gem "nokogiri", platforms: [:ruby, :jruby]
9+
gem "rack"
10+
gem "rack-test"
911
gem "rake"
1012
gem "test-unit"
1113
end

lib/xmlrpc/server.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,49 @@ def http_write(body, status, header)
532532
end
533533

534534

535+
# Implements a XML-RPC application, which works with Rack
536+
class RackApplication < BasicServer
537+
538+
# This method processes a XML-RPC method call and sends the answer
539+
# back to the client.
540+
def call(env)
541+
length = env['CONTENT_LENGTH'].to_i
542+
543+
return http_error(405, "Method Not Allowed") unless env['REQUEST_METHOD'] == "POST"
544+
return http_error(400, "Bad Request") unless parse_content_type(env['CONTENT_TYPE']).first == "text/xml"
545+
return http_error(411, "Length Required") unless length > 0
546+
547+
req = Rack::Request.new(env)
548+
data = req.body.read(length)
549+
550+
return http_error(400, "Bad Request") if data.nil? or data.bytesize != length
551+
552+
[200, { "Content-type" => "text/xml; charset=utf-8" }, [process(data)]]
553+
end
554+
555+
556+
private
557+
558+
def http_error(status, message)
559+
err = "#{status} #{message}"
560+
msg = <<-"MSGEND"
561+
<html>
562+
<head>
563+
<title>#{err}</title>
564+
</head>
565+
<body>
566+
<h1>#{err}</h1>
567+
<p>Unexpected error occurred while processing XML-RPC request!</p>
568+
</body>
569+
</html>
570+
MSGEND
571+
572+
[status, { "Content-Type" => "text/html" }, [msg]]
573+
end
574+
575+
end
576+
577+
535578
class WEBrickServlet < BasicServer; end # forward declaration
536579

537580
# Implements a standalone XML-RPC server. The method XMLRPC::Server#serve is

test/test_rack_server.rb

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# frozen_string_literal: true
2+
3+
require "test/unit"
4+
require "rack/test"
5+
require "xmlrpc/server"
6+
require 'xmlrpc/create'
7+
require 'xmlrpc/parser'
8+
9+
class TestRack < Test::Unit::TestCase
10+
include Rack::Test::Methods
11+
12+
def app
13+
s = XMLRPC::RackApplication.new
14+
15+
s.add_handler("test.add") do |a,b|
16+
a + b
17+
end
18+
19+
s.add_handler("test.div") do |a,b|
20+
if b == 0
21+
raise XMLRPC::FaultException.new(1, "division by zero")
22+
else
23+
a / b
24+
end
25+
end
26+
27+
s.set_default_handler do |name, *args|
28+
raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
29+
" or wrong number of parameters!")
30+
end
31+
32+
s.add_introspection
33+
34+
s
35+
end
36+
37+
def test_successful_call
38+
assert_equal([true, 9],
39+
call("test.add", 4, 5))
40+
end
41+
42+
def test_fault_exception
43+
assert_equal([false, XMLRPC::FaultException.new(1, "division by zero")],
44+
call("test.div", 1, 0))
45+
end
46+
47+
def test_introspection
48+
assert_equal([true, methods = ["test.add", "test.div", "system.listMethods", "system.methodSignature", "system.methodHelp"]],
49+
call("system.listMethods"))
50+
end
51+
52+
def test_missing_handler
53+
assert_equal([false, XMLRPC::FaultException.new(-99, "Method test.nonexisting missing or wrong number of parameters!")],
54+
call("test.nonexisting"))
55+
end
56+
57+
def test_wrong_number_of_arguments
58+
assert_equal([false, XMLRPC::FaultException.new(-99, "Method test.add missing or wrong number of parameters!")],
59+
call("test.add", 1, 2, 3))
60+
end
61+
62+
def test_multibyte_characters
63+
assert_equal([true, "あいうえおかきくけこ"],
64+
call("test.add", "あいうえお", "かきくけこ"))
65+
end
66+
67+
def test_method_not_allowed
68+
get("/", "<stub />", "CONTENT_TYPE" => "text/xml")
69+
assert(last_response.method_not_allowed?, "Expected HTTP status code 405, got #{last_response.status} instead")
70+
end
71+
72+
def test_bad_content_type
73+
post("/", "<stub />", "CONTENT_TYPE" => "text/plain")
74+
assert(last_response.bad_request?, "Expected HTTP status code 400, got #{last_response.status} instead")
75+
end
76+
77+
def test_empty_request
78+
post("/", "", "CONTENT_TYPE" => "text/xml")
79+
assert_equal(411, last_response.status, "Expected HTTP status code 411, got #{last_response.status} instead")
80+
end
81+
82+
def call(methodname, *args)
83+
create = XMLRPC::Create.new(XMLRPC::Config.default_writer.new)
84+
parser = XMLRPC::Config.default_parser.new
85+
86+
request = create.methodCall(methodname, *args)
87+
post("/", request, "CONTENT_TYPE" => "text/xml")
88+
assert(last_response.ok?, "Expected HTTP status code 200, got #{last_response.status} instead")
89+
parser.parseMethodResponse(last_response.body)
90+
end
91+
end

0 commit comments

Comments
 (0)