-
-
Save jnorthrup/055ea9acfe5deda56eeb4b55de723346 to your computer and use it in GitHub Desktop.
Kotlin/Native POSIX Socket Server
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
package sample | |
import kotlinx.cinterop.* | |
import platform.linux.inet_ntoa | |
import platform.posix.* | |
/** | |
* | |
*/ | |
data class Socket(val fd: Int, val addr: sockaddr_in) | |
class ServerSocket(val port: Int, val backlog: Int = 50) { | |
private var socketFd = create() | |
private val socketAddr = socketAddressV4() | |
init { socketAddr.bind(socketFd) } | |
fun socketAddressV4() : sockaddr_in { | |
return memScoped { alloc<sockaddr_in>().apply{ | |
sin_family = AF_INET.convert() // Protocol Family | |
sin_port = htons(port.convert()) // Port number | |
sin_addr.s_addr = INADDR_ANY // AutoFill local address | |
} } | |
} | |
fun create() = socket(AF_INET, SOCK_STREAM, 0).also { | |
if (it == -1) error { println("ERROR: Failed to obtain Socket Descriptor. (errno = $errno)") } | |
else println("[Server] Socket created sucessfully.") | |
} | |
fun Int.listen() = listen(this, backlog).also { | |
if (it == -1) error { println("ERROR: Failed to listen Port. (errno = $errno)") } | |
else println("[Server] Listening the port $port successfully.") | |
} | |
fun sockaddr_in.bind(fd: Int): Int { | |
return bind(fd, this.ptr.reinterpret(), sockaddr_in.size.convert()).also { | |
if (it == -1) error { println("ERROR: Failed to bind Port. (errno = $errno)") } | |
else { | |
println("[Server] Binded tcp port $port in addr 127.0.0.1 sucessfully.") | |
fd.listen() // Listen on port | |
} | |
} | |
} | |
/** | |
* Wait a connection, and obtain a new socket file despriptor for single connection | |
* @return Client's Socket FD | |
*/ | |
fun accept() = Socket(accept(socketFd, null, null).also { | |
if (it == -1) error { println("ERROR: Obtaining new Socket Despcritor. (errno = $errno)") } | |
else println("[Server] Server has got connected from ${socketAddr.getConnectedAddress()}.") | |
}, socketAddr) | |
fun sockaddr_in.getConnectedAddress() = inet_ntoa(sin_addr.readValue())?.toKString() | |
fun error(f: () -> Unit) = run { f(); exit(1) } | |
} | |
/** | |
* IO Extensions | |
*/ | |
fun Socket.readRaw(nchuck: Long, func: (CPointer<ByteVar>) -> Unit) { | |
var end = 0 | |
var len: Long | |
memScoped { | |
do { | |
val revbuf = allocArray<ByteVar>(nchuck) | |
len = read(fd, revbuf, nchuck.convert()) | |
func(revbuf) | |
for (i in 0 until nchuck) { | |
if (revbuf[i] == 13.toByte() || revbuf[i] == 10.toByte()) end++ | |
else end = 0 // Reset counter | |
} | |
} while(len>0 && end!=4) | |
} | |
} | |
fun Socket.request(array: HashMap<String, String>,nchuck: Int = 1024, fn: (HashMap<String, String>, CPointer<ByteVar>) -> Unit) { | |
var end = 0 | |
var strKey = "" | |
var strValue = "" | |
var isKey = true | |
var isFirstLine = true | |
var skipVal = true | |
var header = true | |
var bodyLength = 0L | |
var remainBody = 0L | |
var firstLine = "" | |
memScoped { | |
loop@do { | |
val revbuf = allocArray<ByteVar>(nchuck) | |
val len = read(fd, revbuf, nchuck.convert()) | |
val bodybuf = allocArray<ByteVar>(nchuck) | |
if (!header) { | |
if (remainBody>0) { | |
fn(array, revbuf) | |
remainBody-=len | |
} else break@loop | |
} else { | |
var j = 0 | |
for (i in 0 until nchuck) { | |
if (revbuf[i] == 13.toByte() || revbuf[i] == 10.toByte()) end++ | |
else end = 0 // Reset counter | |
if (isFirstLine) firstLine += revbuf[i].toChar() | |
if (end == 3) { | |
firstLine.split(" ").let { | |
array["Request-Type"] = it[0] | |
array["Request-Path"] = it[1] | |
array["Request-Version"] = it[2] | |
} | |
} | |
if (header) { | |
if (revbuf[i] != 10.toByte()) { | |
if (!isFirstLine && revbuf[i] != 13.toByte()) { | |
if (revbuf[i] == ':'.toByte()) isKey = false | |
if (isKey) strKey += revbuf[i].toChar() | |
else if (!isKey && revbuf[i] != ':'.toByte()) { | |
if (!skipVal) strValue += revbuf[i].toChar() | |
if (revbuf[i] == ' '.toByte()) skipVal = false | |
} | |
} | |
} else { | |
if (!strKey.isEmpty() && !strValue.isEmpty()) { | |
array[strKey] = strValue | |
} | |
if (strKey == "Content-Length") { | |
bodyLength = strValue.toLong() | |
remainBody = bodyLength | |
} | |
isFirstLine = false | |
isKey = true | |
skipVal = true | |
strKey = "" | |
strValue = "" | |
} | |
} else { | |
bodybuf[j] = revbuf[i] | |
j++ | |
remainBody-- | |
} | |
if (end==4) header = false | |
} | |
if (!header && remainBody>0) { | |
remainBody -= read(fd, bodybuf.plus(bodyLength-remainBody), (nchuck-(bodyLength-remainBody)).convert()) | |
fn(array, bodybuf) | |
if (remainBody<=0) break@loop | |
} | |
} | |
} while (len > 0) | |
} | |
} | |
fun Socket.request(nchuck: Int) { | |
val array = arrayListOf<String>() | |
var line = "" | |
var end = 0 | |
var isheader = true | |
var content_size = 0L | |
memScoped { | |
loop@ do { | |
val revbuf = allocArray<ByteVar>(nchuck) | |
val len = read(fd, revbuf, nchuck.convert()) | |
val bodybuf = allocArray<ByteVar>(nchuck) | |
var j = 0L | |
if (isheader) { | |
for (i in 0 until len) { | |
if (revbuf[i] == 13.toByte() || revbuf[i] == 10.toByte()) end++ | |
else end = 0 // Reset counter | |
if (isheader) { | |
if (revbuf[i] != '\r'.toByte() && revbuf[i] != '\n'.toByte()) line += revbuf[i].toChar() | |
if (end == 2){ | |
array.add(line) | |
line = "" | |
} | |
} else { | |
bodybuf[j] = revbuf[i] | |
j++ | |
content_size-- | |
} | |
if (end==4) isheader = false | |
} | |
if (j>0) { | |
read(fd, bodybuf.plus(j), (nchuck-j).convert()) | |
println(bodybuf.toKString()) | |
} | |
} else { | |
println(revbuf.toKString()) | |
} | |
if (!isheader && content_size<=0) break@loop | |
} while (len>0) | |
} | |
println(array) | |
} | |
fun Socket.readHeader(): ResponseType { | |
val tmpHash = hashMapOf<String, String>() | |
var end = 0 | |
var isKey = false | |
var tmpKey = "" | |
var tmpValue = "" | |
var skip = false | |
var isHeader = false | |
var rtype = "" | |
memScoped { | |
loop@ do { | |
val len = alloc<ByteVar>().let { | |
read(fd, it.ptr, 1.convert()).apply { | |
it.value.toChar().let { | |
if (it == '\n') { | |
isKey = true | |
tmpKey = "" | |
} | |
else if (it == ':' && isKey) { | |
isKey = false | |
skip = true | |
tmpValue = "" | |
} | |
else if (it == '\r') { | |
if (tmpKey!="") tmpHash[tmpKey] = tmpValue | |
isHeader = true | |
} | |
else { | |
if (isKey) tmpKey+=it | |
else if (!skip) tmpValue+=it | |
if (it == ' ') skip=false | |
} | |
if (!isHeader) rtype += it | |
if (it == '\r' || it == '\n') end++ | |
else end = 0 // Reset counter | |
} | |
} | |
} | |
} while (len>0 && end<4) | |
return rtype.split(' ').let { | |
ResponseType(fd, it[0], it[1], it[2], tmpHash) | |
} | |
} | |
} | |
fun ResponseType.recieve(nchuck: Long, fn: (CArrayPointer<ByteVar>) -> Unit) { | |
var byteRemain = contentSize | |
memScoped { | |
loop@ do { | |
val len = allocArray<ByteVar>(nchuck).let { | |
byteRemain-=nchuck | |
read(fd, it, nchuck.convert()).apply { fn(it) } | |
} | |
if (byteRemain<=0) break@loop | |
} while (len > 0) | |
} | |
} | |
class ResponseType(fd: Int, type: String, path: String, version: String, pameters: HashMap<String, String>){ | |
enum class Type { | |
GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH, UNKNOWN | |
} | |
val fd = fd | |
val contentSize = pameters["Content-Length"]?.toLong() ?: 0L | |
var type = when (type) { | |
"GET" -> Type.GET | |
"POST" -> Type.POST | |
"PUT" -> Type.PUT | |
"DELETE" -> Type.DELETE | |
"OPTIONS" -> Type.OPTIONS | |
"HEAD" -> Type.HEAD | |
"PATCH" -> Type.PATCH | |
else -> Type.UNKNOWN | |
} | |
var path = path | |
var pameters = pameters | |
} | |
fun ByteArray.findBuff(len: Int, find: String): Boolean { | |
var matched = false | |
for (i in 0 until len) { | |
if (this[i] == find[0].toByte()){ | |
matched = true | |
for (j in 1 until find.length) { | |
if (find[j].toByte() != this[j+i]) { | |
matched = false | |
break | |
} | |
} | |
} | |
} | |
return matched | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment