Linux と Mac で動作する Swift 用の Go のような同時システム + ネットワーキング/http ライブラリ。
このプロジェクトを始めたきっかけは、以前作ったSlimaneというプロジェクトでSwiftで非同期ioを扱うのが非常に難しいと感じたからです。 Swift の非同期パラダイムでは、クロージャにキャプチャ リストをよく使用し、ARC によるリリースを避けるためにオブジェクト (接続など) を保持する必要があることがよくあります。そこで私は、Go の並行/並列および同期メカニズムが、Swift の現段階 (マルチコア マシン上でサーバーを書きたい場合) に適したモデルであると考えました。コールバック チェーンを使用せずに非同期操作を簡単に行うことができ、シンプルな構文でフル コアを使用でき、スレッドとスレッド間でチャネルを介してメモリを共有するのも簡単であるためです。
(Prorsum は Goroutine ではありません。Corotuine はなく、コンテキスト スイッチは OS 側で行われます。Go から多大な影響を受けた、スレッド セーフな共有メモリ メカニズム (GCD 上で動作します) を備えているだけです。)
Prorsum の HTTP サーバー アーキテクチャは、イベント駆動型マスター + マルチスレッド リクエスト ハンドラーです。 DispatchQueue では、 go()
+ Channel<Element>
を使用した同期構文で非同期 I/O を作成できます。
コールバックなしで C10K を解決するコードを簡単に作成できます。
+-----------------+
|-- | Request Handler |
| +-----------------+
+--------+ | +-----------------+
----- TCP ---- | master |---Dispatch Queue---|-- | Request Handler |
+--------+ | +-----------------+
| +-----------------+
|-- | Request Handler |
+-----------------+
現在、Prorsum は SPM のみをサポートしています。
import PackageDescription
let package = Package (
name : " MyApp " ,
dependencies : [
. Package ( url : " https://github.com/noppoMan/Prorsum.git " , majorVersion : 0 , minor : 1 )
]
)
まだサポートされていません
まだサポートされていません
go
go はDispatchQueue().async { }
のエイリアスです
func asyncTask ( ) {
print ( Thread . current )
}
go ( asyncTask ( ) )
go {
print ( Thread . current )
}
gomain {
print ( Thread . current ) // back to the main thread
}
WaitGroup
WaitGroup は、GCD 操作のコレクションが完了するのを待ちます。メインの GCD 操作は、Add を呼び出して、待機する GCD 操作の数を設定します。次に、各 GCD 操作が実行され、完了すると Done が呼び出されます。同時に、Wait を使用して、すべての GCD 操作が完了するまでブロックすることができます。
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>
チャネルは、同時実行を接続するパイプです。 1 つの GCD 操作からチャネルに値を送信し、別の GCD 操作でそれらの値を受信できます。
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
select ステートメントを使用すると、 BlockOperation
複数の通信操作を待機できます。
let magicCh = Channel < String > . make ( capacity : 1 )
go {
try ! magicCh . send ( " Obliviate " )
}
select {
when ( magicCh ) {
print ( $0 )
}
otherwise {
print ( " otherwise " )
}
}
forSelect
一般に、while ループ内で選択をラップする必要があります。このパターンでの作業を容易にするために、 forSelect
使用できます。 forSelect は、 done()
が呼び出されるまでループします。
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
これは Websocket Echo サーバーの例です。
#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 は MIT ライセンスに基づいてリリースされています。詳細については、「ライセンス」を参照してください。