Implement built-in node:net impl - #2217
Conversation
298ff8f to
a2a10e5
Compare
220d9b3 to
e035eb0
Compare
This comment was marked as resolved.
This comment was marked as resolved.
Implements the connect handler and tcp-ingress for a worker See the samples/tcp-ingress for an example
155b5b7 to
5370184
Compare
|
This PR can be reviewed. Landing is blocked on the pending code review of #1429 |
dom96
left a comment
There was a problem hiding this comment.
This is tough to review, but I think great testing coverage is what is likeliest to find bugs anyway. You're planning to give this great test coverage so we should be good on that front.
I think the structure of the implementation is good, so approving.
| "nodejs_compat_v2", | ||
| # The nodejs_compat_net flag explicitly enables the node:net module. | ||
| # The nodejs_compat or nodejs_compat_v2 flags must also be set. | ||
| "nodejs_compat_net", |
There was a problem hiding this comment.
Sucks a bit that we need to have a separate compat flag for this. Is there any way we could avoid this and have this new net implementation be part of nodejs_compat_v2?
There was a problem hiding this comment.
This is part of the strategy to more seamlessly integrate with wrangler. The idea is to use the compat flags as a way of signaling to wrangler that a new node.js compat API is available. In this case it also helps us gate the availability based on use of the experimental flag.
cc @IgorMinar
|
Note that this PR is still blocked pending review of #1429 |
Implement a subset of the
node:netandnode:tlsmodules supportingnet.Socket,tls.TLSSocket,net.BlockList, andnet.SocketAddress. Thenet.Serverandtls.TLSServertypes are explicitly unsupported and won't be implemented.Update: The key next step on this is implementing tests... Folks should feel free to start performing code review on the main piece. If folks would like a walkthrough to help with the code review, let me know
Very rough todo list:
net.connect(...)creates the Socket, normalizes the arguments, and forwards the args tosocket.connect(...)socket.connect(...)validates the input arguments, initiates the underlying socket connectionnew Socket([options])worksoptions.allowHalfOpenworks and passes through to the underlyingSocketoptions.fdthrows an error if setoptions.readableis ignored sinceoptions.fdis unsupportedoptions.writableis ignored sinceoptions.fdin unsupportedoptions.signalsets anAbortSignalthat destroys thenet.Socketwhen triggeredSettingWill implement this separately as it's not critical for immediate compat in the short termoptions.handleto an existing standardSocketinstance should wrap that socketconnectandreadyevents are emitted when the connection is openedcloseevent is emitted when destroy is calledcloseeventhadErrorargument is true when destroy is called with an errorerrorevent is emitted when destroy is called with an errorconnectionAttemptevent is emitted when initializing a connectionconnectionAttemptFailedevent is emitted when initializing a connection failsconnectionAttemptTimeoutevent will not be emitted since the family autoselection feature is not implementedaddressTypeargument in theconnectAttemptandconnectionAttmptFailedevents is the correct typeend()closes the connectiondrainevent is emitted when the write buffer is emptyend()is called, buffered data in the readable side is still avaialbleoptions.lookupis provided and destination address is not an IP address, theoptions.lookupfunction should be called to "resolve" the address. (optional... we could just as easily choose not to supportoptions.lookup)lookupevent is emitted after theoptions.lookupis called and returns a resultnet.Socketto be destroyed with an errornet.Socketcan have areadevent attached putting the stream in flowing modereadevent is attached while the connection is still pending, thereadwill automatically start when the connection is established.dataevent is emitted when a chunk of data has been readendevent is emitted when the underlying stream readable side is doneallowHalfOpenis false, this should close and destroy the socket ONLY once the write buffer is drained. IfallowHalfOpenis true, it should not.socket.setTimeout(...)sets an activity timer on the socket.timeoutevent is emitted when the activity timeout expires.socket.address()returns the local address if the socket is connected, undefined otherwise... we hard code this to0.0.0.0:0since we have no notion of a local bound address in workerssocket.autoSelectFamilyAttemptedAddressesis always an array, when connect is called, it always just contains the one address we're connecting to. This is generally non-op since we do not implement family autoselectionsocket.bufferSizereturns the number of bytes bufferedsocket.bytesReadreturns the number of bytes readsocket.bytesWrittenreturns the number of bytes writtensocket.connect(options[, connectListener])worksoptions.autoSelectFamilyonly accepts a falsy value. truthy values throwoptions.autoSelectFamilyAttemptTimeoutis validated to be a number but is otherwise ignoredoptions.familyis verified to match thehostspecifiedoptions.hintsis ignored unlessoptions.lookupis given, then it is passed to that function when calledoptions.hostis the address to connect to. Defaults tolocalhost.options.keepAliveonly falsy values are accepted. Truthy values throwoptions.keepAliveInitialDelayis validated to be a number but is otherwise ignoredoptions.localAddressis ignoredoptions.localPortis ignoredoptions.lookupis used if thehostis not a valid IP addressoptions.noDelayfalsy values are accepted. Truthy values throwoptions.portis validated to be a proper portoptions.paththrowsoptions.onreadis used if specifiedsocket.connect(path[, connectListener])throwssocket.connect(port[, host[, connectListener])workssocket.connectingis true while the connection is being established. Is false otherwisesocket.destroy(error)immediately destroys the stream and closes the connectionCurrently not planning to implement this for now. We can do this in a separate PR.socket.connect(...)can be called again immediately aftersocket.destroy(...)socket.destroyedis true after callingsocket.destroy()socket.destroySoon()destroys the socket after all data is written. If thefinishevent already emitted, the socket is destroyed immediately. If the socket is still writable,end()is called.socket.end(data[, encoding[, callback]])writes the given chun and ends the writable size and half-closes the socketsocket.localAddressalways returns0.0.0.0socket.localPortalways returns0socket.localFamilyalways returns0socket.pause()pauses reading of data on the stream. Pauses the underlying read loopsocket.pendingistrueif the socket is not connected yet at allsocket.ref()andsocket.unref()are non-opsocket.remoteAddressis the remote hostsocket.remoteFamilyis the remote host IP typesocket.remotePortis the remote portsocket.resetAndDestroy()closes the connection and destroys the stream.socket.resume()resumes reading after a call to pausesocket.setEncoding(...)sets the text encoding for the readable side of the socketsocket.setKeepAlive()accepts falsy values, throws on truthy valuessocket.setNoDelay()accepts falsy values, throws on truthy valuessocket.readyStateaccurately reflects the ready status of the connectionSocketAPI within workerd is fairly limited currentlyFuture work items (to come in separate follow-up PRs
tls.TLSSocketis implementednet.BlockListis implementednet.SocketAddressis implemented