Skip to content

Commit a004009

Browse files
committed
Use a proper Middleware for heavy lifting
The `xmlrpc.synchronousCall` middleware now triggers the handler execution. The RouteKeeper is only convenience wrapping now. Also fixed up the bodyParser, which still had some issues. Finally, moved the basic introspection support to its own file.
1 parent 62eb16f commit a004009

File tree

6 files changed

+152
-48
lines changed

6 files changed

+152
-48
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ let package = Package(
1212

1313
dependencies: [
1414
.package(url: "https://github.com/Macro-swift/Macro.git",
15-
from: "0.5.9"),
15+
from: "0.6.0"),
1616
.package(url: "https://github.com/Macro-swift/MacroExpress.git",
1717
from: "0.5.7"),
1818
.package(url: "https://github.com/AlwaysRightInstitute/SwiftXmlRpc.git",

Sources/MacroXmlRpc/BodyParser.swift

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,31 @@ public extension bodyParser {
2323
*
2424
* Usage:
2525
*
26-
* app.use(bodyParser.xmlRpcCall())
26+
* app.route("/RPC2")
27+
* .use(bodyParser.xmlRpcCall())
28+
* .post("/RPC2") { req, res, next in
29+
* guard let call = req.xmlRpcCall else { return next() }
30+
* console.log("received call:", call)
31+
* }
2732
*
28-
* app.post("/RPC2") { req, res, next in
29-
* guard let call = req.xmlRpcCall else { return next() }
30-
* console.log("received call:", call)
31-
* }
33+
* Note: Do not unnecessary call this middleware, i.e. maybe not at the top
34+
* level, but rather as part of an actual XML-RPC route.
3235
*
3336
* This plays well w/ other body parsers. If no other parser was active,
3437
* it will fill `request.body` as `.text`.
3538
*/
36-
func xmlRpcCall() -> Middleware {
39+
@inlinable
40+
static func xmlRpcCall() -> Middleware {
3741
return { req, res, next in
3842
if req.extra[xmlRpcRequestKey] != nil { return next() } // parsed already
3943

44+
func registerCallInLogger() {
45+
guard let call = req.xmlRpcCall else { return }
46+
// If we parsed an XML-RPC call, add its method name to the logging
47+
// meta data. It is important contextual information.
48+
req.log[metadataKey: "xmlrpc"] = .string(call.methodName)
49+
}
50+
4051
// This deals w/ other bodyParsers being active. If we already have
4152
// content (e.g. from bodyParser.text or .raw) we reuse that.
4253
switch req.body {
@@ -51,6 +62,7 @@ public extension bodyParser {
5162
req.body = .text(string)
5263
req.xmlRpcBody = XmlRpc.parseCall(string).flatMap { .call($0) }
5364
?? .invalid
65+
registerCallInLogger()
5466
return nil
5567
}
5668
catch {
@@ -75,6 +87,7 @@ public extension bodyParser {
7587
req.body = .text(string)
7688
req.xmlRpcBody = XmlRpc.parseCall(string).flatMap { .call($0) }
7789
?? .invalid
90+
registerCallInLogger()
7891
}
7992
catch {
8093
// In this case, this doesn't have to be an error. Could be some
@@ -86,6 +99,7 @@ public extension bodyParser {
8699
case .text(let string):
87100
req.xmlRpcBody = XmlRpc.parseCall(string).flatMap { .call($0) }
88101
?? .invalid
102+
registerCallInLogger()
89103
return next()
90104
}
91105
}
@@ -126,9 +140,10 @@ public extension IncomingMessage {
126140

127141
// MARK: - Helper
128142

129-
private func concatError(request : IncomingMessage,
130-
next : @escaping Next,
131-
handler : @escaping ( Buffer ) -> Swift.Error?)
143+
@usableFromInline
144+
func concatError(request : IncomingMessage,
145+
next : @escaping Next,
146+
handler : @escaping ( Buffer ) -> Swift.Error?)
132147
{
133148
var didCallNext = false
134149

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// Introspection.swift
3+
// MacroXmlRpc
4+
//
5+
// Created by Helge Hess.
6+
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
7+
//
8+
9+
public extension RouteKeeper {
10+
11+
@inlinable
12+
@discardableResult
13+
func systemListMethods() -> Self {
14+
post { req, res, next in
15+
guard let call = XmlRpc.parseCall(req.body.text ?? ""),
16+
call.methodName == "system.listMethods" else {
17+
return next()
18+
}
19+
res.send(XmlRpc.Response(req.knownXmlRpcMethodNames).xmlString)
20+
}
21+
}
22+
}
23+
24+
extension IncomingMessage {
25+
26+
@usableFromInline
27+
var knownXmlRpcMethodNames : [ String ] {
28+
return (extra["rpc.methods"] as? [ String ]) ?? []
29+
}
30+
31+
@usableFromInline
32+
func addKnownXmlRpcMethod(_ methodName: String) {
33+
if var methods = extra.removeValue(forKey: "rpc.methods") as? [ String ] {
34+
methods.append(methodName)
35+
extra["rpc.methods"] = methods
36+
}
37+
else {
38+
extra["rpc.methods"] = [ methodName ]
39+
}
40+
}
41+
}

Sources/MacroXmlRpc/Middleware.swift

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,89 @@
66
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
77
//
88

9+
import enum XmlRpc.XmlRpc
10+
import protocol XmlRpc.XmlRpcValueRepresentable
11+
import MacroExpress
12+
913
// TODO: Try to mirror the JS server in http://xmlrpc.com
1014
public enum xmlrpc {}
15+
16+
public extension xmlrpc {
17+
18+
/**
19+
* Calls an XML-RPC handler function synchronously.
20+
*
21+
* It is recommended to invoke the `bodyParser.xmlRpcCall` in a proper place,
22+
* though if that didn't happen, this method will do it for the user.
23+
*/
24+
static func synchronousCall(_ methodName: String? = nil,
25+
execute: @escaping ( XmlRpc.Call ) throws
26+
-> XmlRpcValueRepresentable)
27+
-> Middleware
28+
{
29+
return { req, res, next in
30+
guard req.method == "POST" else { return next() }
31+
32+
if let methodName = methodName {
33+
req.addKnownXmlRpcMethod(methodName)
34+
}
35+
36+
/* implementation */
37+
38+
func process() {
39+
guard let call = req.xmlRpcCall else {
40+
// TBD: Not quite sure what the best thing is.
41+
if typeIs(req, [ "text/xml" ]) != nil {
42+
req.log.error("Could not parse XML-RPC call.")
43+
return res.sendStatus(400)
44+
}
45+
else {
46+
return next()
47+
}
48+
}
49+
50+
if let methodName = methodName, call.methodName != methodName {
51+
return next()
52+
}
53+
54+
// It is an XML-RPC call and it matches the requested method (or all).
55+
do {
56+
let value = try execute(call)
57+
58+
req.log.log("executed request:", call.methodName)
59+
return res.send(XmlRpc.Response.value(value.xmlRpcValue).xmlString)
60+
}
61+
catch let error as XmlRpc.Fault {
62+
req.log.error("XML-RPC call failed w/ fault:", error)
63+
return res.send(XmlRpc.Response.fault(error).xmlString)
64+
}
65+
catch {
66+
req.log.error("XML-RPC call failed w/ non-fault error:", error)
67+
res.statusCode = 500
68+
return res.send("Call to XML-RPC function failed.")
69+
}
70+
}
71+
72+
/* Body parser is not active */
73+
if req.extra[xmlRpcRequestKey] == nil,
74+
typeIs(req, [ "text/xml" ]) != nil
75+
{
76+
req.log.notice("Use of XML-RPC middleware w/o a bodyParser")
77+
let parser = bodyParser.xmlRpcCall()
78+
do {
79+
try parser(req, res) { (args: Any...) in
80+
assert(req.extra[xmlRpcRequestKey] != nil) // at least .invalid!
81+
process()
82+
}
83+
}
84+
catch {
85+
req.log.notice("failed to parse XML-RPC: \(error)")
86+
return next()
87+
}
88+
}
89+
else { // we have a proper body already
90+
process()
91+
}
92+
}
93+
}
94+
}

Sources/MacroXmlRpc/ReExports.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88

99
@_exported import Macro
1010
@_exported import MacroExpress
11+
@_exported import enum XmlRpc.XmlRpc

Sources/MacroXmlRpc/RouteKeeper.swift

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,44 +18,7 @@ public extension RouteKeeper {
1818
( XmlRpc.Call ) throws -> XmlRpcValueRepresentable)
1919
-> Self
2020
{
21-
post { req, res, next in
22-
if let methodName = methodName {
23-
let methods = (req.extra["rpc.methods"] as? [ String ]) ?? []
24-
req.extra["rpc.methods"] = methods + [ methodName ]
25-
}
26-
27-
guard let call = XmlRpc.parseCall(req.body.text ?? "") else {
28-
return res.sendStatus(400)
29-
}
30-
31-
if let methodName = methodName, call.methodName != methodName {
32-
return next()
33-
}
34-
35-
do {
36-
let value = try execute(call)
37-
res.send(XmlRpc.Response.value(value.xmlRpcValue).xmlString)
38-
}
39-
catch let error as XmlRpc.Fault {
40-
res.send(XmlRpc.Response.fault(error).xmlString)
41-
}
42-
catch {
43-
res.sendStatus(500)
44-
}
45-
}
46-
}
47-
48-
@inlinable
49-
@discardableResult
50-
func systemListMethods() -> Self {
51-
post { req, res, next in
52-
guard let call = XmlRpc.parseCall(req.body.text ?? ""),
53-
call.methodName == "system.listMethods" else {
54-
return next()
55-
}
56-
let methods = (req.extra["rpc.methods"] as? [ String ]) ?? []
57-
res.send(XmlRpc.Response(methods).xmlString)
58-
}
21+
post(xmlrpc.synchronousCall(methodName, execute: execute))
5922
}
6023
}
6124

0 commit comments

Comments
 (0)