hammerspoon/extensions/socket/socket.lua

251 lines
8.6 KiB
Lua

--- === hs.socket ===
---
--- Talk to custom protocols using asynchronous TCP sockets
---
--- For UDP sockets see [`hs.socket.udp`](./hs.socket.udp.html)
---
--- `hs.socket` is implemented with [CocoaAsyncSocket](https://github.com/robbiehanson/CocoaAsyncSocket). CocoaAsyncSocket's [tagging features](https://github.com/robbiehanson/CocoaAsyncSocket/wiki/Intro_GCDAsyncSocket#reading--writing) provide a handy way to implement custom protocols.
---
--- For example, you can easily implement a basic HTTP client as follows (though using [`hs.http`](./hs.http.html) is recommended for the real world):
---
--- ```lua
--- 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
--- ```
---
---
--- === hs.socket.udp ===
---
--- Talk to custom protocols using asynchronous UDP sockets
---
--- For TCP sockets see [`hs.socket`](./hs.socket.html)
---
--- You can do a lot of neat trivial and non-trivial things with these. A simple ping ponger:
--- ```lua
--- function ping(data, addr)
--- print(data)
--- addr = hs.socket.parseAddress(addr)
--- hs.timer.doAfter(1, function()
--- client:send("ping", addr.host, addr.port)
--- end)
--- end
---
--- function pong(data, addr)
--- print(data)
--- addr = hs.socket.parseAddress(addr)
--- hs.timer.doAfter(1, function()
--- server:send("pong", addr.host, addr.port)
--- end)
--- end
---
--- server = hs.socket.udp.server(9001, pong):receive()
--- client = hs.socket.udp.new(ping):send("ping", "localhost", 9001):receive()
--- ```
--- Resulting in the following endless exchange:
--- ```
--- 20:26:56 LuaSkin: (secondary thread): Data written to UDP socket
--- LuaSkin: (secondary thread): Data read from UDP socket
--- ping
--- 20:26:57 LuaSkin: (secondary thread): Data written to UDP socket
--- LuaSkin: (secondary thread): Data read from UDP socket
--- pong
--- 20:26:58 LuaSkin: (secondary thread): Data written to UDP socket
--- LuaSkin: (secondary thread): Data read from UDP socket
--- ping
--- 20:26:59 LuaSkin: (secondary thread): Data written to UDP socket
--- LuaSkin: (secondary thread): Data read from UDP socket
--- pong
--- ...
--- ```
---
--- You can do some silly things with a callback factory and enabling broadcasting:
--- ```lua
--- local function callbackMaker(name)
--- local fun = function(data, addr)
--- addr = hs.socket.parseAddress(addr)
--- print(name.." received data:\n"..data.."\nfrom host: "..addr.host.." port: "..addr.port)
--- end
--- return fun
--- end
---
--- local listeners = {}
--- local port = 9001
---
--- for i=1,3 do
--- table.insert(listeners, hs.socket.udp.new(callbackMaker("listener "..i)):reusePort():listen(port):receive())
--- end
---
--- broadcaster = hs.socket.udp.new():broadcast()
--- broadcaster:send("hello!", "255.255.255.255", port)
--- ```
--- Since neither IPv4 nor IPv6 have been disabled, the broadcast is received on both protocols ('dual-stack' IPv6 addresses shown):
--- ```
--- listener 2 received data:
--- hello!
--- from host: ::ffff:192.168.0.3 port: 53057
--- listener 1 received data:
--- hello!
--- from host: ::ffff:192.168.0.3 port: 53057
--- listener 3 received data:
--- hello!
--- from host: ::ffff:192.168.0.3 port: 53057
--- listener 1 received data:
--- hello!
--- from host: 192.168.0.3 port: 53057
--- listener 3 received data:
--- hello!
--- from host: 192.168.0.3 port: 53057
--- listener 2 received data:
--- hello!
--- from host: 192.168.0.3 port: 53057
--- ```
---
-- module implementation --------------------------------
local module = require("hs.libsocket")
module.udp = require("hs.libsocketudp")
local tcpSocketObject = hs.getObjectMetatable("hs.socket")
local udpSocketObject = hs.getObjectMetatable("hs.socket.udp")
local log=hs.luaSkinLog
module.setLogLevel=log.setLogLevel
module.getLogLevel=log.getLogLevel
--- hs.socket.timeout
--- Variable
--- Timeout for the socket operations, in seconds. New [`hs.socket`](#new) objects will be created with this timeout value, but can individually change it with the [`setTimeout`](#setTimeout) method
---
--- If the timeout value is negative, the operations will not use a timeout. The default value is -1
---
module.timeout = -1
--- hs.socket.udp.timeout
--- Variable
--- Timeout for the socket operations, in seconds. New [`hs.socket.udp`](#new) objects will be created with this timeout value, but can individually change it with the [`setTimeout`](#setTimeout) method
---
--- If the timeout value is negative, the operations will not use a timeout. The default value is -1
---
module.udp.timeout = -1
--- hs.socket.udp.parseAddress(sockaddr) -> table or nil
--- Function
--- Alias for [`hs.socket.parseAddress`](./hs.socket.html#parseAddress)
---
module.udp.parseAddress = module.parseAddress
--- hs.socket.server(port|path[, fn]) -> hs.socket object
--- Constructor
--- Creates and binds an [`hs.socket`](#new) 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](#setCallback) for reading data from the socket, settable here for convenience
---
--- Returns:
--- * An [`hs.socket`](#new) object
---
module.server = function(port, callback)
local sock = module.new(callback)
sock:listen(port)
return sock
end
--- hs.socket.udp.server(port[, fn]) -> hs.socket.udp object
--- Constructor
--- Creates and binds an [`hs.socket.udp`](#new) instance to a port for listening
---
--- Parameters:
--- * port - A port number [0-65535]. Ports [1-1023] are privileged. Port 0 allows the OS to select any available port
--- * fn - An optional [callback function](#setCallback) for reading data from the socket, settable here for convenience
---
--- Returns:
--- * An [`hs.socket.udp`](#new) object
---
module.udp.server = function(port, callback)
local sock = module.udp.new(callback)
sock:listen(port)
return sock
end
--- hs.socket:receive(delimiter[, tag]) -> self
--- Method
--- Alias for [`hs.socket:read`](#read)
---
tcpSocketObject.receive = tcpSocketObject.read
--- hs.socket:send(message[, tag]) -> self
--- Method
--- Alias for [`hs.socket:write`](#write)
---
tcpSocketObject.send = tcpSocketObject.write
--- hs.socket.udp:read(delimiter[, tag]) -> self
--- Method
--- Alias for [`hs.socket.udp:receive`](#receive)
---
udpSocketObject.read = udpSocketObject.receive
--- hs.socket.udp:readOne(delimiter[, tag]) -> self
--- Method
--- Alias for [`hs.socket.udp:receiveOne`](#receiveOne)
---
udpSocketObject.readOne = udpSocketObject.receiveOne
--- hs.socket.udp:write(message[, tag]) -> self
--- Method
--- Alias for [`hs.socket.udp:send`](#send)
---
udpSocketObject.write = udpSocketObject.send
return module