All files / src tcpAdapter.coffee

32.91% Statements 26/79
0% Branches 0/2
0% Functions 0/35
32.91% Lines 26/79
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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 1821x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x   1x   1x 1x   1x               1x         1x             1x           1x                                                                                 1x                                       1x                                                         1x                                     1x         1x           1x                                 1x  
http = require 'http'
net = require 'net'
tls = require 'tls'
config = require "./config/config"
config.tcpAdapter = config.get('tcpAdapter')
logger = require "winston"
Channels = require('./model/channels')
Channel = Channels.Channel
Q = require "q"
tlsAuthentication = require "./middleware/tlsAuthentication"
authorisation = require "./middleware/authorisation"
 
tcpServers = []
 
newKey = 0
datastore = {}
 
process.on 'message', (msg) ->
  if msg.type is 'start-tcp-channel'
    logger.debug "Recieved message to start tcp channel: #{msg.channelID}"
    exports.startupTCPServer msg.channelID, ->
  else if msg.type is 'stop-tcp-channel'
    logger.debug "Recieved message to stop tcp channel: #{msg.channelID}"
    exports.stopServerForChannel msg.channelID, ->
 
exports.popTransaction = (key) ->
  res = datastore["#{key}"]
  delete datastore["#{key}"]
  return res
 
startListening = (channel, tcpServer, host, port, callback) ->
  tcpServer.listen port, host, ->
    tcpServers.push { channelID: channel._id, server: tcpServer }
    callback null
  tcpServer.on 'error', (err) ->
    logger.error err + ' Host: ' + host + ' Port: ' + port
 
exports.notifyMasterToStartTCPServer = (channelID, callback) ->
  logger.debug "Sending message to master to start tcp channel: #{channelID}"
  process.send
    type: 'start-tcp-channel'
    channelID: channelID
 
exports.startupTCPServer = (channelID, callback) ->
  for existingServer in tcpServers
    # server already running for channel
    return callback null if existingServer.channelID.equals channelID
 
  handler = (sock) ->
    Channel.findById channelID, (err, channel) ->
      return logger.error err if err
      sock.on 'data', (data) -> adaptSocketRequest channel, sock, "#{data}"
      sock.on 'error', (err) -> logger.error err
 
  Channel.findById channelID, (err, channel) ->
    host = channel.tcpHost or '0.0.0.0'
    port = channel.tcpPort
 
    return callback "Channel #{channel.name} (#{channel._id}): TCP port not defined" if not port
 
    if channel.type is 'tls'
      tlsAuthentication.getServerOptions true, (err, options) ->
        return callback err if err
 
        tcpServer = tls.createServer options, handler
        startListening channel, tcpServer, host, port, (err) ->
          if err
            callback err
          else
            logger.info "Channel #{channel.name} (#{channel._id}): TLS server listening on port #{port}"
            callback null
    else if channel.type is 'tcp'
      tcpServer = net.createServer handler
      startListening channel, tcpServer, host, port, (err) ->
        if err
          callback err
        else
          logger.info "Channel #{channel.name} (#{channel._id}): TCP server listening on port #{port}"
          callback null
    else
      return callback "Cannot handle #{channel.type} channels"
 
 
# Startup a TCP server for each TCP channel
exports.startupServers = (callback) ->
  Channel.find { $or: [ {type: 'tcp'}, {type: 'tls'} ] }, (err, channels) ->
    return callback err if err
 
    promises = []
 
    for channel in channels
      do (channel) ->
        if Channels.isChannelEnabled channel
          defer = Q.defer()
 
          exports.startupTCPServer channel._id, (err) ->
            return callback err if err
            defer.resolve()
 
          promises.push defer.promise
 
    (Q.all promises).then -> callback null
 
 
adaptSocketRequest = (channel, sock, socketData) ->
  options =
    hostname: config.tcpAdapter.httpReceiver.host
    port: config.tcpAdapter.httpReceiver.httpPort
    path: '/'
    method: 'POST'
  req = http.request options, (res) ->
    response = ''
    res.on 'data', (data) -> response += data
    res.on 'end', ->
      if sock.writable
        sock.write response
 
  req.on "error", (err) -> logger.error err
 
  # don't write the actual data to the http receiver
  # instead send a reference through (see popTransaction)
  datastore["#{newKey}"] = {}
  datastore["#{newKey}"].data = socketData
  datastore["#{newKey}"].channel = channel
  req.write "#{newKey}"
 
  newKey++
  # in case we've been running for a couple thousand years
  newKey = 0 if newKey is Number.MAX_VALUE
 
  req.end()
 
 
stopTCPServers = (servers, callback) ->
  promises = []
 
  for server in servers
    do (server) ->
      defer = Q.defer()
 
      server.server.close (err) ->
        if err
          logger.error "Could not close tcp server: #{err}"
          defer.reject err
        else
          logger.info "Channel #{server.channelID}: Stopped TCP/TLS server"
          defer.resolve()
 
      promises.push defer.promise
 
  (Q.all promises).then -> callback()
 
exports.stopServers = (callback) ->
  stopTCPServers tcpServers, ->
    tcpServers = []
    callback()
 
exports.notifyMasterToStopTCPServer = (channelID, callback) ->
  logger.debug "Sending message to master to stop tcp channel: #{channelID}"
  process.send
    type: 'stop-tcp-channel'
    channelID: channelID
 
exports.stopServerForChannel = (channelID, callback) ->
  server = null
  notStoppedTcpServers = []
  for serverDetails in tcpServers
    if serverDetails.channelID.equals channelID
      server = serverDetails
    else
      # push all except the server we're stopping
      notStoppedTcpServers.push serverDetails
 
  return callback "Server for channel #{channelID} not running" if not server
 
  tcpServers = notStoppedTcpServers
  stopTCPServers [server], callback
 
 
if process.env.NODE_ENV == "test"
  exports.tcpServers = tcpServers