Skip to content

Commit 90f098c

Browse files
committed
Add socket API
1 parent f10ef29 commit 90f098c

File tree

2 files changed

+293
-4
lines changed

2 files changed

+293
-4
lines changed

examples/socket.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//TODO: close socket_srv from os.signal()
2+
///@ts-check
3+
import * as os from "os";
4+
import * as std from "std";
5+
6+
7+
function must(ret) { if (ret < 0) throw ret; }
8+
const addr2uri = (addr, proto = "sock") => `${proto}://${addr.addr}:${addr.port}`
9+
10+
const [port = "8080", host = "localhost"] = scriptArgs.slice(1);
11+
12+
const sock_srv = os.socket(os.AF_INET, os.SOCK_STREAM);
13+
os.setsockopt(sock_srv,os.SO_REUSEADDR, new Uint32Array([1]).buffer);
14+
const ai = os.getaddrinfo(host, port)[0]; // { addr: "0.0.0.0", port: 8080}
15+
must(os.bind(sock_srv, ai))
16+
must(os.listen(sock_srv))
17+
console.log(`Listening on ${addr2uri(ai, "http")} ...`)
18+
19+
while (true) {
20+
const [sock_cli] = os.accept(sock_srv); // don't care about sockaddr
21+
const sock_cli_r = std.fdopen(sock_cli, "r")
22+
const sock_cli_w = std.fdopen(sock_cli, "w") // wrap with os.dup() ?
23+
24+
const [method, path, http_ver] = sock_cli_r.getline()?.trimEnd("\r").split(' ');
25+
console.log(method, path);
26+
const headers = new Map()
27+
for (; ;) {
28+
const line = sock_cli_r.getline()?.trimEnd("\r")
29+
if (!line) break
30+
const index = line.indexOf(': ')
31+
headers.set(line.slice(0, index), line.slice(index + 2))
32+
}
33+
34+
sock_cli_w.puts([
35+
'HTTP/1.1 200 OK',
36+
'Content-Type: application/json',
37+
'',
38+
JSON.stringify({
39+
method,
40+
path,
41+
http_ver,
42+
headers: Object.fromEntries(headers.entries()),
43+
scriptArgs,
44+
platform: os.platform,
45+
now: new Date(),
46+
})
47+
].join('\r\n'))
48+
sock_cli_w.close();
49+
if (path == "/quit") break;
50+
}
51+
52+
os.close(sock_srv) // ret=0 but won't prevent ALREADY IN USE error => added SO_REUSEADDR in qjs

quickjs-libc.c

Lines changed: 241 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,19 @@
4141
#include <windows.h>
4242
#include <conio.h>
4343
#include <utime.h>
44+
#include <winsock2.h>
4445
#else
4546
#include <dlfcn.h>
4647
#include <termios.h>
4748
#include <sys/ioctl.h>
4849
#include <sys/wait.h>
50+
#include <sys/types.h>
51+
#include <sys/socket.h>
52+
#include <netinet/in.h>
53+
#include <netinet/ip.h>
54+
#include <arpa/inet.h>
55+
#define __USE_XOPEN2K // for addrinfo
56+
#include <netdb.h>
4957

5058
#if defined(__FreeBSD__)
5159
extern char **environ;
@@ -80,10 +88,6 @@ typedef sig_t sighandler_t;
8088
#define PATH_MAX 4096
8189
#endif
8290

