Created
June 9, 2014 23:26
-
-
Save brendanberg/eb10bda0d24d01606d4c to your computer and use it in GitHub Desktop.
This file contains hidden or 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
println("Hello") | |
var conn = Socket().connect(8000, address: "127.0.0.1").listen(limit:128).accept() { connection, address in | |
println("Accepted a connection from port \(address.addressString)") | |
return connection | |
}.read() { connection, inStr in | |
let arr = inStr.componentsSeparatedByString("\n") | |
let response = (arr[0].uppercaseString + "\n") | |
return connection.write(response) | |
}.close() | |
switch conn { | |
case .Error(let str): | |
println("ERROR: \(str)") | |
case .Descriptor: | |
println("Goodbye") | |
} |
This file contains hidden or 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
// Created by Brendan Berg on 6/4/14. | |
// Copyright (c) 2014 NegativeZero. All rights reserved. | |
import Darwin | |
/** | |
Representation of a connected socket or error. | |
Either a Descriptor with an associated value for the POSIX | |
socket file descriptor or an Error with a string describing | |
the error. | |
*/ | |
enum Connection { | |
case Descriptor(CInt) | |
case Error(String) | |
} | |
/** | |
Representation of a socket or error. | |
Either a Descriptor with an associated value for the POSIX | |
socket file descriptor or an Error with a string describing | |
the error. | |
*/ | |
enum Socket { | |
case Descriptor(CInt) | |
case Error(String) | |
/** | |
Creates a new, unbound POSIX socket and encapsulates the file descriptor. | |
*/ | |
init() { | |
let newSocket = Darwin.socket(AF_INET, SOCK_STREAM, 0) | |
if newSocket < 0 { | |
self = .Error(String.fromCString(strerror(errno))) | |
} else { | |
self = .Descriptor(newSocket) | |
} | |
} | |
/** | |
Binds a socket to the specified port, listening on the interface specified by `address`. | |
*/ | |
func connect(port: CUnsignedShort, address: CString = "127.0.0.1", fn: Socket -> Socket = { $0 }) -> Socket { | |
switch self { | |
case let .Descriptor(sock): | |
let addr = inet_addr(address) | |
if addr == __uint32_t.max { | |
return .Error("unable to parse address") | |
} | |
var server_addr = sockaddr() | |
server_addr.sa_family = sa_family_t(AF_INET) | |
server_addr.sin_port = port | |
server_addr.sin_addr = addr | |
let err = bind(sock, &server_addr, socklen_t(sizeof(sockaddr))) | |
if err != 0 { | |
return .Error(String.fromCString(strerror(errno))) | |
} | |
return fn(self) | |
case .Error: | |
return self | |
} | |
} | |
/** | |
Listens for connections on the socket, pulling from a queue with | |
a maximum length specified by `limit`. Per the man page, `limit` | |
is silently limited to 128. | |
*/ | |
func listen(limit: CInt = 128, fn: Socket -> Socket = { $0 }) -> Socket { | |
switch self { | |
case let .Descriptor(sock): | |
let success = Darwin.listen(sock, limit) | |
if success != 0 { | |
return .Error(String.fromCString(strerror(errno))) | |
} else { | |
return self | |
} | |
case .Error: | |
return self | |
} | |
} | |
/** | |
Accepts a connection on a socket. Turns a Socket into a Connection. | |
*/ | |
func accept(fn: (Connection, sockaddr) -> Connection = { c, _ in c }) -> Connection { | |
switch self { | |
case .Descriptor(let sock): | |
var address = sockaddr() | |
var length = socklen_t(sizeof(sockaddr)) | |
let newSocket = Darwin.accept(sock, &address, &length) | |
if newSocket < 0 { | |
return .Error(String.fromCString(strerror(errno))) | |
} else { | |
return fn(.Descriptor(newSocket), address) | |
} | |
case .Error(let str): | |
return .Error(str) // self | |
} | |
} | |
/** | |
Closes a connection. | |
*/ | |
func close(fn: Socket -> Socket = { $0 }) -> Socket { | |
switch self { | |
case let .Descriptor(sock): | |
if Darwin.close(sock) != 0 { | |
return .Error(String.fromCString(strerror(errno))) | |
} else { | |
return fn(self) | |
} | |
case .Error: | |
return self | |
} | |
} | |
} | |
extension Socket: LogicValue { | |
/** | |
Gets the logical value of the socket. | |
Returns `true` if the Socket represents a valid descriptor | |
and `false` if the Socket represents an error. | |
*/ | |
func getLogicValue() -> Bool { | |
switch self { | |
case .Descriptor: | |
return true | |
case .Error: | |
return false | |
} | |
} | |
} | |
// Connection Extensions | |
// --------------------- | |
// Methods on the Connection enum are here because of the forward declaration | |
// required for the Socket implementation. | |
extension Connection { | |
/** | |
Reads data from the connection. The data read from the connection is passed as | |
the second parameter to the success function. | |
The `success` parameter is a function to be called upon successful reading. | |
Returns a `Connection` monad | |
*/ | |
func read(fn: (Connection, String) -> Connection = { c, _ in c }) -> Connection { | |
// Use this to quickly zero out memory if we reuse the same buffer per read | |
// memset(&array, CInt(sizeof(CChar)) * CInt(array.count), 0) | |
// | |
// This is less Swiftish but might be faster... | |
// | |
// var buff = calloc(UInt(sizeof(CChar)), 256) | |
// var n = read(conn, buff, 255) | |
// var charBuf = UnsafePointer<CChar>(buff) | |
// charBuf[n] = 0 | |
// var s: String = String.fromCString(charBuf) | |
// println(s) | |
switch self { | |
case .Descriptor(let sock): | |
var buffer = new CChar[256] | |
let bytesRead = Darwin.read(sock, &buffer, UInt(buffer.count)) | |
if bytesRead < 0 { | |
return .Error(String.fromCString(strerror(errno))) | |
} else { | |
buffer[bytesRead] = 0 | |
return fn(self, buffer.withUnsafePointerToElements { String.fromCString($0) }) | |
} | |
case .Error: | |
return self | |
} | |
} | |
/** | |
Writes the contents of a string to the connection. | |
The `response` parameter is the string to be written; `success` | |
is the function to be called upon successful writing. | |
Returns a `Connection` monad. | |
*/ | |
func write(response: String, fn: Connection -> Connection = { $0 }) -> Connection { | |
switch self { | |
case .Descriptor(let sock): | |
let bytesOut = UInt8[](response.utf8) | |
let bytesWritten = Darwin.write(sock, bytesOut, UInt(bytesOut.count)) | |
if bytesWritten < 0 { | |
return .Error(String.fromCString(strerror(errno))) | |
} else { | |
return fn(self) | |
} | |
case .Error: | |
return self | |
} | |
} | |
/** | |
Closes the connection. | |
The `success` parameter is a function to be called upon successful closure of the connection. | |
Returns A `Connection` monad. | |
*/ | |
func close(fn: Connection -> Connection = { $0 }) -> Connection { | |
switch self { | |
case let .Descriptor(sock): | |
if Darwin.close(sock) != 0 { | |
return .Error(String.fromCString(strerror(errno))) | |
} else { | |
return fn(self) | |
} | |
case .Error: | |
return self | |
} | |
} | |
} | |
extension Connection: LogicValue { | |
func getLogicValue() -> Bool { | |
/** | |
Gets the logical value of the connection. | |
Returns `true` if the Connection represents a valid descriptor, | |
and `false` if the Connection represents an error | |
*/ | |
switch self { | |
case .Descriptor: | |
return true | |
case .Error: | |
return false | |
} | |
} | |
} | |
// C sockaddr struct Extension | |
// --------------------------- | |
// The Swift type checker doesn't allow us to use sockaddr and sockaddr_in | |
// interchangably, so the following extension destructures port and address | |
// types and sets the appropriate bytes in sa_data to use with the socket | |
// system calls. | |
extension sockaddr { | |
init () { | |
sa_len = 0 | |
sa_family = 0 | |
sa_data = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | |
} | |
var sin_port: in_port_t { | |
/*! Gets the socket's port number by restructuring bytes in the sa_data field. | |
* \returns The socket's port number as a 16-bit unsigned integer | |
*/ | |
get { | |
// TODO: Make sure this is done in a machine-architecture indepenent way. | |
return (UInt16(sa_data.1.asUnsigned()) << 8) + UInt16(sa_data.0.asUnsigned()) | |
} | |
/*! Sets the socket's port number by destructuring the first two bytes of the | |
* sa_data field. | |
* \param newValue The port number as a 16-bit unsigned integer | |
*/ | |
set { | |
// TODO: Make sure this is done in a machine-architecture indepenent way. | |
sa_data.0 = CChar((newValue & 0xFF00) >> 8) | |
sa_data.1 = CChar((newValue & 0x00FF) >> 0) | |
} | |
} | |
var sin_addr: in_addr_t { | |
get { | |
return ( | |
// Restructures bytes 3 through 6 of sa_data into a 32-bit unsigned | |
// integer IPv4 address | |
// TODO: This should probably go through ntohs() first. | |
in_addr_t(sa_data.2) >> 00 + in_addr_t(sa_data.3) >> 08 + | |
in_addr_t(sa_data.4) >> 16 + in_addr_t(sa_data.5) >> 24 | |
) | |
} | |
set { | |
// Destructures a 32-bit IPv4 address to set as bytes 3 through 6 of sa_data | |
// TODO: This should probably go through htons() first. | |
sa_data.2 = CChar((newValue & 0x000000FF) >> 00) | |
sa_data.3 = CChar((newValue & 0x0000FF00) >> 08) | |
sa_data.4 = CChar((newValue & 0x00FF0000) >> 16) | |
sa_data.5 = CChar((newValue & 0xFF000000) >> 24) | |
} | |
} | |
/** | |
The human-readable, dotted quad string representation of the socket's IPv4 address. | |
*/ | |
var addressString: String { | |
let data = self.sa_data | |
return "\(data.2).\(data.3).\(data.4).\(data.5)" | |
} | |
} |
@trogdoro:
Did you have look at https://github.com/eaigner/SwiftHTTP?
I did a quick, un-experienced update to Swift 2. It mostly works, just sometimes it seems to lag (AppleTV4, tvOS), there is a noticeable delay in response - not sure yet where this comes from.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Any chance you have an updated version of this, that works with the latest swift version? Would love to have a working version of a simple socket server in swift - seems to be nearly impossible to find...