WebSockets on LAMP stack Part 1

How to use WebSockets on a LAMP stack Part 1

The way PHP works on Apache or Nginx (php-fpm) makes it impossible to keep long lasting connections like Websockets especially if you are looking to scale it. So another service is needed that is capable of handling hundreds of connections simultaneously without much overhead. It could be written in Go, Java, Python, PHP or any other language that has support for even driven programming.

Now frameworks that you could use with the above languages. This is not an exhaustive list but rather personal preferences.

Small note here. The people @(Ratchet)[http://socketo.me/] are already doing this with PHP so it would be good to pay them a visit if you are interested.

For this example I am going to use python with Twisted Matrix. Unfortunately at this point the good people at Twisted Matrix do not have a Websocket implementation so I am going to use a modified version of txWebSocket that I have changed to make compatible with https://tools.ietf.org/html/rfc6455. The implementation is far from solid but for this example it should work. At some point I discovered that txWebSocket was not compatible with the most recent version of Twisted Matrix so I had to stop and make some more changes. This turned out to be a bad choice.

The concept for how this is going to work is the following. We are going to open one Websocket listening port and a UNIX socket using Twisted Matrix. We are going to use the UNIX socket to send messages to the browser through the Websocket.

main.py

 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
import sys
sys.path.append("./txWebSocket/")
from websocket import WebSocketSite, WebSocketHandler, WebSocketFrame
from twisted.internet import reactor, protocol
from twisted.protocols import basic
from twisted.python import log

class WebSocketTracker:

    def __init__(self):
        self.websockets=list()

    def addWebSocket(self,ws):
        self.websockets.append(ws)

    def removeWebSocket(self,ws):
        self.websockets.remove(ws)

    def findWebSocketWithSession(self,sessionId):
        for ws in self.websockets:
            if(ws.sessionId==sessionId):
                return ws
        return None

class WebSocket(WebSocketHandler):

    def __init__(self,transport,request):
        WebSocketHandler.__init__(self,transport,request)
        self.sessionId=None

    def frameReceived(self, frame):
        message=json.loads(frame)

    def connectionMade(self):
        global websocketTracker
        log.msg("Connection Open")
        websocketTracker.addWebSocket(self)
        print self.request.args

        if "id" not in self.request.args:
            self.transport.loseConnection()
            return
        self.sessionId=self.request.args['id'][0]

    def sendMessageJSON(self,message):
        ws=WebSocketFrame(WebSocketFrame.TEXT,json.dumps(message))
        self.transport.write(ws)

    def sendMessage(self,message):
        ws=WebSocketFrame(WebSocketFrame.TEXT,message)
        self.transport.write(ws)

    def connectionLost(self, reason):
        print "Connection Lost"
        global websocketTracker
        websocketTracker.removeWebSocket(self)

class UnixSocketProtocol(basic.LineReceiver):

    def __init__(self):
		self.delimiter="\n"

    def lineReceived(self, line):
        if(self.startsWith(line,"LIST WEBSOCKETS")):
            global webSocketTracker
            for ws in websocketTracker.websockets:
                peer=ws.transport.getPeer()
                self.sendLine("{0} {1}:{2!s}".format(ws.sessionId,peer.host,peer.port))
        if(self.startsWith(line,"SEND MESSAGE")):
            sm=line.split(" ",4)
            sessionId=sm[2]
            message=sm[3]
            ws=websocketTracker.findWebSocketWithSession(sessionId)
            ws.sendMessage(message)

    def startsWith(self,line,start):
        if(len(line)<len(start)):
            return False
        elif(line[0:len(start)]==start):
            return True
        return False

if __name__=="__main__":
    global websocketTracker
    log.startLogging(sys.stdout)
    websocketTracker=WebSocketTracker()

    site = WebSocketSite(None)
    site.addHandler('/websocket', WebSocket)
    reactor.listenTCP(8080, site)

    sf=protocol.ServerFactory()
    sf.protocol=UnixSocketProtocol
    reactor.listenUNIX("/tmp/unix_socket",sf)

    reactor.run()

Lets go through the important parts. The WebSocketTracker is the class that is responsible for keeping a list of the active WebSockets. The WebSocket class is the one that is instantiated each time a new WebSocket is created, and the UnixSocketProtocol class is the one that we will use to talk to the WebSockets. The UnixSocketProtocol supports 2 commands. LIST WEBSOCKETS that lists all active WebSockets and SEND MESSAGE

index.html

 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
<!DOCTYPE html>
<html>
	<head>
	</head>


	<body>
		<h2>Message Board</h2>
		<div><h3>Board Id:<h3><span id="boardId"></span></div>
		<div id="message_board">

		</div>


		<script type="text/javascript">
		function generateId() {
			var ret="";
			var charset="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
			for(var i=0;i<10;i++) {
				ret+=charset.charAt(Math.random()*charset.length);
			}
			return ret;
		}
		randomId=generateId();
		var ele=document.getElementById("boardId");
		ele.innerText=randomId;
		ws = new WebSocket("ws://localhost:8080/websocket?id="+randomId); //?id="+randomId
		ws.onmessage=function(event){
			console.log(event.data);
			var message=JSON.parse(event.data);
			var messageElement=document.createElement("div");
			messageElement.innerText=message.message;
			document.getElementById("message_board").appendChild(messageElement);
		}
		</script>
	</body>
</html>

Here we have a very basic web page that is going to display the messages that arrive from the WebSocket. The JavaScript is generating a random id that is used later as a url parameter to open the WebSocket. The python script will read that id and is going to be displayed in the LIST WEBSOCKETS command. We can use that id to send messages to a particular client. After we run the python program the unix socket can be accessed with this command.

1
2
3
4
user@localhost$ socat UNIX-CONNECT:/tmp/unix_socket STDIO
LIST WEBSOCKETS
QVZS7CQWAE 127.0.0.1:50424
SEND MESSAGE QVZS7CQWAE {"message":"test"}

In the next part we are going to see how to access the unix socket from PHP.