Skip to content

Commit 29a8f53

Browse files
committed
Add Async Socket API
1 parent f10ef29 commit 29a8f53

File tree

3 files changed

+613
-34
lines changed

3 files changed

+613
-34
lines changed

examples/http_client.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env qjs
2+
///@ts-check
3+
/// <reference path="../doc/globals.d.ts" />
4+
/// <reference path="../doc/os.d.ts" />
5+
import * as os from "os";
6+
/** @template T @param {os.Result<T>} result */
7+
function must(result) {
8+
if (typeof result === "number" && result < 0) throw result;
9+
return /** @type {T} */ (result)
10+
}
11+
12+
const sockfd = must(os.socket(os.AF_INET, os.SOCK_STREAM));
13+
await os.connect(sockfd, os.getaddrinfo("bellard.org",'80')[0]);
14+
const httpReq = ["GET / HTTP/1.0", "", ""].join('\r\n')
15+
must(await os.send(sockfd, Uint8Array.from(httpReq, c => c.charCodeAt(0)).buffer) > 0);
16+
const chunk = new Uint8Array(512);
17+
const recvd = await os.recv(sockfd, chunk.buffer);
18+
console.log([...chunk.slice(0,recvd)].map(c => String.fromCharCode(c)).join(''));

examples/http_server.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/usr/bin/env qjs
2+
///@ts-check
3+
/// <reference path="../doc/globals.d.ts" />
4+
/// <reference path="../doc/os.d.ts" />
5+
import * as os from "os";
6+
7+
const MIMES = new Map([
8+
['html', 'text/html'],
9+
['txt', 'text/plain'],
10+
['css', 'text/css'],
11+
['c', 'text/plain'],
12+
['h', 'text/plain'],
13+
['json', 'application/json'],
14+
['mjs', 'application/javascript'],
15+
['js', 'application/javascript'],
16+
['', 'application/octet-stream'],
17+
]);
18+
/** @template T @param {os.Result<T>} result */
19+
function must(result) {
20+
if (typeof result === "number" && result < 0) throw result;
21+
return /** @type {T} */ (result)
22+
}
23+
/**@param {os.FileDescriptor} fd */
24+
async function* recvLines(fd) {
25+
const chunk = new Uint8Array(1);
26+
let line = '';
27+
while (await os.recv(fd, chunk.buffer) > 0) {
28+
const char = String.fromCharCode(...chunk);
29+
if (char == '\n') {
30+
yield line;
31+
line = '';
32+
} else line += char;
33+
}
34+
if (line) yield line;
35+
}
36+
/** @param {os.FileDescriptor} fd @param {string[]} lines */
37+
function sendLines(fd, lines) {
38+
const buf = Uint8Array.from(lines.join('\r\n'), c => c.charCodeAt(0));
39+
return os.send(fd, buf.buffer);
40+
}
41+
//USAGE: qjs http_server.js [PORT=8080 [HOST=localhost]]
42+
const [port = "8080", host = "localhost"] = scriptArgs.slice(1);
43+
const [ai] = os.getaddrinfo(host, port);
44+
//if (!ai.length) throw `Unable to getaddrinfo(${host}, ${port})`;
45+
const sock_srv = must(os.socket(os.AF_INET, os.SOCK_STREAM));
46+
must(os.setsockopt(sock_srv, os.SO_REUSEADDR, new Uint32Array([1]).buffer));
47+
must(os.bind(sock_srv, ai));
48+
must(os.listen(sock_srv));
49+
//os.signal(os.SIGINT, ()=>os.close(sock_srv)); // don't work
50+
console.log(`Listening on http://${host}:${port} (${ai.addr}:${ai.port}) ...`);
51+
const openCmd = { linux: "xdg-open", darwin: "open", win32: "start" }[os.platform];
52+
if (openCmd) os.exec([openCmd, `http://${host}:${port}`]);
53+
while (true) { // TODO: break on SIG*
54+
const [sock_cli] = await os.accept(sock_srv);
55+
56+
const lines = recvLines(sock_cli);
57+
const [method, path, http_ver] = ((await lines.next()).value || '').split(' ');
58+
let safe_path = '.' + path.replaceAll(/\.+/g, '.'); // may += index.html later
59+
console.log(method, safe_path, http_ver);
60+
61+
const headers = new Map()
62+
for await (const line of lines) {
63+
const header = line.trimEnd();
64+
if (!header) break;
65+
const sepIdx = header.indexOf(': ');
66+
headers.set(header.slice(0, sepIdx), header.slice(sepIdx + 2));
67+
}
68+
69+
let [obj, err] = os.stat(safe_path);
70+
if (obj?.mode & os.S_IFDIR && safe_path.endsWith('/') && os.stat(safe_path + 'index.html')[0]) {
71+
safe_path += 'index.html';
72+
[obj, err] = os.stat(safe_path);
73+
}
74+
if (err) {
75+
await sendLines(sock_cli, ['HTTP/1.1 404', '', safe_path, 'errno:' + err])
76+
} else if (obj?.mode & os.S_IFDIR) {
77+
if (!safe_path.endsWith('/'))
78+
await sendLines(sock_cli, ['HTTP/1.1 301', `Location: ${safe_path}/`, '']);
79+
else
80+
await sendLines(sock_cli, ['HTTP/1.1 200', 'Content-Type: text/html', '',
81+
os.readdir(safe_path)[0]?.filter(e => e[0] != '.').map(e => `<li><a href="${e}">${e}</a></li>`).join('')
82+
]);
83+
} else {
84+
const mime = MIMES.get(safe_path.split('.').at(-1) || '') || MIMES.get('');
85+
await sendLines(sock_cli, ['HTTP/1.1 200', `Content-Type: ${mime}`, '', '']);
86+
const fd = must(os.open(safe_path));
87+
const fbuf = new Uint8Array(4096);
88+
for (let got = 0; (got = os.read(fd, fbuf.buffer, 0, fbuf.byteLength)) > 0;) {
89+
await os.send(sock_cli, fbuf.buffer, got);
90+
}
91+
}
92+
93+
os.close(sock_cli);
94+
}

0 commit comments

Comments
 (0)