83-
/* TODO:
84-
- add socket calls
85-
*/
86-
8791
typedef struct {
8892
struct list_head link;
8993
int fd;
@@ -1671,6 +1675,11 @@ static int js_std_init(JSContext *ctx, JSModuleDef *m)
16711675
{
16721676
JSValue proto;
16731677

1678+
#if defined(_WIN32)
1679+
WSADATA wsa_data;
1680+
WSAStartup(0x0202, &wsa_data);
1681+
#endif
1682+
16741683
/* FILE class */
16751684
/* the class ID is created once */
16761685
JS_NewClassID(&js_std_file_class_id);
@@ -3401,6 +3410,220 @@ static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val,
34013410

34023411
#endif /* !_WIN32 */
34033412

3413+
static int JS_toSockaddrStruct(JSContext *ctx, JSValue addr,
3414+
struct sockaddr_in *sockaddr)
3415+
{
3416+
JSValue val;
3417+
const char *addr_str;
3418+
uint32_t port, family;
3419+
int ret;
3420+
3421+
val = JS_GetPropertyStr(ctx, addr, "family");
3422+
if (JS_IsException(val) || JS_IsUndefined(val) ||
3423+
JS_ToUint32(ctx, &family, val)) {
3424+
sockaddr->sin_family = AF_INET;
3425+
} else {
3426+
sockaddr->sin_family = family;
3427+
}
3428+
JS_FreeValue(ctx, val);
3429+
3430+
val = JS_GetPropertyStr(ctx, addr, "addr");
3431+
addr_str = JS_ToCString(ctx, val);
3432+
ret = inet_pton(AF_INET, addr_str, &(sockaddr->sin_addr.s_addr));
3433+
JS_FreeCString(ctx, addr_str);
3434+
JS_FreeValue(ctx, val);
3435+
if (ret != 1)
3436+
return -1;
3437+
3438+
val = JS_GetPropertyStr(ctx, addr, "port");
3439+
ret = JS_ToUint32(ctx, &port, val);
3440+
JS_FreeValue(ctx, val);
3441+
if(ret)
3442+
return -1;
3443+
sockaddr->sin_port = htons(port);
3444+
return 0;
3445+
}
3446+
3447+
static JSValue js_toSockaddrObj(JSContext *ctx, struct sockaddr_in *sockaddr)
3448+
{
3449+
JSValue obj, prop;
3450+
char ip_str[INET_ADDRSTRLEN];
3451+
const char *ip_ptr;
3452+
3453+
obj = JS_NewObject(ctx);
3454+
if (JS_IsException(obj))
3455+
goto fail;
3456+
3457+
prop = JS_NewUint32(ctx, sockaddr->sin_family);
3458+
JS_DefinePropertyValueStr(ctx, obj, "family", prop, JS_PROP_C_W_E);
3459+
3460+
prop = JS_NewUint32(ctx, ntohs(sockaddr->sin_port));
3461+
JS_DefinePropertyValueStr(ctx, obj, "port", prop, JS_PROP_C_W_E);
3462+
3463+
ip_ptr = inet_ntop(AF_INET, &sockaddr->sin_addr, ip_str, sizeof(ip_str));
3464+
prop = ip_ptr ? JS_NewString(ctx, ip_ptr) : JS_NULL;
3465+
JS_DefinePropertyValueStr(ctx, obj, "addr", prop, JS_PROP_C_W_E);
3466+
return obj;
3467+
3468+
fail:
3469+
JS_FreeValue(ctx, obj);
3470+
return JS_EXCEPTION;
3471+
}
3472+
3473+
static JSValue js_os_socket(JSContext *ctx, JSValueConst this_val,
3474+
int argc, JSValueConst *argv)
3475+
{
3476+
int domain, type, protocol = 0, ret;
3477+
3478+
if (JS_ToInt32(ctx, &domain, argv[0]))
3479+
return JS_EXCEPTION;
3480+
if (JS_ToInt32(ctx, &type, argv[1]))
3481+
return JS_EXCEPTION;
3482+
if (argc >= 3 && JS_ToInt32(ctx, &protocol, argv[2]))
3483+
return JS_EXCEPTION;
3484+
ret = js_get_errno(socket(domain, type, protocol));
3485+
return JS_NewInt32(ctx, ret);
3486+
}
3487+
3488+
static JSValue js_os_setsockopt(JSContext *ctx, JSValueConst this_val,
3489+
int argc, JSValueConst *argv)
3490+
{
3491+
int sock, optname, ret;
3492+
uint8_t *opt;
3493+
size_t size;
3494+
3495+
if (JS_ToInt32(ctx, &sock, argv[0]))
3496+
return JS_EXCEPTION;
3497+
if (JS_ToInt32(ctx, &optname, argv[1]))
3498+
return JS_EXCEPTION;
3499+
opt = JS_GetArrayBuffer(ctx, &size, argv[2]);
3500+
if (!opt)
3501+
return JS_EXCEPTION;
3502+
ret = js_get_errno(setsockopt(sock, SOL_SOCKET, optname, &opt, size));
3503+
return JS_NewInt32(ctx, ret);
3504+
}
3505+
3506+
static JSValue js_os_getaddrinfo(JSContext *ctx, JSValueConst this_val,
3507+
int argc, JSValueConst *argv)
3508+
{
3509+
int ret;
3510+
socklen_t len, objLen;
3511+
JSValue obj, addrObj;
3512+
const char* node = NULL;
3513+
const char* service = NULL;
3514+
struct addrinfo *ai,*it;
3515+
3516+
if (!JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0]))
3517+
node = JS_ToCString(ctx, argv[0]);
3518+
service = JS_ToCString(ctx, argv[1]);
3519+
if (!service)
3520+
goto fail;
3521+
3522+
ret = js_get_errno(getaddrinfo(node, service, NULL, &ai));
3523+
if(ret)
3524+
goto fail;
3525+
3526+
obj = JS_NewArray(ctx);
3527+
for (objLen = 0, it = ai; it; it = it->ai_next) {
3528+
for (len = 0; len < it->ai_addrlen; len++) {
3529+
addrObj = js_toSockaddrObj(ctx,(struct sockaddr_in *)&it->ai_addr[len]);
3530+
JS_SetPropertyUint32(ctx,obj,objLen++,addrObj);
3531+
}
3532+
}
3533+
3534+
freeaddrinfo(ai);
3535+
return obj;
3536+
fail:
3537+
JS_FreeValue(ctx, obj);
3538+
JS_FreeCString(ctx, service);
3539+
JS_FreeCString(ctx, node);
3540+
return JS_EXCEPTION;
3541+
}
3542+
3543+
static JSValue js_os_bind(JSContext *ctx, JSValueConst this_val,
3544+
int argc, JSValueConst *argv)
3545+
{
3546+
int sockfd, ret;
3547+
struct sockaddr_in saddr;
3548+
3549+
if (JS_ToInt32(ctx, &sockfd, argv[0]))
3550+
return JS_EXCEPTION;
3551+
if (JS_toSockaddrStruct(ctx, argv[1], &saddr))
3552+
return JS_EXCEPTION;
3553+
ret = js_get_errno(bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)));
3554+
return JS_NewInt32(ctx, ret);
3555+
}
3556+
3557+
static JSValue js_os_listen(JSContext *ctx, JSValueConst this_val,
3558+
int argc, JSValueConst *argv)
3559+
{
3560+
int sockfd, backlog = 10, ret;
3561+
3562+
if (JS_ToInt32(ctx, &sockfd, argv[0]))
3563+
return JS_EXCEPTION;
3564+
if (argc >= 2 && JS_ToInt32(ctx, &backlog, argv[1]))
3565+
return JS_EXCEPTION;
3566+
3567+
ret = js_get_errno(listen(sockfd, backlog));
3568+
return JS_NewInt32(ctx, ret);
3569+
}
3570+
3571+
static JSValue js_os_accept(JSContext *ctx, JSValueConst this_val,
3572+
int argc, JSValueConst *argv)
3573+
{
3574+
JSValue arr, sockaddr_obj;
3575+
int sockfd, ret;
3576+
struct sockaddr_in client_addr;
3577+
socklen_t addr_len = sizeof(client_addr);
3578+
3579+
if (JS_ToInt32(ctx, &sockfd, argv[0]))
3580+
return JS_EXCEPTION;
3581+
ret = js_get_errno(accept(sockfd, (struct sockaddr *)&client_addr, &addr_len));
3582+
3583+
// TODO: refactor into a JS_NewSockObj
3584+
sockaddr_obj = JS_NewObject(ctx);
3585+
if (JS_IsException(sockaddr_obj))
3586+
return sockaddr_obj;
3587+
JS_DefinePropertyValue(ctx, sockaddr_obj, JS_NewAtom(ctx, "addr"),
3588+
JS_NewInt32(ctx, ntohl(client_addr.sin_addr.s_addr)), JS_PROP_C_W_E);
3589+
JS_DefinePropertyValue(ctx, sockaddr_obj, JS_NewAtom(ctx, "port"),
3590+
JS_NewInt32(ctx, ntohs(client_addr.sin_port)), JS_PROP_C_W_E);
3591+
3592+
arr = JS_NewArray(ctx);
3593+
if (JS_IsException(arr))
3594+
return arr;
3595+
3596+
JS_DefinePropertyValueUint32(ctx, arr, 0, JS_NewInt32(ctx, ret), JS_PROP_C_W_E);
3597+
JS_DefinePropertyValueUint32(ctx, arr, 1, sockaddr_obj, JS_PROP_C_W_E);
3598+
return arr;
3599+
}
3600+
static JSValue js_os_connect(JSContext *ctx, JSValueConst this_val,
3601+
int argc, JSValueConst *argv)
3602+
{
3603+
int sockfd, ret;
3604+
unsigned u32;
3605+
struct sockaddr_in client_addr;
3606+
socklen_t client_len = sizeof(client_addr);
3607+
JSValue val;
3608+
3609+
if (JS_ToInt32(ctx, &sockfd, argv[0]))
3610+
return JS_EXCEPTION;
3611+
// TODO: refactor into a JS_GetSockObj
3612+
val = JS_GetPropertyStr(ctx, argv[1], "addr");
3613+
ret = JS_ToUint32(ctx, &u32, val);
3614+
JS_FreeValue(ctx, val);
3615+
client_addr.sin_addr.s_addr = htonl(u32);
3616+
3617+
val = JS_GetPropertyStr(ctx, argv[1], "port");
3618+
ret = JS_ToUint32(ctx, &u32, val);
3619+
JS_FreeValue(ctx, val);
3620+
client_addr.sin_port = htons(u32);
3621+
3622+
ret = js_get_errno(accept(sockfd, (struct sockaddr *)&client_addr, &client_len));
3623+
3624+
return JS_NewInt32(ctx, ret);
3625+
}
3626+
34043627
#ifdef USE_WORKER
34053628

