summaryrefslogtreecommitdiffstatshomepage
path: root/plugins/coro-net/init.lua
blob: 3ae6c2ad824bfc9ab6bb3ce9c629af3179f0be2c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
local exports = {}
exports.name = "creationix/coro-net"
exports.version = "1.1.1-1"
exports.dependencies = {
  "creationix/coro-channel@1.2.0"
}
exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-net.lua"
exports.description = "An coro style client and server helper for tcp and pipes."
exports.tags = {"coro", "tcp", "pipe", "net"}
exports.license = "MIT"
exports.author = { name = "Tim Caswell" }

local uv = require('luv')
local wrapStream = require('coro-channel').wrapStream

local function makeCallback(timeout)
  local thread = coroutine.running()
  local timer, done
  if timeout then
    timer = uv.new_timer()
    timer:start(timeout, 0, function ()
      if done then return end
      done = true
      timer:close()
      return assert(coroutine.resume(thread, nil, "timeout"))
    end)
  end
  return function (err, data)
    if done then return end
    done = true
    if timer then timer:close() end
    if err then
      return assert(coroutine.resume(thread, nil, err))
    end
    return assert(coroutine.resume(thread, data or true))
  end
end
exports.makeCallback = makeCallback

local function normalize(options)
  local t = type(options)
  if t == "string" then
    options = {path=options}
  elseif t == "number" then
    options = {port=options}
  elseif t ~= "table" then
    assert("Net options must be table, string, or number")
  end
  if options.port or options.host then
    return true,
      options.host or "127.0.0.1",
      assert(options.port, "options.port is required for tcp connections")
  elseif options.path then
    return false, options.path
  else
    error("Must set either options.path or options.port")
  end
end

function exports.connect(options)
  local socket, success, err
  local isTcp, host, port = normalize(options)
  if isTcp then
    assert(uv.getaddrinfo(host, port, {
      socktype = options.socktype or "stream",
      family = options.family or "inet",
    }, makeCallback(options.timeout)))
    local res
    res, err = coroutine.yield()
    if not res then return nil, err end
    socket = uv.new_tcp()
    socket:connect(res[1].addr, res[1].port, makeCallback(options.timeout))
  else
    socket = uv.new_pipe(false)
    socket:connect(host, makeCallback(options.timeout))
  end
  success, err = coroutine.yield()
  if not success then return nil, err end
  local read, write = wrapStream(socket)
  return read, write, socket
end

function exports.createServer(options, onConnect)
  local server
  local isTcp, host, port = normalize(options)
  if isTcp then
    server = uv.new_tcp()
    assert(server:bind(host, port))
  else
    server = uv.new_pipe(false)
    assert(server:bind(host))
  end
  assert(server:listen(256, function (err)
    assert(not err, err)
    local socket = isTcp and uv.new_tcp() or uv.new_pipe(false)
    server:accept(socket)
    coroutine.wrap(function ()
      local success, failure = xpcall(function ()
        local read, write = wrapStream(socket)
        return onConnect(read, write, socket)
      end, debug.traceback)
      if not success then
        print(failure)
      end
      if not socket:is_closing() then
        socket:close()
      end
    end)()
  end))
  return server
end

return exports