Created
March 12, 2024 16:29
-
-
Save stephancasas/c7eb741557b018bba1d12934f09e6261 to your computer and use it in GitHub Desktop.
A basic HTTP server built in JXA for macOS
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
#!/usr/bin/env osascript -l JavaScript | |
ObjC.bindFunction('malloc', ['void*', ['int']]); | |
ObjC.bindFunction('memset', ['void*', ['void*', 'int', 'int']]); | |
ObjC.bindFunction('listen', ['int', ['int', 'int']]); | |
ObjC.bindFunction('socket', ['int', ['int', 'int', 'int']]); | |
ObjC.bindFunction('accept', ['int', ['int', 'void*', 'void*']]); | |
ObjC.bindFunction('bind', ['int', ['int', 'void*', 'int']]); | |
ObjC.bindFunction('htons', ['int', ['int']]); | |
ObjC.bindFunction('read', ['int', ['int', 'void*', 'int']]); | |
ObjC.bindFunction('write', ['int', ['int', 'void*', 'int']]); | |
ObjC.bindFunction('close', ['int', ['int']]); | |
function run(_) { | |
const LISTEN_PORT = 4444; // incoming port number | |
const REQUEST_BUF = 1024; // request buffer size | |
// --------------------------- Configure Socket --------------------------- // | |
// Create file descriptor from socket. | |
const listen_fd = $.socket(2, 1, 0); | |
// Convert port number to ordered byte representation for ππππππππ_ππ ππππππ. | |
const port_byte_rep = (() => { | |
const buf = new ArrayBuffer(4); | |
const uint32_rep = new Uint32Array(buf); | |
uint32_rep[0] = $.htons(LISTEN_PORT); | |
const uint8_rep = new Uint8Array(buf); | |
return [...uint8_rep]; | |
})(); | |
// Create ππππππππ_ππ ππππππ where: | |
// servaddr.sin_family = AF_INET | |
// servaddr.sin_addr.s_addr = htonl(INADDR_ANY) | |
const sockaddr_bytes = [0, 2, ...port_byte_rep, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | |
const $sockaddr = $.malloc(sockaddr_bytes.length); | |
$.memset($sockaddr, 0, sockaddr_bytes.length); | |
for (let i = 0; i < sockaddr_bytes.length; i++) { | |
$sockaddr[i] = sockaddr_bytes[i]; | |
} | |
// Bind socket address to file descriptor. | |
$.bind(listen_fd, $sockaddr, sockaddr_bytes.length); | |
// Begin listening for incoming connections. | |
$.listen(listen_fd, 10); | |
// Await/accept incoming connection. | |
const connection_fd = $.accept(listen_fd, null, null); | |
// ----------------------------- Read Request ----------------------------- // | |
// Read request from incoming connection. | |
const buf_read = $.malloc(REQUEST_BUF); | |
$.memset(buf_read, 0, REQUEST_BUF); | |
$.read(connection_fd, buf_read, REQUEST_BUF); | |
// Convert the incoming buffered byte array to a JS String. | |
const request_string = (() => { | |
let buf = ''; | |
for (let i = 0; i < REQUEST_BUF; i++) { | |
if (buf_read[i] == 0) break; | |
buf = buf.concat(String.fromCharCode(buf_read[i])); | |
} | |
return buf; | |
})(); | |
// ---------------------------- Handle Request ---------------------------- // | |
console.log(request_string); | |
// ---------------------------- Write Response ---------------------------- // | |
// Prepare response with headers and body. | |
const response_code = 200; | |
const response_disp = 'OK'; | |
const response_body = 'ok'; | |
const response_string = [ | |
`HTTP/1.1 ${response_code} ${response_disp}`, | |
'Content-Type: text/plain', | |
'Connection: close', | |
`Content-Length: ${response_body.length}`, | |
'', | |
response_body, | |
].join('\r\n'); | |
// Write response to UTF-8 character buffer. | |
const buf_write = $.malloc(response_string.length); | |
$.memset(buf_write, 0, response_string.length); | |
for (let i = 0; i < response_string.length; i++) { | |
buf_write[i] = response_string.charCodeAt(i); | |
} | |
// Write buffered response to connection. | |
$.write(connection_fd, buf_write, response_string.length); | |
// Close the connection. | |
$.close(connection_fd); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment