Contents

noppoman/prorsum

A Go like concurrent system + networking/http libraries for Swift that works on Linux and Mac.

Features

Go like equipments
  • [x] GCD based Concurrent System
  • [x] WaitGroup
  • [x] Once
  • [x] Channels
  • [ ] Channel Iteration
  • [x] Select
  • [ ] Timers
Networking/HTTP
  • [x] DNS ipv6/v4
  • [x] TCP Server
  • [x] TCP Client
  • [x] UDP Socket
  • [ ] QUIC
  • [x] HTTP Server
  • [x] HTTP Client
  • [x] HTTPS Client
  • [ ] HTTP2.0
  • [x] WebSocket

Installation

Currenty Prorsum supports only SPM.

SPM

import PackageDescription

let package = Package(
    name: "MyApp",
    dependencies: [
        .Package(url: "https://github.com/noppoMan/Prorsum.git", majorVersion: 0, minor: 1)
    ]
)

Cocoapods

Not supported yet

Carthage

Not supported yet

Usage

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")
    }
}

Networking

HTTP Server

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

HTTP/HTTPS Client

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."

TCP

#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

Websocket

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()

License

Prorsum is released under the MIT license. See LICENSE for details.

Package Metadata

Repository: noppoman/prorsum

Default branch: master

README: README.md