]> git.plutz.net Git - quickjs_net/commitdiff
refactoring: use constructor for socket object
authorPaul Hänsch <paul@plutz.net>
Thu, 19 Mar 2026 22:47:14 +0000 (23:47 +0100)
committerPaul Hänsch <paul@plutz.net>
Thu, 19 Mar 2026 22:47:14 +0000 (23:47 +0100)
socket.c

index d4bdff65681d4d7a6c07aa62e050587e98ba5d65..84cc20ba1764409eba2650fbdbf3fb1d5755dcd7 100644 (file)
--- a/socket.c
+++ b/socket.c
@@ -21,7 +21,6 @@ static JSValue js_os_fork(
 }
 
 static JSClassID socket_cid;
-static double net_timeout = -1;
 
 typedef struct {
   int fd;
@@ -65,19 +64,52 @@ static int net_addrinfo(
 }
 
 static int net_ip_listen(SocketData *so, int type) {
+  int o = 1;
   if (!so) return 1;
 
-  if ( (so->fd = socket(so->bind.ss_family, type, 0)) >= 0
+  if ( !setsockopt(so->fd, SOL_SOCKET, SO_REUSEADDR, &o, sizeof(int))
     && !bind(so->fd, (struct sockaddr *) &(so->bind), sizeof(so->bind))
-    && !sock_set_timeout(so, net_timeout)
     && (type == SOCK_DGRAM || !listen(so->fd, 1))
   ) {
     so->type = type;
     return 0;
-  } else {
-    if (so->fd >= 0) close(so->fd);
-    return 1;
-  }
+  } else return 1;
+}
+
+static int net_ip_connect(SocketData *so, int type) {
+  socklen_t ps = sizeof(struct sockaddr_storage);
+  if (!so) return 1;
+
+  if ( !connect(so->fd, (struct sockaddr *) &(so->peer), ps)
+    && !getsockname(so->fd, (struct sockaddr *) &(so->bind), &ps)
+  ){
+    so->type = type;
+    so->connected = 1;
+    return 0;
+  } else return 1;
+}
+
+static int net_unix_bind(SocketData *so, const char *path, size_t plen, int con) {
+  struct sockaddr_un * addr;
+  if (!so || plen >= sizeof(addr->sun_path)) return 1;
+
+  addr = (struct sockaddr_un *) (con ? &(so->peer) : &(so->bind));
+  addr->sun_family = AF_UNIX;
+  strncpy(addr->sun_path, path, plen);
+  so->type = SOCK_STREAM;
+
+  if ( con
+    && !connect(so->fd,(struct sockaddr *) addr ,sizeof(*addr))
+  ) {
+    so->bind.ss_family = AF_UNIX;
+    so->connected = 1;
+    return 0;
+  } else if (!con
+    && !bind(so->fd,(struct sockaddr *) addr ,sizeof(*addr))
+    && !listen(so->fd, 1)
+  ) {
+    return 0;
+  } else return 1;
 }
 
 static JSValue js_sock_set_timeout(
@@ -291,7 +323,8 @@ static JSValue js_sock_close(
 
 static void sock_destroy(JSRuntime *rt, JSValue this){
   SocketData *data = JS_GetOpaque(this, socket_cid);
-  // close(data->fd);  // do not do what a c programmer wouldn't do
+  // XXX: do not do what a c programmer wouldn't do
+  // close(data->fd);
   js_free_rt(rt, data);
 };
 
@@ -345,178 +378,135 @@ static JSValue js_sock_async(
   return promise;
 }
 
-static const JSCFunctionListEntry socket_ptype[] = {
-  JS_CFUNC_DEF("accept", 0, js_sock_accept),
-  JS_CFUNC_MAGIC_DEF("acceptAsync", 0, js_sock_async, 1),
-  JS_CFUNC_DEF("send",   1, js_sock_send),
-  JS_CFUNC_DEF("close",  0, js_sock_close),
-  JS_CFUNC_MAGIC_DEF("recv",       0, js_sock_recv, 0),
-  JS_CFUNC_MAGIC_DEF("recvString", 0, js_sock_recv, 1),
-  JS_CFUNC_MAGIC_DEF("recvAsync",       0, js_sock_async, 2),
-  JS_CFUNC_MAGIC_DEF("recvStringAsync", 0, js_sock_async, 3),
-  JS_CGETSET_MAGIC_DEF("localName", js_sock_get_addr, NULL, 0),
-  JS_CGETSET_MAGIC_DEF("localPort", js_sock_get_addr, NULL, 1),
-  JS_CGETSET_MAGIC_DEF("peerName",  js_sock_get_addr, NULL, 2),
-  JS_CGETSET_MAGIC_DEF("peerPort",  js_sock_get_addr, NULL, 3),
-  JS_CGETSET_DEF("timeout", js_sock_get_timeout, js_sock_set_timeout),
-  JS_CGETSET_DEF("fd",      js_sock_get_fd, NULL),
-  JS_CGETSET_DEF("family",  js_sock_get_family, NULL),
-  JS_CGETSET_DEF("type",    js_sock_get_type, NULL),
-  JS_CGETSET_DEF("connected",    js_sock_get_connected, NULL),
-};
-
-static JSClassDef socket_class = {
-  "Socket", .finalizer = sock_destroy
-};
-
-static JSValue js_net_ip_listen(
-  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int magic
+static JSValue js_new_socket(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
 ) {
-  int family = (magic&2) ? AF_INET6 : AF_INET;
-  int type = (magic&1) ? SOCK_STREAM : SOCK_DGRAM;
-  const char *host, *port; int gai_err = 0;
   SocketData *data = js_mallocz(ctx, sizeof(*data));
   JSValue new = JS_NewObjectClass(ctx, socket_cid);
-
-  host = JS_ToCString(ctx, argv[0]);
-  port = JS_ToCString(ctx, argv[1]);
-
-  if ( data && (data->fd = -1)
-    && !(gai_err = net_addrinfo(family, type, host, port, &(data->bind)))
-    && !net_ip_listen(data, type)
+  const char * type;
+
+  type = JS_ToCString(ctx, argv[0]);
+
+  if (data) {
+    if        ( !strcmp(type, "unix") ) {
+      data->bind.ss_family = AF_UNIX;  data->type = SOCK_STREAM;
+    } else if ( !strcmp(type, "tcp")  ) {
+      data->bind.ss_family = AF_INET;  data->type = SOCK_STREAM;
+    } else if ( !strcmp(type, "tcp6")  ) {
+      data->bind.ss_family = AF_INET6; data->type = SOCK_STREAM;
+    } else if ( !strcmp(type, "udp")  ) {
+      data->bind.ss_family = AF_INET;  data->type = SOCK_DGRAM;
+    } else if ( !strcmp(type, "udp6")  ) {
+      data->bind.ss_family = AF_INET6; data->type = SOCK_DGRAM;
+    } else {
+      js_free(ctx, data);
+      data = NULL;
+    }
+  }
+  if ( data 
+    && (data->fd = socket(data->bind.ss_family, data->type, 0)) >= 0
   ) {
     JS_SetOpaque(new, data);
   } else {
     JS_FreeValue(ctx, new);
-    if (!data) new = JS_EXCEPTION;
-    else if (!gai_err) new = libc_error(ctx);
-    else new = JS_ThrowInternalError(ctx, "%s", gai_strerror(gai_err));
-    js_free(ctx, data);
+    new = data ? libc_error(ctx) : JS_EXCEPTION;
   }
-  JS_FreeCString(ctx, host); JS_FreeCString(ctx, port);
+  JS_FreeCString(ctx, type);
 
   return new;
 }
 
-static int net_ip_connect(SocketData *so, int type) {
-  socklen_t ps = sizeof(struct sockaddr_storage);
-  if (!so) return 1;
-
-  if ( (so->fd = socket(so->peer.ss_family, type, 0)) >= 0
-    && !sock_set_timeout(so, net_timeout)
-    && !connect(so->fd, (struct sockaddr *) &(so->peer), ps)
-    && !getsockname(so->fd, (struct sockaddr *) &(so->bind), &ps)
-  ){
-    so->type = type;
-    so->connected = 1;
-    return 0;
-  } else {
-    if (so->fd >= 0) close(so->fd);
-    return 1;
-  }
-}
-
-static JSValue js_net_ip_connect(
-  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int magic
+static JSValue js_sock_listen(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
 ) {
-  int family = (magic&2) ? AF_INET6 : AF_INET;
-  int type = (magic&1) ? SOCK_STREAM : SOCK_DGRAM;
-  const char *host, *port; int gai_err = 0;
-  SocketData *data = js_mallocz(ctx, sizeof(*data));
-  JSValue new = JS_NewObjectClass(ctx, socket_cid);
+  SocketData *data = JS_GetOpaque2(ctx, this, socket_cid);
+  const char *host = NULL, *port = NULL; int gai_err = 0;
+  const size_t l_sun_path = sizeof( ((struct sockaddr_un){}).sun_path );
+  size_t plen; JSValue ret = JS_UNDEFINED;
 
-  host = JS_ToCString(ctx, argv[0]);
+  host = JS_ToCStringLen(ctx, &plen, argv[0]);
   port = JS_ToCString(ctx, argv[1]);
 
-  if ( data && (data->fd = -1)
-    && !(gai_err = net_addrinfo(family, type, host, port, &(data->peer)))
-    && !(net_ip_connect(data, type))
-  ) {
-    JS_SetOpaque(new, data);
-  } else {
-    JS_FreeValue(ctx, new);
-    if (!data) new = JS_EXCEPTION;
-    else if (!gai_err) new = libc_error(ctx);
-    else new = JS_ThrowInternalError(ctx, "%s", gai_strerror(gai_err));
-    js_free(ctx, data);
+  if ( data->bind.ss_family == AF_UNIX) {
+    if ( plen >= l_sun_path )
+      ret = JS_ThrowRangeError(
+        ctx, "pathname too long (>= %lu bytes)", l_sun_path
+      );
+    else if ( net_unix_bind(data, host, plen, 0) )
+      ret = libc_error(ctx);
+
+  } else {  // ip connection
+    if ( (gai_err = net_addrinfo(
+      data->bind.ss_family, data->type, host, port, &(data->bind)
+    )) )
+      ret = JS_ThrowInternalError(ctx, "%s", gai_strerror(gai_err));
+    else if ( net_ip_listen(data, data->type) )
+      ret = libc_error(ctx);
   }
   JS_FreeCString(ctx, host); JS_FreeCString(ctx, port);
 
-  return new;
+  if (JS_IsUndefined(ret) ) return JS_DupValue(ctx, this);
+  else return ret;
 }
 
-static int net_unix_bind(SocketData *so, const char *path, size_t plen, int con) {
-  struct sockaddr_un * addr;
-  if (!so || plen >= sizeof(addr->sun_path)) return 1;
-
-  addr = (struct sockaddr_un *) (con ? &(so->peer) : &(so->bind));
-  addr->sun_family = AF_UNIX;
-  strncpy(addr->sun_path, path, plen);
-  so->type = SOCK_STREAM;
-
-  if ( con
-    && (so->fd = socket(AF_UNIX, so->type, 0)) >= 0
-    && !sock_set_timeout(so, net_timeout)
-    && !connect(so->fd,(struct sockaddr *) addr ,sizeof(*addr))
-  ) {
-    so->bind.ss_family = AF_UNIX;
-    so->connected = 1;
-    return 0;
-  } else if (!con
-    && (so->fd = socket(AF_UNIX, so->type, 0)) >= 0
-    && !bind(so->fd,(struct sockaddr *) addr ,sizeof(*addr))
-    && !sock_set_timeout(so, net_timeout)
-    && !listen(so->fd, 1)
-  ) {
-    return 0;
-  } else {
-    if (so->fd >= 0) close(so->fd);
-    return 1;
-  }
-}
-
-static JSValue js_net_unix_bind(
-  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int c
+static JSValue js_sock_connect(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
 ) {
-  SocketData *data = js_mallocz(ctx, sizeof(*data));
-  JSValue new = JS_NewObjectClass(ctx, socket_cid);
-  const char *path; size_t plen;
+  SocketData *data = JS_GetOpaque2(ctx, this, socket_cid);
+  const char *host, *port; int gai_err = 0;
   const size_t l_sun_path = sizeof( ((struct sockaddr_un){}).sun_path );
+  size_t plen; JSValue ret = JS_UNDEFINED;
 
-  path = JS_ToCStringLen(ctx, &plen, argv[0]);
+  host = JS_ToCStringLen(ctx, &plen, argv[0]);
+  port = JS_ToCString(ctx, argv[1]);
 
-  if ( data && (plen < l_sun_path )
-    && !net_unix_bind(data, path, plen, c)
-  ) {
-    JS_SetOpaque(new, data);
-  } else {
-    JS_FreeValue(ctx, new);
-    if (!data) {
-      new = JS_EXCEPTION;
-    } else if (plen < l_sun_path) {
-      new = libc_error(ctx);
-    } else new = JS_ThrowRangeError(
-      ctx, "pathname too long (>= %lu bytes)", l_sun_path
-    );
-    js_free(ctx, data);
+  if ( data->bind.ss_family == AF_UNIX) {
+    if ( plen >= l_sun_path )
+      ret = JS_ThrowRangeError(
+        ctx, "pathname too long (>= %lu bytes)", l_sun_path
+      );
+    else if ( net_unix_bind(data, host, plen, 1) )
+      ret = libc_error(ctx);
+
+  } else {  // ip connection
+    if ( (gai_err = net_addrinfo(
+      data->bind.ss_family, data->type, host, port, &(data->peer)
+    )) )
+      ret = JS_ThrowInternalError(ctx, "%s", gai_strerror(gai_err));
+    else if ( net_ip_connect(data, data->type) )
+      ret = libc_error(ctx);
   }
-  JS_FreeCString(ctx, path);
+  JS_FreeCString(ctx, host); JS_FreeCString(ctx, port);
 
-  return new;
+  if (JS_IsUndefined(ret) ) return JS_DupValue(ctx, this);
+  else return ret;
 }
 
-static JSValue net_set_timeout(
-  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
-) {
-  if ( ! JS_ToFloat64(ctx, &net_timeout, argv[0])) return argv[0];
-  else return JS_EXCEPTION;
-}
-static JSValue net_get_timeout(
-  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
-) {
-  if (net_timeout < 0) return JS_UNDEFINED;
-  return JS_NewFloat64(ctx, net_timeout);
-}
+static const JSCFunctionListEntry socket_ptype[] = {
+  JS_CFUNC_DEF("accept", 0, js_sock_accept),
+  JS_CFUNC_DEF("send",   1, js_sock_send),
+  JS_CFUNC_DEF("close",  0, js_sock_close),
+  JS_CFUNC_DEF("listen", 1, js_sock_listen),
+  JS_CFUNC_DEF("connect", 1, js_sock_connect),
+  JS_CFUNC_MAGIC_DEF("recv",       0, js_sock_recv, 0),
+  JS_CFUNC_MAGIC_DEF("recvString", 0, js_sock_recv, 1),
+  JS_CGETSET_MAGIC_DEF("localName", js_sock_get_addr, NULL, 0),
+  JS_CGETSET_MAGIC_DEF("localPort", js_sock_get_addr, NULL, 1),
+  JS_CGETSET_MAGIC_DEF("peerName",  js_sock_get_addr, NULL, 2),
+  JS_CGETSET_MAGIC_DEF("peerPort",  js_sock_get_addr, NULL, 3),
+  JS_CGETSET_DEF("timeout", js_sock_get_timeout, js_sock_set_timeout),
+  JS_CGETSET_DEF("fd",      js_sock_get_fd, NULL),
+  JS_CGETSET_DEF("family",  js_sock_get_family, NULL),
+  JS_CGETSET_DEF("type",    js_sock_get_type, NULL),
+  JS_CGETSET_DEF("connected",    js_sock_get_connected, NULL),
+  JS_CFUNC_MAGIC_DEF("acceptAsync", 0, js_sock_async, 1),
+  JS_CFUNC_MAGIC_DEF("recvAsync",       0, js_sock_async, 2),
+  JS_CFUNC_MAGIC_DEF("recvStringAsync", 0, js_sock_async, 3),
+};
+
+static JSClassDef socket_class = {
+  "Socket", .finalizer = sock_destroy
+};
 
 static const JSCFunctionListEntry net_funcs[] = {
   JS_PROP_INT32_DEF("AF_UNIX",     AF_UNIX,     JS_PROP_CONFIGURABLE),
@@ -524,34 +514,25 @@ static const JSCFunctionListEntry net_funcs[] = {
   JS_PROP_INT32_DEF("AF_INET6",    AF_INET6,    JS_PROP_CONFIGURABLE),
   JS_PROP_INT32_DEF("SOCK_STREAM", SOCK_STREAM, JS_PROP_CONFIGURABLE),
   JS_PROP_INT32_DEF("SOCK_DGRAM",  SOCK_DGRAM,  JS_PROP_CONFIGURABLE),
-  JS_CFUNC_MAGIC_DEF("udpListen",   2, js_net_ip_listen,  0),
-  JS_CFUNC_MAGIC_DEF("tcpListen",   2, js_net_ip_listen,  1),
-  JS_CFUNC_MAGIC_DEF("udp6Listen",  2, js_net_ip_listen,  2),
-  JS_CFUNC_MAGIC_DEF("tcp6Listen",  2, js_net_ip_listen,  3),
-  JS_CFUNC_MAGIC_DEF("udpConnect",  2, js_net_ip_connect, 0),
-  JS_CFUNC_MAGIC_DEF("tcpConnect",  2, js_net_ip_connect, 1),
-  JS_CFUNC_MAGIC_DEF("udp6Connect", 2, js_net_ip_connect, 2),
-  JS_CFUNC_MAGIC_DEF("tcp6Connect", 2, js_net_ip_connect, 3),
-  JS_CFUNC_MAGIC_DEF("unixListen",  1, js_net_unix_bind,  0),
-  JS_CFUNC_MAGIC_DEF("unixConnect", 1, js_net_unix_bind,  1),
-
-  // XXX: How does one use a get/set property in a module provided object?
-  // JS_CGETSET_DEF("timeout", net_get_timeout, net_set_timeout),
-  JS_CFUNC_DEF("setTimeout", 1, net_set_timeout),
-  JS_CFUNC_DEF("getTimeout", 1, net_get_timeout),
   JS_CFUNC_DEF("fork", 0, js_os_fork),
 };
 
 static int sock_modinit(JSContext *ctx, JSModuleDef *mod ) {
   JSValue proto = JS_NewObject(ctx);
+  JSValue socket = JS_NewCFunction2(
+    ctx, js_new_socket, "Socket", 1, JS_CFUNC_constructor, 0
+  );
 
   JS_NewClassID(&socket_cid);
   JS_NewClass(JS_GetRuntime(ctx), socket_cid, &socket_class);
 
   JS_SetPropertyFunctionList(ctx, proto, socket_ptype, countof(socket_ptype));
+  JS_SetConstructor(ctx, socket, proto);
   JS_SetClassProto(ctx, socket_cid, proto);
 
-  return JS_SetModuleExportList(ctx, mod, net_funcs, countof(net_funcs));
+  JS_SetModuleExportList(ctx, mod, net_funcs, countof(net_funcs));
+  JS_SetModuleExport(ctx, mod, "Socket", socket );
+  return 0;
 }
 
 JSModuleDef *js_init_module(JSContext *ctx, const char *name) {
@@ -559,6 +540,7 @@ JSModuleDef *js_init_module(JSContext *ctx, const char *name) {
 
   if (mod) {
     JS_AddModuleExportList(ctx, mod, net_funcs, countof(net_funcs));
+    JS_AddModuleExport(ctx, mod, "Socket");
   }
 
   return mod;