A Go like concurrent system + networking/http library for Swift that works on Linux and Mac
A Go like concurrent system + networking/http libraries for Swift that works on Linux and Mac.
The reason why I started this project is because I felt it was very difficult to handle asynchronous io with Swift in the project called Slimane which I had previously made. In the Asynchronous paradigm in Swift, We need to often use the capture list well for closures and sometimes retain the object(Connection etc…) to avoid to release by ARC.
Then I thought Go’s concurrent/parallel and synchronous mecanism is suitable model for the present stage of Swift(If you want to write Server on the MultiCore Machine). Because we can easy to make async operations wituhout callback chains, can use Full Cores with the simple syntax and easy to share the memory via Channel between a Thread and a Thread.
(Prorsum is not Goroutine. It doesn’t have Corotuines and Context Switch is done on the OS side. It just has thread safe shared memory mechanism(It works on the GCD) that is heavily inspired by Go.)
Prorsum’s HTTP Server architecure is Event Driven master + Multithreading Request Handler.
In a DispatchQueue, you can write asynchronous I/O with synchronous syntax with go()
+ Channel<Element>
.
Easy to make codes solve C10K without callbacks.
+-----------------+
|-- | Request Handler |
| +-----------------+
+--------+ | +-----------------+
----- TCP ---- | master |---Dispatch Queue---|-- | Request Handler |
+--------+ | +-----------------+
| +-----------------+
|-- | Request Handler |
+-----------------+
Currenty Prorsum supports only SPM.
import PackageDescription
let package = Package(
name: "MyApp",
dependencies: [
.Package(url: "https://github.com/noppoMan/Prorsum.git", majorVersion: 0, minor: 1)
]
)
Not supported yet
Not supported yet
go
go is an alias of DispatchQueue().async { }
func asyncTask(){
print(Thread.current)
}
go(asyncTask())
go {
print(Thread.current)
}
gomain {
print(Thread.current) // back to the main thread
}
WaitGroup
A WaitGroup waits for a collection of GCD operations to finish. The main GCD operation calls Add to set the number of GCD operations to wait for. Then each of the GCD operations runs and calls Done when finished. At the same time, Wait can be used to block until all GCD operations have finished.
let wg = WaitGroup()
wg.add(1)
go {
sleep(1)
print("wg: 1")
wg.done()
}
wg.add(1)
go {
sleep(1)
print("wg: 2")
wg.done()
}
wg.wait() // block unitle twice wg.done() is called.
print("wg done")
Channel<Element>
Channels are the pipes that connect concurrent operation. You can send values into channels from one GCD operation and receive those values into another GCD operation.
let ch = Channel<String>.make(capacity: 1)
func asyncSend(){
try! ch.send("Expecto patronum!")
}
go(asyncSend()) // => Expecto patronum!
go {
try! ch.send("Accio!")
}
try! ch.receive() // => Accio!
ch.close()
select
The select statement lets a BlockOperation
wait on multiple communication operations.
let magicCh = Channel<String>.make(capacity: 1)
go {
try! magicCh.send("Obliviate")
}
select {
when(magicCh) {
print($0)
}
otherwise {
print("otherwise")
}
}
forSelect
Generally You need to wrap the select inside a while loop. To make it easier to work with this pattern You can use forSelect
. forSelect will loop until done()
is called.
let magicCh = Channel<String>.make(capacity: 1)
let doneCh = Channel<String>.make(capacity: 1)
go {
try! magicCh.send("Crucio")
try! magicCh.send("Imperio")
}
go {
try! doneCh.send("Avada Kedavra!")
}
forSelect { done in
when(magicCh) {
print($0)
}
when(doneCh) {
done() // break current loop
}
otherwise {
print("otherwise")
}
}
import Prorsum
import Foundation
let server = try! HTTPServer { (request, writer) in
do {
let response = Response(
headers: ["Server": "Prorsum Micro HTTP Server"],
body: .buffer("hello".data)
)
try writer.serialize(response)
writer.close()
} catch {
fatalError("\(error)")
}
}
try! server.bind(host: "0.0.0.0", port: 3000)
print("Server listening at 0.0.0.0:3000")
try! server.listen()
RunLoop.main.run() //start run loop
import Prorsum
let url = URL(string: "https://google.com")
let client = try! HTTPClient(url: url!)
try! client.open()
let response = try! client.request()
print(response)
// HTTP/1.1 200 OK
// Set-Cookie: NID=91=CPfJo7FsoC_HXmq7kLrs-e0DhR0lAaHcYc8GFxhazE5OXdc3uPvs22oz_UP3Bcd2mZDczDgtW80OrjC6JigVCGIhyhXSD7e1RA7rkinF3zxUNsDnAtagvs5pbZSjXuZE; expires=Sun, 04-Jun-2017 16:21:39 GMT; path=/; domain=.google.co.jp; HttpOnly
// Transfer-Encoding: chunked
// Accept-Ranges: none
// Date: Sat, 03 Dec 2016 16:21:39 GMT
// Content-Type: text/html; charset=Shift_JIS
// Expires: -1
// Alt-Svc: quic=":443"; ma=2592000; v="36,35,34"
// Cache-Control: private, max-age=0
// Server: gws
// X-XSS-Protection: 1; mode=block
// Vary: Accept-Encoding
// X-Frame-Options: SAMEORIGIN
// P3P: CP="This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info."
#if os(Linux)
import Glibc
#else
import Darwin.C
#endif
import Prorsum
import Foundation
let server = try! TCPServer { clientStream in
while !clientStream.isClosed {
let bytes = try! clientStream.read()
try! clientStream.write(bytes)
clientStream.close()
}
}
// setup client
go {
sleep(1)
let client = try! TCPSocket()
try! client.connect(host: "0.0.0.0", port: 3000)
while !client.isClosed {
try! client.write(Array("hello".utf8))
let bytes = try! client.recv()
if !bytes.isEmpty {
print(String(bytes: bytes, encoding: .utf8))
}
}
server.terminate() // terminate server
}
try! server.bind(host: "0.0.0.0", port: 3000)
try! server.listen() //start run loop
RunLoop.main.run() //start run loop
Here is a Websocket Echo Server Example.
#if os(Linux)
import Glibc
#else
import Darwin.C
#endif
import Foundation
import Prorsum
let server = try! HTTPServer { (request, writer) in
do {
let response: Response
if request.isWebSocket {
response = try request.upgradeToWebSocket { request, websocket in
websocket.onText {
print("received: \($0)")
try! websocket.send($0)
}
}
} else {
response = Response(
headers: ["Server": "Prorsum Micro HTTP Server"],
body: .buffer("hello".data)
)
}
try writer.serialize(response)
try response.upgradeConnection?(request, writer.stream)
writer.close()
} catch {
fatalError("\(error)")
}
}
try! server.bind(host: "0.0.0.0", port: 8080)
print("Server listening at 0.0.0.0:8080")
try! server.listen()
RunLoop.main.run()
Prorsum is released under the MIT license. See LICENSE for details.