-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
248 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import Glibc | ||
import Nest | ||
|
||
|
||
enum Address { | ||
case IP(address: String, port: UInt16) | ||
|
||
func socket() throws -> Socket { | ||
let socket = try Socket() | ||
try socket.bind("0.0.0.0", port: 8000) | ||
try socket.listen(20) | ||
// TODO: Set socket non blocking | ||
return socket | ||
} | ||
} | ||
|
||
|
||
/// Arbiter maintains the worker processes | ||
class Arbiter<Worker : WorkerType> { | ||
var listeners: [Socket] = [] | ||
var workers: [pid_t: Worker] = [:] | ||
|
||
let numberOfWorkers: Int | ||
let addresses: [Address] | ||
|
||
let application: RequestType -> ResponseType | ||
|
||
init(application: RequestType -> ResponseType, workers: Int, addresses: [Address]) { | ||
self.application = application | ||
self.numberOfWorkers = workers | ||
self.addresses = addresses | ||
} | ||
|
||
func createSockets() throws { | ||
for address in addresses { | ||
listeners.append(try address.socket()) | ||
print("[arbiter] Listening on port 8000") | ||
} | ||
} | ||
|
||
// Main run loop for the master process | ||
func run() throws { | ||
try createSockets() | ||
|
||
manageWorkers() | ||
|
||
while true { | ||
sleep() | ||
manageWorkers() | ||
} | ||
} | ||
|
||
func sleep() { | ||
// Wait's for stuff happening on our signal | ||
// TODO make signals for worker<>arbiter communcation | ||
Glibc.sleep(10) // Until method is implemented, don't use CPU too much | ||
} | ||
|
||
// MARK: Worker | ||
|
||
// Maintain number of workers by spawning or killing as required. | ||
func manageWorkers() { | ||
spawnWorkers() | ||
killWorkers() | ||
} | ||
|
||
// Spawn workers until we have enough | ||
func spawnWorkers() { | ||
let neededWorkers = numberOfWorkers - workers.count | ||
for _ in 0..<neededWorkers { | ||
spawnWorker() | ||
} | ||
} | ||
|
||
// Kill unused workers, oldest first | ||
func killWorkers() { | ||
// TODO | ||
} | ||
|
||
// Spawns a new worker process | ||
func spawnWorker() { | ||
let worker = Worker(listeners: listeners, application: application) | ||
|
||
let pid = fork() | ||
if pid != 0 { | ||
workers[pid] = worker | ||
print("[arbiter] Started worker process \(pid)") | ||
return | ||
} | ||
|
||
worker.run() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import Nest | ||
import Inquiline | ||
|
||
|
||
enum HTTPParserError : ErrorType { | ||
|
||
} | ||
|
||
class HTTPParser { | ||
let socket: Socket | ||
|
||
init(socket: Socket) { | ||
self.socket = socket | ||
} | ||
|
||
func parse() throws -> RequestType { | ||
// TODO build parser | ||
return Request(method: "GET", path: "/") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import Nest | ||
import Inquiline | ||
|
||
|
||
protocol WorkerType { | ||
init(listeners: [Socket], application: RequestType -> ResponseType) | ||
func run() | ||
} | ||
|
||
|
||
final class SyncronousWorker : WorkerType { | ||
let listeners: [Socket] | ||
let timeout: Double = 0.5 | ||
let application: RequestType -> ResponseType | ||
var isAlive: Bool = false | ||
|
||
init(listeners: [Socket], application: RequestType -> ResponseType) { | ||
self.listeners = listeners | ||
self.application = application | ||
} | ||
|
||
func run() { | ||
isAlive = true | ||
|
||
if listeners.count == 1 { | ||
runOne(listeners.first!) | ||
} else { | ||
runMultiple(listeners) | ||
} | ||
} | ||
|
||
func runOne(listener: Socket) { | ||
while isAlive { | ||
notify() | ||
accept(listener) | ||
wait() | ||
} | ||
} | ||
|
||
func runMultiple(listeners: [Socket]) { | ||
// TODO multiple listners | ||
fatalError("Currasow Syncronous worker cannot yet handle multiple listeners") | ||
} | ||
|
||
func notify() { | ||
// TODO communicate with arbiter | ||
} | ||
|
||
func wait() -> [Socket] { | ||
return [] | ||
} | ||
|
||
func accept(listener: Socket) { | ||
if let client = try? listener.accept() { | ||
// TODO: Set socket non blocking | ||
handle(client) | ||
} | ||
} | ||
|
||
func handle(client: Socket) { | ||
let parser = HTTPParser(socket: client) | ||
if let request = try? parser.parse() { | ||
handle(client, request: request) | ||
} | ||
|
||
client.close() | ||
} | ||
|
||
func handle(client: Socket, request: RequestType) { | ||
let response = application(request) | ||
|
||
client.send("HTTP/1.1 \(response.statusLine)\r\n") | ||
|
||
client.send("Connection: Close\r\n") | ||
var hasLength = false | ||
|
||
for (key, value) in response.headers { | ||
if key != "Connection" { | ||
client.send("\(key): \(value)\r\n") | ||
} | ||
|
||
if key == "Content-Length" { | ||
hasLength = true | ||
} | ||
} | ||
|
||
if !hasLength { | ||
if let body = response.body { | ||
// TODO body shouldn't be a string | ||
client.send("Content-Length: \(body.characters.count)\r\n") | ||
} else { | ||
client.send("Content-Length: 0\r\n") | ||
} | ||
} | ||
|
||
client.send("\r\n") | ||
|
||
if let body = response.body { | ||
client.send(body) | ||
} | ||
|
||
print("[worker] \(request.method) \(request.path) - \(response.statusLine)") | ||
} | ||
} | ||
|