Skip to content

Commit 933ad74

Browse files
committed
Merge branch 'develop' into main
2 parents 84a9a88 + 442b93d commit 933ad74

File tree

10 files changed

+735
-44
lines changed

10 files changed

+735
-44
lines changed

Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ let package = Package(
1111
],
1212

1313
dependencies: [
14+
.package(url: "https://github.com/Macro-swift/Macro.git",
15+
from: "0.6.0"),
1416
.package(url: "https://github.com/Macro-swift/MacroExpress.git",
1517
from: "0.5.7"),
1618
.package(url: "https://github.com/AlwaysRightInstitute/SwiftXmlRpc.git",
1719
from: "0.8.5")
1820
],
1921

2022
targets: [
21-
.target(name: "MacroXmlRpc", dependencies: [ "MacroExpress", "XmlRpc" ])
23+
.target(name: "MacroXmlRpc",
24+
dependencies: [ "Macro", "MacroExpress", "XmlRpc" ])
2225
]
2326
)

README.md

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,30 @@
1-
<h2>MacroExpress
1+
<h2>Macro XML-RPC
22
<img src="http://zeezide.com/img/macro/MacroExpressIcon128.png"
33
align="right" width="100" height="100" />
44
</h2>
55

6-
A small, unopinionated "don't get into my way" / "I don't wanna `wait`"
7-
asynchronous web framework for Swift.
8-
With a strong focus on replicating the Node APIs in Swift.
9-
But in a typesafe, and fast way.
6+
XML-RPC support for
7+
[MacroExpress](https://github.com/Macro-swift/MacroExpress).
108

11-
MacroExpress is a more capable variant of
12-
[µExpress](https://github.com/NozeIO/MicroExpress).
13-
The goal is still to keep a small core, but add some
14-
[Noze.io](http://noze.io)
15-
modules and concepts.
16-
17-
MacroExpress adds the web framework components to
18-
[Macro](https://github.com/Macro-swift/Macro/)
19-
(kinda like `Express.js` adds to `Node.js`).
20-
21-
[MacroLambda](https://github.com/Macro-swift/MacroLambda) has the bits to
22-
directly deploy MacroExpress applications on AWS Lambda.
23-
[MacroApp](https://github.com/Macro-swift/MacroApp) adds a SwiftUI-style
24-
declarative DSL to setup MacroExpress routes.
9+
This is covered in the
10+
[Writing an Swift XML-RPC Server](http://www.alwaysrightinstitute.com/macro-xmlrpc/)
11+
blog entry.
2512

2613

2714
## What does it look like?
2815

29-
The Macro [Examples](https://github.com/Macro-swift/Examples) package
30-
contains a few examples which all can run straight from the source as
31-
swift-sh scripts.
32-
3316
```swift
3417
#!/usr/bin/swift sh
35-
import MacroExpress // @Macro-swift ~> 0.5.5
18+
import MacroExpress // @Macro-swift
19+
import MacroXmlRpc // @Macro-swift
3620

3721
let app = express()
38-
app.use(logger("dev"))
39-
app.use(bodyParser.urlencoded())
40-
app.use(serveStatic(__dirname() + "/public"))
4122

42-
app.get("/hello") { req, res, next in
43-
res.send("Hello World!")
44-
}
45-
app.get {
46-
res.render("index")
47-
}
23+
app.route("/RPC2")
24+
.use(bodyParser.xmlRpcCall())
25+
.rpc("ping") { _ in "pong" }
26+
.rpc("add") { ( a: Int, b: Int ) in a + b }
27+
.use(xmlrpc.introspection())
4828

4929
app.listen(1337)
5030
```
@@ -62,20 +42,19 @@ app.listen(1337)
6242

6343
### Links
6444

45+
- [Writing an Swift XML-RPC Server](http://www.alwaysrightinstitute.com/macro-xmlrpc/)
46+
- [MacroExpress](https://github.com/Macro-swift/MacroExpress).
6547
- [Macro](https://github.com/Macro-swift/Macro/)
66-
- [µExpress](http://www.alwaysrightinstitute.com/microexpress-nio2/)
67-
- [Noze.io](http://noze.io)
68-
- [SwiftNIO](https://github.com/apple/swift-nio)
69-
- JavaScript Originals
70-
- [Connect](https://github.com/senchalabs/connect)
71-
- [Express.js](http://expressjs.com/en/starter/hello-world.html)
72-
- Swift Apache
73-
- [mod_swift](http://mod-swift.org)
74-
- [ApacheExpress](http://apacheexpress.io)
48+
- [XML-RPC Homepage](http://xmlrpc.com)
49+
- [XML-RPC Spec](http://xmlrpc.com/spec.md)
50+
- [XML-RPC for Newbies](http://scripting.com/davenet/1998/07/14/xmlRpcForNewbies.html)
51+
- [Original site](http://1998.xmlrpc.com)
52+
- [XML-RPC HowTo](https://tldp.org/HOWTO/XML-RPC-HOWTO/index.html) by Eric Kidd
53+
- [Python](https://docs.python.org/3/library/xmlrpc.client.html#module-xmlrpc.client) Client
7554

7655
### Who
7756

78-
**MacroExpress** is brought to you by
57+
**Macro XML-RPC** is brought to you by
7958
the
8059
[Always Right Institute](http://www.alwaysrightinstitute.com)
8160
and
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
//
2+
// BodyParser.swift
3+
// MacroXmlRpc
4+
//
5+
// Created by Helge Hess.
6+
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
7+
//
8+
9+
import MacroExpress
10+
import enum XmlRpc.XmlRpc
11+
12+
public extension bodyParser {
13+
14+
enum XmlRpcBodyParserBody {
15+
case invalid
16+
case call (XmlRpc.Call)
17+
case response(XmlRpc.Response)
18+
}
19+
20+
/**
21+
* If available, parse an XML-RPC method call into the `request.xmlRpcBody`
22+
* extra slot.
23+
*
24+
* Usage:
25+
*
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+
* }
32+
*
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.
35+
*
36+
* This plays well w/ other body parsers. If no other parser was active,
37+
* it will fill `request.body` as `.text`.
38+
*/
39+
@inlinable
40+
static func xmlRpcCall() -> Middleware {
41+
return { req, res, next in
42+
if req.extra[xmlRpcRequestKey] != nil { return next() } // parsed already
43+
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+
51+
// This deals w/ other bodyParsers being active. If we already have
52+
// content (e.g. from bodyParser.text or .raw) we reuse that.
53+
switch req.body {
54+
55+
case .notParsed:
56+
guard typeIs(req, [ "text/xml" ]) != nil else { return next() }
57+
58+
// Collect request content using the `concat` stream.
59+
return concatError(request: req, next: next) { bytes in
60+
do {
61+
let string = try bytes.toString()
62+
req.body = .text(string)
63+
req.xmlRpcBody = XmlRpc.parseCall(string).flatMap { .call($0) }
64+
?? .invalid
65+
registerCallInLogger()
66+
return nil
67+
}
68+
catch {
69+
req.body = .error(BodyParserError.couldNotDecodeString(error))
70+
req.xmlRpcBody = .invalid
71+
return error
72+
}
73+
}
74+
75+
case .noBody, .error:
76+
req.xmlRpcBody = .invalid
77+
return next()
78+
79+
case .urlEncoded, .json: // TODO: we could try to map those!
80+
req.xmlRpcBody = .invalid
81+
return next()
82+
83+
case .raw(let bytes):
84+
// TBD: check for text/xml?
85+
do {
86+
let string = try bytes.toString()
87+
req.body = .text(string)
88+
req.xmlRpcBody = XmlRpc.parseCall(string).flatMap { .call($0) }
89+
?? .invalid
90+
registerCallInLogger()
91+
}
92+
catch {
93+
// In this case, this doesn't have to be an error. Could be some
94+
// other raw data.
95+
req.xmlRpcBody = .invalid
96+
}
97+
return next()
98+
99+
case .text(let string):
100+
req.xmlRpcBody = XmlRpc.parseCall(string).flatMap { .call($0) }
101+
?? .invalid
102+
registerCallInLogger()
103+
return next()
104+
}
105+
}
106+
}
107+
108+
}
109+
110+
@usableFromInline
111+
let xmlRpcRequestKey = "macro.xmlrpc.body-parser"
112+
113+
public extension IncomingMessage {
114+
115+
/**
116+
* Returns the XML-RPC body parsed by e.g. `bodyParser.xmlRpcCall`. It is
117+
* only filled when the middleware executed, otherwise it returns `.invalid`.
118+
*/
119+
@inlinable
120+
var xmlRpcBody: bodyParser.XmlRpcBodyParserBody {
121+
set { extra[xmlRpcRequestKey] = newValue }
122+
get {
123+
return (extra[xmlRpcRequestKey] as? bodyParser.XmlRpcBodyParserBody)
124+
?? .invalid
125+
}
126+
}
127+
128+
/**
129+
* Returns the XML-RPC body parsed by e.g. `bodyParser.xmlRpcCall`. It is
130+
* only filled when the middleware executed and the content was a proper body,
131+
* otherwise it returns `nil`.
132+
*/
133+
@inlinable
134+
var xmlRpcCall: XmlRpc.Call? {
135+
guard case .call(let call) = xmlRpcBody else { return nil }
136+
return call
137+
}
138+
}
139+
140+
141+
// MARK: - Helper
142+
143+
@usableFromInline
144+
func concatError(request : IncomingMessage,
145+
next : @escaping Next,
146+
handler : @escaping ( Buffer ) -> Swift.Error?)
147+
{
148+
var didCallNext = false
149+
150+
request | concat { bytes in
151+
guard !didCallNext else { return }
152+
if let error = handler(bytes) {
153+
next(error)
154+
}
155+
else {
156+
next()
157+
}
158+
}
159+
.onceError { error in
160+
guard !didCallNext else { return }
161+
didCallNext = true
162+
next(error)
163+
}
164+
}

0 commit comments

Comments
 (0)