openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
182 lines (143 loc) • 5.48 kB
text/coffeescript
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