NaviServer Built-in Commands – 5.1.0
ns_connchan - Manage connection channels.
The command ns_connchan allows one to detach the current channel from a connection thread and manage the connection outside the initiating connection thread. It allows one to write or read to the channel, to define callbacks and to list open connections and to close the connection. The read and write operations on this channel will use directly the driver infrastructure which was in use during the detach command.
The command allows e.g. to read from and to write to all network drivers (such as plain HTTP channels and from SSL/TLS connections). It can be used to implement e.g. WebSockets or asynchronous deliveries (e.g. h264 streams) including secure connections. Therefore, this command is more general than the approaches based on ns_conn channel using plain Tcl channels.
NaviServer maintains an internal table per server to keep track of the detached connection channels and to offer introspection to the state of the detached channels.
Sets or queries the debugging level of the current connection. When set to "1", debug messages are written to the system log file; setting it to "2" causes a separate debug file to be created. Currently, this command primarily affects send operations, although its functionality may be expanded in future releases.
Detaches the current connection channel from the active connection thread and stores it under a fresh handle name in a per-virtual-server private table. The command returns the created handle as result.
When this command is called from a request handler, NaviServer has already received the full client request, including the request body.
This command is primarily intended for cases where longer-running request processing should be performed in the background without blocking a connection thread. Background processing can be done, for example, via ns_job.
It can also be used for spooling data to a client, where the duration of the request depends on the client (e.g., slow readers). When the primary concern is protection against slow-read attacks, the use of writer threads is typically more appropriate.
After this command has been issued in a connection thread, direct access to the original connection socket via connection-thread commands such as ns_write will fail. Further processing of the detached connection must use the returned handle and the ns_connchan subcommands.
Closes the named connection channel. The -server can be used for cleanup of stale handles.
Returns 1 if the named connection channel exists, 0 otherwise.
Returns a list of the currently detached connection channels for the current or named server.
Every list entry contains
name of the channel
name of the thread
start time of the initiating request,
driver,
the IP address of the client,
sent bytes,
received bytes,
the client data as provided via [ns_conn clientdata],
the cmd name of the callback, or "" when no callback is registered,
the callback condition flags, or "" when no callback is registered.
When NaviServer is running in reverse proxy mode, the client IP address is taken from the value as provided by the reverse proxy server.
Registers a Tcl callback for the named connection channel. -timeout is the poll timeout (the maximum time between two events), -receivetimeout is a timeout for incoming packets, -sendtimeout is the timeout for outgoing packets. When -sendtimeout has the value of 0, a read operation might return the empty string. A value larger than 0 might block the event processing for the specified time. All timeouts values are specified in the form secs?:microseconds?, or secs.fraction or as a number with a time unit.
The argument when consist of one or more characters of r, w, e, or x, specifying, when the callback should fire.
When the callback is fired, the specified Tcl commands will be called with an additional argument, which is an indicator for the reason of the call when. The value of when will be as follows:
r - the socket is readable
w - the socket is writable
e - the socket has an exceptional condition
x - the server is shutting down (not related to peer connection closure)
t - timeout received
When the callback exits, its return value determines, whether the callback should be canceled or not. The return value is interpreted as follows:
0 - the callback is canceled, and the channel is deleted automatically (typically, when an error occurs)
1 - the callback will be used as well for further events
2 - the callback will be suspended. No further events will be fired, but the channel is not deleted.
When the callback returns a value greater than or equal to 1, the channel remains open. A return value of 1 keeps the callback registered for future events, while a return value of 2 suspends the callback without deleting the channel.
When the peer closes its side of the connection, the channel typically becomes readable and ns_connchan read returns the empty string. This condition can be used to detect end-of-file (EOF) or peer connection closure.
Opens a client connection channel to the specified host and port. The command is similar to ns_connchan open, but does less: While ns_connchan open sends an HTTP request to the server denoted by a URL, ns_connchan connect just connects to the server without sending any request data.
The option -tls will connection via TLS, otherwise the connection is via plain socket. -timeout specifies the timeout for establishing the connection (default 1 second). The timeout value time can be specified in the form secs?:microseconds?, or secs.fraction, or as a number with a time unit.
Opens a listening socket. Invokes the script callback for each incoming connection. On success, this command returns a dict containing channel, port, sock and address.
When port is specified as 0, the operating system selects an ephemeral local port automatically. The selected port is returned in the result dictionary under the key port.
The callback script is invoked for each incoming connection and is passed the connection channel as argument.
The return value of the callback controls the lifetime of the connection. When the callback returns a true value, the connection remains open and can be managed via ns_connchan commands. When the callback returns a false value, the connection is closed immediately.
When the connection is kept open, the accept callback will typically register an event handler via ns_connchan callback to process incoming or outgoing data.
# Listen only on the loopback interface (localhost)
set info [ns_connchan listen -bind 127.0.0.1 9000 {
# handle incoming connection
}]
# Connections are accepted only from local clients
# Without -bind: address is only validated, not enforced
set info [ns_connchan listen 127.0.0.1 9000 {
# handle incoming connection
}]
# The listening socket may accept connections on all interfaces
# (depending on system defaults, e.g. 0.0.0.0)
# Allocate an ephemeral port bound to a specific interface
set info [ns_connchan listen -bind 192.168.1.10 0 {
# handle incoming connection
}]
# The returned dict contains the kernel-assigned port
set port [dict get $info port]
Opens a client connection channel to the specified url. The URL can either be an HTTP or an HTTPS URL.
Note that the network driver for the used protocol specified in the url must be loaded. For details, see CONFIGURATION.
The options can be any of:
Reads from the specified connection channel and returns the received data.
When the option -websocket is used, then the command expects WebSocket frames. In this mode, the result of the command is a Tcl dict containing the following elements:
fin status bit, frame state (incomplete or complete), unprocessed (received number of bytes in buffer not handled so far), fragments (number of bytes in the WebSocket fragments buffer), haveData (boolean value to express that unprocessed data might be sufficient for the next frame without an extra read operation.
In case the frame is finished (fin status bit is set), the dict contains as well the WebSocket opcode and the payload of the frame.
Returns status information from the current or named server. The command returns a dict containing the following elements:
driver (module name the driver), fragments (for WebSocket read operations: number of bytes in the WebSocket fragments buffer), framebuffer (for WebSocket read operations: number of bytes in the WebSocket frame buffer), peer (communication peer, the IP address of the other side of the channel), received (number of bytes received), sendbuffer number of bytes in the send buffer, sent (number of bytes sent), and start (time when the connection started). When a callback is defined for this channel, the dict contains as well the callback and the condition on which the callback will be fired.
Returns a binary websocket frame based on the provided information in the form of a Tcl byte array. When -mask is specified, the data will be masked with random information. For the opcode binary, the input is treated as binary as well. When -binary is used, the data will be treated as binary. This is e.g. necessary on multi-segment messages, where later segments have the opcode continue.
Writes the specified message to the given connection channel. The command returns the number of bytes actually sent, which may be less than the length of the provided message.
In cases of partial write operations, any unsent data is automatically queued in the connection's send buffer. This queued data is merged with any new data during the subsequent write operations. To check the current status of the send buffer, use the ns_conn status command. If you need to drain the buffer completely, repeatedly call ns_conn write with an empty message until the buffer is empty.
The following example demonstrates how to detach an incoming HTTP request from the connection thread and process it asynchronously in the background. The request handler returns immediately after calling ns_connchan detach, while the actual work is performed later (e.g., via ns_job). This pattern avoids blocking connection threads for longer-running tasks and allows the application to send the HTTP response at a later time via the detached connection channel.
After detaching, the application is responsible for sending a complete HTTP response (including status line and headers) and for closing the connection explicitly.
# Example: detach request and process it in the background
# Log request details
set identifier "[ns_conn request] [ns_set format [ns_conn headers]]"
ns_log notice "REQUEST START $identifier"
# Ensure a job queue exists
if {"q1" ni [ns_job queues]} { ns_job create q1 }
# Detach the connection channel from the connection thread
set channel [ns_connchan detach]
# Process the request asynchronously
ns_job queue -detached q1 [subst {
ns_log notice "start detached job"
# Simulate a long-running task
ns_sleep 2s
# Send minimal HTTP response and close connection
ns_connchan write $channel "HTTP/1.0 200 OK\r\n\r\n"
ns_connchan close $channel
ns_log notice "DONE detached job"
}]
ns_log notice "REQUEST DONE $identifier"
The log output below shows that the request handler completes before the background job starts and finishes, demonstrating that the connection thread is not blocked.
REQUEST START ... REQUEST DONE ... start detached job ... (delay) DONE detached job
The next example shows a two-stage use of ns_connchan listen and ns_connchan callback. The callback provided to ns_connchan listen is invoked when a new connection is accepted and must return a true value to keep the connection open. The accepted channel is then typically passed to ns_connchan callback for event-driven processing. In such a callback, peer connection closure is commonly detected when the channel becomes readable and ns_connchan read returns the empty string.
package require nx
namespace eval connchanserver {
#
# Server class: manages the listening socket and accepts connections
#
nx::Class create Server {
# Default settings:
# - bind to IPv6 loopback and ephemeral port.
# - The OS assigned port is in the logentry (listening on ...)
:property {host ::1}
:property {port 0}
#
# Start listening for incoming connections
#
:public method start-listen {} {
# Create a listening socket via ns_connchan.
# -bind ensures that the socket is bound exactly to :host/:port.
# The callback is invoked for every new incoming connection.
set d [ns_connchan listen -bind ${:host} ${:port} \
[list [self] accept-handler]]
# On success, ns_connchan listen returns a dict with details
if {[dict exists $d channel]} {
# Extract returned values (channel, port, address, sock)
dict with d {
ns_log notice "Listening on address $address port $port"
# Remember the listening channel (needed to close later)
set :listen $channel
}
} else {
# Failure (e.g., port already in use or bind not possible)
ns_log warning "Failed to listen on host ${:host} port ${:port}"
}
}
#
# Callback invoked for every accepted connection
#
# channel: connection channel for the accepted client socket
#
:public method accept-handler {channel} {
ns_log notice "incoming data on channel $channel"
# Create a new Connection object to handle this client
Connection new -channel $channel
# Return 1 to accept the connection and keep it open
# Returning 0 would close the connection immediately
return 1
}
}
#
# Connection class: handles a single client connection
#
nx::Class create Connection {
# Channel associated with this connection
:property {channel}
#
# Constructor: called after object creation
#
:method init {} {
ns_log notice "[current class] starting with channel ${:channel}"
# Register a callback for this connection channel
ns_connchan callback ${:channel} \
[list [self] receive] rex
}
#
# Callback invoked when data is available or connection state changes
#
# condition: one of r/e/x (readable, exceptional condition, server shutdown)
#
:public method receive {condition} {
# Read available data from the channel
# ns_connchan read returns raw bytes
set bytes [encoding convertfrom utf-8 \
[ns_connchan read ${:channel}]]
# Determine length of received data
set rlen [string length $bytes]
ns_log notice "[current class] received $rlen bytes on ${:channel} cond $condition\n$bytes"
# Return value controls whether the callback remains active:
# 0 -> close channel and stop callbacks
# 1 -> keep channel open and continue receiving callbacks
# 2 -> keep channel open but suspend the current callback
#
# Here: close when no data was received (e.g., EOF)
if {$rlen == 0} {
#
# destroy the Connection object
#
:destroy
return 0
} else {
return 1
}
}
}
#
# Create a server instance bound to IPv4 loopback
#
Server create s1 -host 127.0.0.1 -port 9000
}
#
# Register startup hook to begin listening when NaviServer starts
#
ns_atstartup {
# Start listening on configured host/port
connchanserver::s1 start-listen
}
The ns_connchan command uses the network driver configuration of NaviServer. Therefore, the used network driver for the protocol must by loaded via the NaviServer configuration file. When e.g. an outgoing HTTPS request should be made, but the site administrator does not want to listen on an HTTPS port, specify in the configuration of HTTPS the port with value 0.
The default values for the peer server validation parameters -cafile, -capath, and -insecure can be configured in the configuration file as documented in ns_http. The configuration of the client certificate management is as well shared with ns_http. For configuration details, see the section HTTP client security configuration in the manual.