Skip to content

Instantly share code, notes, and snippets.

@bolasblack
Last active August 29, 2015 14:08
Show Gist options
  • Save bolasblack/fd638e6d5716c9768bf9 to your computer and use it in GitHub Desktop.
Save bolasblack/fd638e6d5716c9768bf9 to your computer and use it in GitHub Desktop.
wrap web service with websocket
node_modules
express = require 'express'
http = require 'http'
httpMocks = require 'node-mocks-http'
httpStatus = require 'statuses'
app = express()
server = http.createServer app
io = require('socket.io') server
io.on 'connection', (socket) ->
console.log 'io connected'
socket.on 'request', (data) ->
console.log 'request event', data
{callbackId, info} = data
req = httpMocks.createRequest(
url: info.url
method: info.method
headers: info.headers
body: info.body
)
res = httpMocks.createResponse()
# https://github.com/strongloop/express/blob/661435256384165bb656cb7b6046b4138ca24c9e/lib/express.js#L28
app.handle req, res, ->
socket.emit 'response', {
callbackId: callbackId
info:
textStatus: httpStatus[res.statusCode]
status: res.statusCode
headers: res.headers
body: res._getData()
}
socket.on 'disconnect', ->
console.log 'io disconnected'
app.use express.static __dirname + '/'
app.post '/hello', (req, res, next) ->
console.log 'server get post /hello', req.body
res.status(201).send a: 1
next()
server.listen 3000, ->
console.log 'socket.io server listening at 3000'
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<body>
<script src="/socket.io/socket.io.js"></script>
<script>
(function() {
var socket = io()
socket.on('connect', function() {
console.log('io connected')
socket.emit('request', {
callbackId: 0,
info: {
url: '/hello',
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({test: true})
}
})
socket.on('response', function(data) {
console.log('response event', data.info)
})
})
})()
</script>
</body>
</html>
{
"name": "test-ws",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"express": "`npm bin`/coffee express.coffee",
"restify": "`npm bin`/coffee restify.coffee"
},
"author": "",
"license": "ISC",
"dependencies": {
"coffee-script": "^1.8.0",
"express": "^4.10.1",
"lodash": "^2.4.1",
"node-mocks-http": "^1.2.1",
"restify": "2.8.2",
"socket.io": "^1.2.0",
"statuses": "^1.2.0"
}
}
restify = require 'restify'
_ = require 'lodash'
http = require 'http'
httpMocks = require 'node-mocks-http'
httpStatus = require 'statuses'
server = restify.createServer()
io = require('socket.io') server.server
io.on 'connection', (socket) ->
socket.on 'request', (request) ->
{info, callbackId} = request
# https://github.com/mcavage/node-restify/blob/885f1b95cf97a144ad10778d9dfa00fdfca6878c/lib/request.js#L109
# Restify read header in downcase
headers = _(info.headers).reduce (lowerHeader, value, key) ->
lowerHeader[key.toLowerCase()] = value
lowerHeader
, {}
req = httpMocks.createRequest(
url: info.url
# Restify read method in upcase
method: info.method.toUpperCase()
headers: headers
body: info.body
)
req.__proto__ = http.IncomingMessage.prototype
req.pause = ->
req.resume = ->
# https://github.com/mcavage/node-restify/commit/bcdbda4b97d6d4621b502c3bc1dc36e10c70fee8#L55
# We didn't need bodyReader, you can complete it if you need
req.isChunked = -> req.contentType() in ['multipart/form-data', 'application/octet-stream']
req.getContentLength = -> 0
res = httpMocks.createResponse()
afterEventHandler = (inputReq, inputRes) ->
return if inputReq isnt req
server.removeListener 'after', afterEventHandler
statusCode = if res.statusCode >= 100 then res.statusCode else 200
socket.emit 'response', {
callbackId: callbackId
info:
status: statusCode
textStatus: httpStatus[statusCode]
headers: res.headers
body: res._getData()
}
server.on 'after', afterEventHandler
# https://github.com/mcavage/node-restify/blob/3e38cec398841c64473ce534b2372aeb8a786868/lib/server.js#L250
server.emit 'request', req, res
server._setupRequest req, res
server._handle req, res
socket.on 'disconnect', ->
console.log 'io disconnected'
server.get '/', restify.serveStatic(
directory: '.'
default: 'index.html'
)
server.post '/hello', (req, res, next) ->
console.log 'server get post /hello', req.body
res.send 201, a: 1
next()
server.listen 3000, ->
console.log 'socket.io server listening at 3000'
angular.module('ng')
.config(['$provide', ($provide) ->
wsURLRE = /^\$websocket\/?/
globalCallbackId = 0
isSocketReq = (url) ->
wsURLRE.test url
fakeResponseHeader = (headerObj) ->
_.map headerObj, (value, key) ->
"#{key}: #{value}"
.join '\n'
$provide.decorator('$httpBackend', [
'$delegate', '$q', '$window', '$log', 'API_HOST'
(httpBackend, $q , $window , $log , API_HOST) ->
socket = $window.io API_HOST.replace wsURLRE, ''
waitSocketReady = (cb) ->
(args...) ->
return cb.apply(this, args) if socket.connected
socket.once 'connect', => cb.apply(this, args)
serializeUrl = (url) ->
return url if /^https?:/.test url
return url if /^\//.test url
"/#{url}"
socketBackend = waitSocketReady (method, url, post, callback, headers, timeout, withCredentials, responseType) ->
callbackId = globalCallbackId
globalCallbackId++
reqData = {
callbackId: callbackId
info:
url: serializeUrl url.replace wsURLRE, ''
method: method
headers: headers
body: post
}
respReceiver = (resp) ->
return unless resp.callbackId is callbackId
$log.debug "[Socket]", "Request:", reqData.info, "Response:", resp.info
socket.off 'response', respReceiver
{status, textStatus, body, headers} = resp.info
callback status, JSON.stringify(body), fakeResponseHeader(headers), textStatus
socket.on 'response', respReceiver
socket.emit 'request', reqData
(method, url) ->
# if url start with "$websocket", switch to websocket backend
backend = if isSocketReq(url) then socketBackend else httpBackend
backend.apply this, arguments
])
])
@sarnobat
Copy link

sarnobat commented Jun 3, 2015

Could you describe in a bit more detail what happens in one cycle? I'm unsure whether the case I'm working on is analogous to this or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment