Skip to content

Commit

Permalink
feat: update engine.io to capture and forward sticky session header
Browse files Browse the repository at this point in the history
  • Loading branch information
hyperlink committed Apr 7, 2017
1 parent 188fb2b commit fcc4543
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
17 changes: 17 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
language: node_js
cache:
directories:
- node_modules
notifications:
email: false
node_js:
- '7'
- '6'
- '4'
before_script:
- npm prune
after_success:
- npm run semantic-release
branches:
except:
- /^v\d+\.\d+\.\d+$/
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,31 @@
# socket.io-sticky-headers

Use custom header to maintain sticky sessions with socket.io

## Problem

For cluster of socket.io servers Socket.io connections made by the client requires a handshake process that involves sending several XHR requests. If any of those requests fail to connect to the same instance the handshake will fail and socket.io will be in a reconnect loop (failing with 401 errors).

Sticky sessions are need to successfully connect to a cluster of socket.io servers. Usually this can be managed with a Cookie. However browsers such as Safari has very strict third party cookie rules which could prevent this from working. An example could be a chat client placed on a customers site.

This module patches the engine.io XHR polling to capture a custom http header response of your choosing. Any future requests made will use the same http header. Future requests will update the header if that header ever changed.

## Install

```bash
npm install socket.io-sticky-headers --save
```

## Usage

### Socket.io

```javascript
require('socket.io-sticky-headers')(require('socket.io-client/node_modules/engine.io-client/lib/transports/polling-xhr'), 'My-Session-Id');
```

### Engine.io

```javascript
require('socket.io-sticky-headers')(require('engine.io-client/lib/transports/polling-xhr'), 'My-Session-Id');
```
59 changes: 59 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict'

const debug = require('debug')('engine.io-sticky-headers')

let initialized = false

function initialize (XHR, stickyHeader) {
if (initialized) return

if (stickyHeader == null) {
stickyHeader = 'Session-Id'
}

if (XHR == null || typeof XHR !== 'function') {
throw new Error('Please provide XHR function constructor ie require("engine.io-client/lib/transports/polling-xhr"')
}

const Request = XHR.Request

const originalXHRRequest = XHR.prototype.request
if (originalXHRRequest == null) {
throw new Error('XHR.prototype.request is not set')
}

const originalOnLoad = Request.prototype.onLoad

if (originalOnLoad == null) {
throw new Error('Request.prototype.onLoad is not set')
}

Request.prototype.onLoad = function () {
this.emit('onLoad')
originalOnLoad.call(this)
}

XHR.prototype.request = function (opts) {
let request = originalXHRRequest.call(this, opts)
request.once('onLoad', createStickyUpdater(this))
return request
}

// Update sticky header after every successful request
function createStickyUpdater (xhr) {
return function updateSticky () {
let newStickyValue = this.xhr.getResponseHeader(stickyHeader)
if (newStickyValue != null) {
debug('setting header %s to %s', stickyHeader, newStickyValue)
if (xhr.extraHeaders == null) {
xhr.extraHeaders = {}
}
xhr.extraHeaders[stickyHeader] = newStickyValue
}
}
}

initialized = true
}

module.exports = initialize
40 changes: 40 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "socket.io-sticky-headers",
"version": "0.0.0-development",
"description": "Use custom header to maintain sticky sessions with socket.io",
"main": "index.js",
"scripts": {
"test": "standard && mocha",
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
},
"repository": {
"type": "git",
"url": "https://github.com/hyperlink/socket.io-sticky-headers.git"
},
"files": [
"index.js"
],
"keywords": [
"socket.io",
"sticky sessions",
"headers"
],
"author": "Xiaoxin Lu <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/hyperlink/socket.io-sticky-headers/issues"
},
"homepage": "https://github.com/hyperlink/socket.io-sticky-headers#readme",
"devDependencies": {
"mocha": "^3.2.0",
"semantic-release": "^6.3.2",
"engine.io-client": "^2.0.2",
"standard": "^10.0.0"
},
"dependencies": {
"debug": "^2.6.3"
},
"peerDependencies": {
"engine.io-client": "2"
}
}
28 changes: 28 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-env node, mocha */

'use strict'

const stickyHeaders = require('../')
const assert = require('assert')

describe('sticky-headers', function () {
describe('verify patching', function () {
it('should throw exception if passing in undefined XHR polling', function () {
assert.throws(function () {
stickyHeaders()
}, Error)
})

it('should throw exception if passing in null XHR polling', function () {
assert.throws(function () {
stickyHeaders(null)
}, Error)
})

it('should throw exception if missing prototype methods to patch', function () {
assert.throws(function () {
stickyHeaders(function () {})
}, Error)
})
})
})

0 comments on commit fcc4543

Please sign in to comment.