Skip to content

Commit

Permalink
[docs] Add httpd cluster example (socketio#2819)
Browse files Browse the repository at this point in the history
  • Loading branch information
darrachequesne authored Jan 16, 2017
1 parent f7eed6e commit e28b475
Show file tree
Hide file tree
Showing 10 changed files with 713 additions and 0 deletions.
31 changes: 31 additions & 0 deletions examples/cluster-httpd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

# Socket.IO Chat with httpd & redis

A simple chat demo for socket.io

## How to use

Install [Docker Compose](https://docs.docker.com/compose/install/), then:

```
$ docker-compose up -d
```

And then point your browser to `http://localhost:3000`.

This will start four Socket.IO nodes, behind a httpd proxy which will loadbalance the requests (using a cookie for sticky sessions, see [cookie](http://httpd.apache.org/docs/2.4/fr/mod/mod_proxy_balancer.html)).

Each node connects to the redis backend, which will enable to broadcast to every client, no matter which node it is currently connected to.

```
# you can kill a given node, the client should reconnect to another node
$ docker-compose stop server-george
```

## Features

- Multiple users can join a chat room by each entering a unique username
on website load.
- Users can type chat messages to the chat room.
- A notification is sent to all users when a user joins or leaves
the chatroom.
51 changes: 51 additions & 0 deletions examples/cluster-httpd/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

httpd:
build: ./httpd
links:
- server-john
- server-paul
- server-george
- server-ringo
ports:
- "3000:80"

server-john:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=John

server-paul:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Paul

server-george:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=George

server-ringo:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Ringo

redis:
image: redis:alpine
expose:
- "6379"
2 changes: 2 additions & 0 deletions examples/cluster-httpd/httpd/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM httpd:2.4-alpine
COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf
52 changes: 52 additions & 0 deletions examples/cluster-httpd/httpd/httpd.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

Listen 80

ServerName localhost

LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so

LoadModule headers_module modules/mod_headers.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
LoadModule unixd_module modules/mod_unixd.so

User daemon
Group daemon

ErrorLog /proc/self/fd/2

Header add Set-Cookie "SERVERID=sticky.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED

<Proxy "balancer://nodes_polling">
BalancerMember "http://server-john:3000" route=john
BalancerMember "http://server-paul:3000" route=paul
BalancerMember "http://server-george:3000" route=george
BalancerMember "http://server-ringo:3000" route=ringo
ProxySet stickysession=SERVERID
</Proxy>

<Proxy "balancer://nodes_ws">
BalancerMember "ws://server-john:3000" route=john
BalancerMember "ws://server-paul:3000" route=paul
BalancerMember "ws://server-george:3000" route=george
BalancerMember "ws://server-ringo:3000" route=ringo
ProxySet stickysession=SERVERID
</Proxy>

RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) balancer://nodes_ws/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) balancer://nodes_polling/$1 [P,L]

ProxyTimeout 3
15 changes: 15 additions & 0 deletions examples/cluster-httpd/server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM mhart/alpine-node:6

# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install

# Bundle app source
COPY . /usr/src/app

EXPOSE 3000
CMD [ "npm", "start" ]
82 changes: 82 additions & 0 deletions examples/cluster-httpd/server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Setup basic express server
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var redis = require('socket.io-redis');
var port = process.env.PORT || 3000;
var serverName = process.env.NAME || 'Unknown';

io.adapter(redis({ host: 'redis', port: 6379 }));

server.listen(port, function () {
console.log('Server listening at port %d', port);
console.log('Hello, I\'m %s, how can I help?', serverName);
});

// Routing
app.use(express.static(__dirname + '/public'));

// Chatroom

var numUsers = 0;

io.on('connection', function (socket) {
socket.emit('my-name-is', serverName);

var addedUser = false;

// when the client emits 'new message', this listens and executes
socket.on('new message', function (data) {
// we tell the client to execute 'new message'
socket.broadcast.emit('new message', {
username: socket.username,
message: data
});
});

// when the client emits 'add user', this listens and executes
socket.on('add user', function (username) {
if (addedUser) return;

// we store the username in the socket session for this client
socket.username = username;
++numUsers;
addedUser = true;
socket.emit('login', {
numUsers: numUsers
});
// echo globally (all clients) that a person has connected
socket.broadcast.emit('user joined', {
username: socket.username,
numUsers: numUsers
});
});

// when the client emits 'typing', we broadcast it to others
socket.on('typing', function () {
socket.broadcast.emit('typing', {
username: socket.username
});
});

// when the client emits 'stop typing', we broadcast it to others
socket.on('stop typing', function () {
socket.broadcast.emit('stop typing', {
username: socket.username
});
});

// when the user disconnects.. perform this
socket.on('disconnect', function () {
if (addedUser) {
--numUsers;

// echo globally that this client has left
socket.broadcast.emit('user left', {
username: socket.username,
numUsers: numUsers
});
}
});
});
17 changes: 17 additions & 0 deletions examples/cluster-httpd/server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "socket.io-chat",
"version": "0.0.0",
"description": "A simple chat client using socket.io",
"main": "index.js",
"author": "Grant Timmerman",
"private": true,
"license": "BSD",
"dependencies": {
"express": "4.13.4",
"socket.io": "^1.7.2",
"socket.io-redis": "^3.0.0"
},
"scripts": {
"start": "node index.js"
}
}
28 changes: 28 additions & 0 deletions examples/cluster-httpd/server/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Socket.IO Chat Example</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<ul class="pages">
<li class="chat page">
<div class="chatArea">
<ul class="messages"></ul>
</div>
<input class="inputMessage" placeholder="Type here..."/>
</li>
<li class="login page">
<div class="form">
<h3 class="title">What's your nickname?</h3>
<input class="usernameInput" type="text" maxlength="14" />
</div>
</li>
</ul>

<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/main.js"></script>
</body>
</html>
Loading

0 comments on commit e28b475

Please sign in to comment.