Talk to custom protocols using asynchronous TCP sockets
For UDP sockets see hs.socket.udp
hs.socket is implemented with CocoaAsyncSocket. CocoaAsyncSocket's tagging features provide a handy way to implement custom protocols.
For example, you can easily implement a basic HTTP client as follows (though using hs.http is recommended for the real world):
local TAG_HTTP_HEADER, TAG_HTTP_CONTENT = 1, 2
local body = ""
local function httpCallback(data, tag)
if tag == TAG_HTTP_HEADER then
print(tag, "TAG_HTTP_HEADER"); print(data)
local contentLength = data:match("\r\nContent%-Length: (%d+)\r\n")
client:read(tonumber(contentLength), TAG_HTTP_CONTENT)
elseif tag == TAG_HTTP_CONTENT then
print(tag, "TAG_HTTP_CONTENT"); print(data)
body = data
end
end
client = hs.socket.new(httpCallback):connect("google.com", 80)
client:write("GET /index.html HTTP/1.0\r\nHost: google.com\r\n\r\n")
client:read("\r\n\r\n", TAG_HTTP_HEADER)
Resulting in the following console output (adjust log verbosity with hs.socket.setLogLevel()) :
LuaSkin: (secondary thread): TCP socket connected
LuaSkin: (secondary thread): Data written to TCP socket
LuaSkin: (secondary thread): Data read from TCP socket
1 TAG_HTTP_HEADER
HTTP/1.0 301 Moved Permanently
Location: http://www.google.com/index.html
Content-Type: text/html; charset=UTF-8
Date: Thu, 03 Mar 2016 08:38:02 GMT
Expires: Sat, 02 Apr 2016 08:38:02 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 229
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
LuaSkin: (secondary thread): Data read from TCP socket
2 TAG_HTTP_CONTENT
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/index.html">here</A>.
</BODY></HTML>
LuaSkin: (secondary thread): TCP socket disconnected Socket closed by remote peer
| Signature |
hs.socket.timeout |
| Type |
Variable |
| Description |
Timeout for the socket operations, in seconds. New hs.socket objects will be created with this timeout value, but can individually change it with the setTimeout method |
| Signature |
hs.socket.parseAddress(sockaddr) -> table or nil |
| Type |
Function |
| Description |
Parses a binary socket address structure into a readable table |
| Parameters |
|
| Returns |
- A table describing the address with the following keys or
nil: - host - A string containing the host IP
- port - A number containing the port
- addressFamily - A number containing the address family
|
| Notes |
- Some address family definitions from
<sys/socket.h>:
|
| Signature |
hs.socket.new([fn]) -> hs.socket object |
| Type |
Constructor |
| Description |
Creates an unconnected asynchronous TCP socket object |
| Parameters |
- fn - An optional callback function for reading data from the socket, settable here for convenience
|
| Returns |
|
| Signature | hs.socket.server(port|path[, fn]) -> hs.socket object |
| -----------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| Type | Constructor |
| Description | Creates and binds an hs.socket instance to a port or path (Unix domain socket) for listening |
| Parameters |
- port - A port number [0-65535]. Ports [1-1023] are privileged. Port 0 allows the OS to select any available port
- path - A string containing the path to the Unix domain socket
- fn - An optional callback function for reading data from the socket, settable here for convenience
|
| Returns | |
| Signature | hs.socket:connect({host, port}|path[, fn]) -> self or nil |
| -----------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| Type | Method |
| Description | Connects an unconnected hs.socket instance |
| Parameters |
- host - A string containing the hostname or IP address
- port - A port number [1-65535]
- path - A string containing the path to the Unix domain socket
- fn - An optional single-use callback function to execute after establishing the connection. Receives no parameters
|
| Returns | - The
hs.socket object or nil if an error occurred
|
| Notes | - Either a host/port pair OR a Unix domain socket path must be supplied. If no port is passed, the first param is assumed to be a path to the socket file
|
| Signature |
hs.socket:connected() -> bool |
| Type |
Method |
| Description |
Returns the connection status of the hs.socket instance |
| Parameters |
|
| Returns |
true if connected, otherwise false
|
| Signature |
hs.socket:connections() -> number |
| Type |
Method |
| Description |
Returns the number of connections to the socket, which is at most 1 for default (non-listening) sockets |
| Parameters |
|
| Returns |
- The number of connections to the socket
|
| Signature |
hs.socket:disconnect() -> self |
| Type |
Method |
| Description |
Disconnects the hs.socket instance, freeing it for reuse |
| Parameters |
|
| Returns |
|
| Notes |
- If called on a listening socket with multiple connections, each client is disconnected
|
| Signature |
hs.socket:info() -> table |
| Type |
Method |
| Description |
Returns information on the hs.socket instance |
| Parameters |
|
| Returns |
- A table containing the following keys:
- connectedAddress -
string (sockaddr struct) - connectedHost -
string - connectedPort -
number - connectedURL -
string - connections -
number - isConnected -
boolean - isDisconnected -
boolean - isIPv4 -
boolean - isIPv4Enabled -
boolean - isIPv4PreferredOverIPv6 -
boolean - isIPv6 -
boolean - isIPv6Enabled -
boolean - isSecure -
boolean - localAddress -
string (sockaddr struct) - localHost -
string - localPort -
number - timeout -
number - unixSocketPath -
string - userData -
string
|
| Signature | hs.socket:listen(port|path) -> self or nil |
| -----------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| Type | Method |
| Description | Binds an unconnected hs.socket instance to a port or path (Unix domain socket) for listening |
| Parameters |
- port - A port number [0-65535]. Ports [1-1023] are privileged. Port 0 allows the OS to select any available port
- path - A string containing the path to the Unix domain socket
|
| Returns | - The
hs.socket object or nil if an error occurred
|
| Signature |
hs.socket:read(delimiter[, tag]) -> self or nil |
| Type |
Method |
| Description |
Read data from the socket. Results are passed to the callback function, which must be set to use this method |
| Parameters |
- delimiter - Either a number of bytes to read, or a string delimiter such as "\n" or "\r\n". Data is read up to and including the delimiter
- tag - An optional integer to assist with labeling reads. It is passed to the callback to assist with implementing state machines for processing complex protocols
|
| Returns |
- The
hs.socket object or nil if an error occured
|
| Notes |
- If called on a listening socket with multiple connections, data is read from each of them
|
| Signature |
hs.socket:receive(delimiter[, tag]) -> self |
| Type |
Method |
| Description |
Alias for hs.socket:read |
| Signature |
hs.socket:send(message[, tag]) -> self |
| Type |
Method |
| Description |
Alias for hs.socket:write |
| Signature |
hs.socket:setCallback([fn]) -> self |
| Type |
Method |
| Description |
Sets the read callback for the hs.socket instance. Must be set to read data from the socket |
| Parameters |
- fn - An optional callback function to process data read from the socket.
nil or no argument clears the callback
|
| Returns |
|
| Signature |
hs.socket:setTimeout(timeout) -> self |
| Type |
Method |
| Description |
Sets the timeout for the socket operations. If the timeout value is negative, the operations will not use a timeout, which is the default |
| Parameters |
- timeout - A number containing the timeout duration, in seconds
|
| Returns |
|
| Signature |
hs.socket:startTLS([verify][, peerName]) -> self |
| Type |
Method |
| Description |
Secures the socket with TLS. The socket will disconnect immediately if TLS negotiation fails |
| Parameters |
- verify - An optional boolean that, if
false, allows TLS handshaking with servers with self-signed certificates and does not evaluate the chain of trust. Defaults to true and omitted if peerName is supplied - peerName - An optional string containing the fully qualified domain name of the peer to validate against — for example,
store.apple.com. It should match the name in the X.509 certificate given by the remote party. See notes below
|
| Returns |
|
| Notes |
- * IMPORTANT SECURITY NOTE:
- The default settings will check to make sure the remote party's certificate is signed by a
- trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired.
- However it will not verify the name on the certificate unless you
- give it a name to verify against via
peerName. - The security implications of this are important to understand.
- Imagine you are attempting to create a secure connection to MySecureServer.com,
- but your socket gets directed to MaliciousServer.com because of a hacked DNS server.
- If you simply use the default settings, and MaliciousServer.com has a valid certificate,
- the default settings will not detect any problems since the certificate is valid.
- To properly secure your connection in this particular scenario you
- should set
peerName to "MySecureServer.com".
|
| Signature |
hs.socket:write(message[, tag][, fn]) -> self |
| Type |
Method |
| Description |
Write data to the socket |
| Parameters |
- message - A string containing data to be sent on the socket
- tag - An optional integer to assist with labeling writes
- fn - An optional single-use callback function to execute after writing data to the socket. Receives the tag parameter
|
| Returns |
|
| Notes |
- If called on a listening socket with multiple connections, data is broadcasted to all connected sockets
|