Skip to content

Instantly share code, notes, and snippets.

@kavanmevada
Last active March 25, 2023 15:33
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save kavanmevada/20458c1e99f6d750bf122c3f975d0396 to your computer and use it in GitHub Desktop.
Kotlin/Native POSIX Socket Server
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