Skip to content

Instantly share code, notes, and snippets.

@stephancasas
Created March 12, 2024 16:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stephancasas/c7eb741557b018bba1d12934f09e6261 to your computer and use it in GitHub Desktop.
Save stephancasas/c7eb741557b018bba1d12934f09e6261 to your computer and use it in GitHub Desktop.
A basic HTTP server built in JXA for macOS
#!/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