34063629
/* Worker */
@@ -3936,6 +4159,20 @@ static const JSCFunctionListEntry js_os_funcs[] = {
39364159
JS_CFUNC_DEF("dup", 1, js_os_dup ),
39374160
JS_CFUNC_DEF("dup2", 2, js_os_dup2 ),
39384161
#endif
4162+
/* SOCKET I/O */
4163+
JS_CFUNC_DEF("socket", 2, js_os_socket ),
4164+
JS_CFUNC_DEF("setsockopt", 3, js_os_setsockopt ),
4165+
JS_CFUNC_DEF("getaddrinfo", 2, js_os_getaddrinfo ),
4166+
JS_CFUNC_DEF("bind", 2, js_os_bind ),
4167+
JS_CFUNC_DEF("listen", 1, js_os_listen ),
4168+
JS_CFUNC_DEF("accept", 1, js_os_accept ),
4169+
JS_CFUNC_DEF("connect", 1, js_os_connect ),
4170+
OS_FLAG(AF_INET),
4171+
OS_FLAG(AF_INET6),
4172+
OS_FLAG(SOCK_STREAM),
4173+
OS_FLAG(SOCK_DGRAM),
4174+
OS_FLAG(SOCK_RAW),
4175+
OS_FLAG(SO_REUSEADDR),
39394176
};
39404177

39414178
static int js_os_init(JSContext *ctx, JSModuleDef *m)

0 commit comments

Comments
 (0)