Friday, June 27, 2014

Transferring images through WebSockets as text with Base64

While communicating through WebSockets, there are various kinds of data we might transfer between servers and clients. These data can be mainly classified into two categories as text data and binary data. When we are sending some strings of messages which can be printed in textual format as it is, they are text data. We have to deal with binary data when we have things such as files, multimedia, etc to be transferred between WebSocket clients and servers. The focus of this blog post is to share some information which I explored recently. Its about transferring some binary data through WebSockets however not as binary data but instead as textual data. How exactly we do such a thing. This is where Base64 encoding/decoding fits in.

Base64 is a way to encode binary data as ASCII text strings so that we can transfer them through channels which are specifically designed to transfer texts. For example, a WebSocket client can take an image file, encode it in Base64 and transfer it as a text stream through a WebSocket to a server. The server can decode the text stream and regenerate the binary patterns that resembles the image file at server side. That is the scenario which I will show with example codes in this article. I will leave the further readings about Base64 up to interested parties. There are lot of on-line articles to read about it and a good place to understand it simply is this Wikipedia article.

Now it's time to try some example codes. This example assumes that you have installed and tried Autobahn WebSocket library in your Linux box already. If not, checkout this article which I wrote a couple of weeks back. Once you have settled everything with Autobahn installation and tried the echo client / server example, we are ready to begin this exploration. We need three things to try a file transferring scenario between a server and a client through a WebSocket. Those are, a client program, a server program and a file to be transferred. We will write the server and client programs in Python language using the Autobahn Python library. The source codes shown below can also be downloaded from GitHub.

Client program code ( client.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
from autobahn.twisted.websocket import WebSocketClientProtocol, \
                                       WebSocketClientFactory

# import the base64 module
import base64

class MyClientProtocol(WebSocketClientProtocol):

   def onConnect(self, response):
      print("Server connected: {0}".format(response.peer))

   def onOpen(self):
      print("WebSocket connection open.")

      def hello():

         # opening the image file and encoding in base64
         with open("image.jpg", "rb") as image_file:
            encoded_string = base64.b64encode(image_file.read())

         # printing the size of the encoded image which is sent
         print("Encoded size of the sent image: {0} bytes".format(len(encoded_string)))

         # sending the encoded image
         self.sendMessage(encoded_string.encode('utf8'))

      hello()

   def onMessage(self, payload, isBinary):
      if isBinary:
         print("Binary message received: {0} bytes".format(len(payload)))
      else:
         # printing the size of the encoded image which is received
         print("Encoded size of the received image: {0} bytes".format(len(payload)))

   def onClose(self, wasClean, code, reason):
      print("WebSocket connection closed: {0}".format(reason))



if __name__ == '__main__':

   import sys

   from twisted.python import log
   from twisted.internet import reactor

   log.startLogging(sys.stdout)

   factory = WebSocketClientFactory("ws://localhost:9000", debug = False)
   factory.protocol = MyClientProtocol

   reactor.connectTCP("127.0.0.1", 9000, factory)
   reactor.run()


Server program code ( server.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
from autobahn.twisted.websocket import WebSocketServerProtocol, \
                                       WebSocketServerFactory

# import the base64 module
import base64

class MyServerProtocol(WebSocketServerProtocol):

   def onConnect(self, request):
      print("Client connecting: {0}".format(request.peer))

   def onOpen(self):
      print("WebSocket connection open.")

   def onMessage(self, payload, isBinary):
      if isBinary:
         print("Binary message received: {0} bytes".format(len(payload)))
      else:
         print("Text message received, saving to a file")         

         # decode the image and save locally
         with open("image_received.jpg", "wb") as image_file:
            image_file.write(base64.b64decode(payload))

      # echo back message verbatim
      self.sendMessage(payload, isBinary)

   def onClose(self, wasClean, code, reason):
      print("WebSocket connection closed: {0}".format(reason))



if __name__ == '__main__':

   import sys

   from twisted.python import log
   from twisted.internet import reactor

   log.startLogging(sys.stdout)

   factory = WebSocketServerFactory("ws://localhost:9000", debug = False)
   factory.protocol = MyServerProtocol

   reactor.listenTCP(9000, factory)
   reactor.run()

As you can see from the client program code, we need to have the image.jpg file which is supposed to be transferred from the client, in the same directory as client.py file. Let's run the programs and see how they work. Open two terminals and run the server and client programs in those two separate terminals. Server will create a new image file named as image_received.jpg based on the data stream it received through the WebSocket.

python server.py

python client.py

This simple example demonstrate how we can transmit binary data such as image files through WebSockets as textual data. Even though we are allowed to transmit binary data through WebSockets as it is, there can be practical scenarios where we have to restrict the communication to text only. In such situations, Base64 can play a great role to assist us on encoding and decoding binary data with textual data.

~***********~

2 comments:

  1. client.py appears to be same as server.py. Will you please verify?

    Thanks,

    ReplyDelete