I tried to implement my first websocket example but I cannot make it work.
I use a python webserver:
import threading
import socket
def start_server():
tick = 0
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 1234))
sock.listen(100)
while True:
print 'listening...'
csock, address = sock.accept()
tick+=1
print 'connection!'
handshake(csock, tick)
print 'handshaken'
while True:
interact(csock, tick)
tick+=1
def send_data(client, str):
#_write(request, '\x00' + message.encode('utf-8') + '\xff')
str = '\x00' + str.encode('utf-8') + '\xff'
return client.send(str)
def recv_data(client, count):
data = client.recv(count)
return data.decode('utf-8', 'ignore')
def handshake(client, tick):
our_handshake = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"+"Upgrade: WebSocket\r\n"+"Connection: Upgrade\r\n"+"WebSocket-Origin: http://localhost:8888\r\n"+"WebSocket-Location: "+" ws://localhost:1234/websession\r\n\r\n"
shake = recv_data(client, 255)
print shake
#We want to send this without any encoding
client.send(our_handshake)
def interact(client, tick):
data = recv_data(client, 255)
print 'got:%s' %(data)
send_data(client, "clock ! tick%d" % (tick))
send_data(client, "out ! %s" %(data))
if __name__ == '__main__':
start_server()
And HTML:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Web Socket Example</title>
<meta charset="UTF-8">
<script>
window.onload = function() {
var s = new WebSocket("ws://localhost:1234/");
s.onopen = function(e) { s.send('Ping'); }
s.onmessage = function(e) { alert("got: " + e.data); }
s.onclose = function(e) { alert("closed"); }
};
</script>
</head>
<body>
<div id="holder" style="width:600px; height:300px"></div>
</body>
</html>
When I point my browser to http://localhost/websocket.html over apache2
I got the following error:
python websocketserver.py
listening...
connection!
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: localhost:1234
Origin: http://localhost
Sec-WebSocket-Key: A4sVkUhjVlTZbJrp2NUrqg==
Sec-WebSocket-Version: 13
handshaken
got:
Traceback (most recent call last):
File "websocketserver.py", line 43, in <module>
start_server()
File "websocketserver.py", line 17, in start_server
interact(csock, tick)
File "websocketserver.py", line 40, in interact
send_data(client, "out ! %s" %(data))
File "websocketserver.py", line 24, in send_data
return client.send(str)
socket.error: [Errno 32] Broken pipe
Can someone help me to fix this?
Thanks
You're responding with the older Hixie 75 protocol but the client only speaks the newer HyBi/IETF RFC 6455 WebSocket protocol.
Your response to the handshake should look more like this (the accept value is calculated from the key value from the client):
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
In HyBi/6455, the frames are no longer delimited with \x00 and \xff. Instead there is a header to every frame that contains several pieces of data including frame type and payload length.
See the spec for more information. Or better yet, you could refer and/or use an existing python WebSocket implementation such as pywebsocket, tornado, or my own project websockify which contains websocket.py which is a generic websocket server lib.
Related
Summarize the Problem
I am using Flask-SocketIO for a project and am basically trying to make it so that users can rejoin a room and "pick up where they left off." To be more specific:
The server emits a request to the client, with a callback to process the response and a timeout of 1 second. This is done in a loop so that the request is resent if a user rejoins the room.
A user "rejoining" a room is defined as a user joining a room with the same name as a user who has previously been disconnected from that room. The user is given their new SID in this case and the request to the client is sent to the new SID.
What I am seeing is this:
If the user joins the room and does everything normally, the callback is processed correctly on the server.
It a user rejoins the room while the server is sending requests and then submits a response, everything on the JavaScript side works fine, the server receives an ack but does not actually run the callback that it is supposed to:
uV7BTVtBXwQ6oopnAAAE: Received packet MESSAGE data 313["#000000"]
received ack from Ac8wmpy2lK-kTQL7AAAF [/]
This question is similar to mine but the solution for them was to update Flask-SocketIO and I am running a version newer than theirs: python flask-socketio server receives message but doesn't trigger event
Show Some Code
I have created a repository with a "minimal" example here: https://github.com/eshapiro42/socketio-example.
In case something happens to that link in the future, here are the relevant bits:
# app.py
from gevent import monkey
monkey.patch_all()
import flask_socketio
from collections import defaultdict
from flask import Flask, request, send_from_directory
from user import User
app = Flask(__name__)
socketio = flask_socketio.SocketIO(app, async_mode="gevent", logger=True, engineio_logger=True)
#app.route("/")
def base():
return send_from_directory("static", "index.html")
#app.route("/<path:path>")
def home(path):
return send_from_directory("static", path)
# Global dictionary of users, indexed by room
connected_users = defaultdict(list)
# Global dictionary of disconnected users, indexed by room
disconnected_users = defaultdict(list)
#socketio.on("join room")
def join_room(data):
sid = request.sid
username = data["username"]
room = data["room"]
flask_socketio.join_room(room)
# If the user is rejoining, change their sid
for room, users in disconnected_users.items():
for user in users:
if user.name == username:
socketio.send(f"{username} has rejoined the room.", room=room)
user.sid = sid
# Add the user back to the connected users list
connected_users[room].append(user)
# Remove the user from the disconnected list
disconnected_users[room].remove(user)
return True
# If the user is new, create a new user
socketio.send(f"{username} has joined the room.", room=room)
user = User(username, socketio, room, sid)
connected_users[room].append(user)
return True
#socketio.on("disconnect")
def disconnect():
sid = request.sid
# Find the room and user with this sid
user_found = False
for room, users in connected_users.items():
for user in users:
if user.sid == sid:
user_found = True
break
if user_found:
break
# If a matching user was not found, do nothing
if not user_found:
return
room = user.room
socketio.send(f"{user.name} has left the room.", room=room)
# Remove the user from the room
connected_users[room].remove(user)
# Add the user to the disconnected list
disconnected_users[room].append(user)
flask_socketio.leave_room(room)
#socketio.on("collect colors")
def collect_colors(data):
room = data["room"]
for user in connected_users[room]:
color = user.call("send color", data)
print(f"{user.name}'s color is {color}.")
if __name__ == "__main__":
socketio.run(app, debug=True)
# user.py
from threading import Event # Monkey patched
class User:
def __init__(self, name, socketio, room, sid):
self.name = name
self.socketio = socketio
self.room = room
self._sid = sid
#property
def sid(self):
return self._sid
#sid.setter
def sid(self, new_sid):
self._sid = new_sid
def call(self, event_name, data):
"""
Send a request to the player and wait for a response.
"""
event = Event()
response = None
# Create callback to run when a response is received
def ack(response_data):
print("WHY DOES THIS NOT RUN AFTER A REJOIN?")
nonlocal event
nonlocal response
response = response_data
event.set()
# Try in a loop with a one second timeout in case an event gets missed or a network error occurs
tries = 0
while True:
# Send request
self.socketio.emit(
event_name,
data,
to=self.sid,
callback=ack,
)
# Wait for response
if event.wait(1):
# Response was received
break
tries += 1
if tries % 10 == 0:
print(f"Still waiting for input after {tries} seconds")
return response
// static/client.js
var socket = io.connect();
var username = null;
var room = null;
var joined = false;
var colorCallback = null;
function joinedRoom(success) {
if (success) {
joined = true;
$("#joinForm").hide();
$("#collectColorsButton").show();
$("#gameRoom").text(`Room: ${room}`);
}
}
socket.on("connect", () => {
console.log("You are connected to the server.");
});
socket.on("connect_error", (data) => {
console.log(`Unable to connect to the server: ${data}.`);
});
socket.on("disconnect", () => {
console.log("You have been disconnected from the server.");
});
socket.on("message", (data) => {
console.log(data);
});
socket.on("send color", (data, callback) => {
$("#collectColorsButton").hide();
$("#colorForm").show();
console.log(`Callback set to ${callback}`);
colorCallback = callback;
});
$("#joinForm").on("submit", (event) => {
event.preventDefault();
username = $("#usernameInput").val();
room = $("#roomInput").val()
socket.emit("join room", {username: username, room: room}, joinedRoom);
});
$("#colorForm").on("submit", (event) => {
event.preventDefault();
var color = $("#colorInput").val();
$("#colorForm").hide();
colorCallback(color);
});
$("#collectColorsButton").on("click", () => {
socket.emit("collect colors", {username: username, room: room});
});
<!-- static/index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Socket.IO Example</title>
</head>
<body>
<p id="gameRoom"></p>
<form id="joinForm">
<input id="usernameInput" type="text" placeholder="Your Name" autocomplete="off" required>
<input id="roomInput" type="text" placeholder="Room ID" autocomplete="off" required>
<button id="joinGameSubmitButton" type="submit" btn btn-dark">Join Room</button>
</form>
<button id="collectColorsButton" style="display: none;">Collect Colors</button>
<form id="colorForm" style="display: none;">
<p>Please select a color.</p>
<input id="colorInput" type="color" required>
<button id="colorSubmitButton" type="submit">Send Color</button>
</form>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://cdn.socket.io/4.4.1/socket.io.min.js" integrity="sha384-fKnu0iswBIqkjxrhQCTZ7qlLHOFEgNkRmK2vaO/LbTZSXdJfAu6ewRBdwHPhBo/H" crossorigin="anonymous"></script>
<script src="client.js"></script>
</body>
</html>
Edit
Steps to Reproduce
Start the server python app.py and visit localhost:5000 in your browser.
Enter any username and Room ID and click "Join Room."
Click "Collect Colors."
Select a color and click "Send." The selector should disappear and the server should print out a confirmation.
Reload everything.
Repeat steps 2 and 3 and copy the Room ID.
Exit the page and then navigate back to it.
Enter the same username and Room ID as you did in step 6 and click "Join Room."
Select a color and click "Send." The selector disappears briefly but then comes back, since the server did not correctly process the response and keeps sending requests instead.
Edit 2
I managed to work around (not solve) the problem by adding more state variables on the server side and implementing a few more events to avoid using callbacks entirely. I would still love to know what was going wrong with the callback-based approach though since using that seems cleaner to me.
The reason why those callbacks do not work is that you are making the emits from a context that is based on the old and disconnected socket.
The callback is associated with the socket identified by request.sid. Associating the callback with a socket allows Flask-SocketIO to install the correct app and request contexts when the callback is invoked.
The way that you coded your color prompt is not great, because you have a long running event handler that continues to run after the client goes aways and reconnects on a different socket. A better design would be for the client to send the selected color in its own event instead of as a callback response to the server.
I'm currently trying to create a contact us form where users can send a report of any kind to my personal email address. For the sake of this example let's call it my-email-address#email.com.
For the moment I don't care a lot about the user's email. Let's say I'm going to use the following information.
from: "my-email-address#email.com"
to: "my-email-address#email.com"
subject: "a subject name"
STEP 1: I created my form in views/home/contact_us.html.erb with an AJAX POST request:
<form id="sendEmailForm">
<div class="form-group mb-3">
<input type="email" class="form-control" id="exampleFormControlInput1" placeholder="Enter your email">
</div>
<div class="form-group mb-3">
<input type="text" class="form-control" id="exampleFormControlInput2" placeholder="Enter a subject (Optional)">
</div>
<div class="form-group mb-3">
<textarea class="form-control" placeholder="Please write your name, company-name, and what you would like to achieve." id="exampleFormControlTextarea3" rows="5"></textarea>
</div>
<button type="submit" class="btn btn-primary mb-2">Send Email</button>
</form>
<script type="text/javascript">
$('#sendEmailForm').on('submit', function(e) {
e.preventDefault();
e.stopPropagation();
let final_json_data = {
email: document.getElementById("exampleFormControlInput1").value,
subject: document.getElementById("exampleFormControlInput2").value,
content: document.getElementById("exampleFormControlTextarea3").value
};
jQuery.ajax({
url: '/home/send_email_to_server',
type: "POST",
data: {emailDetails: final_json_data},
success: function(result) {
alert("ajax request OK!");
},
fail: function(result) {
alert("ajax has failed")
}
});
});
</script>
STEP 2: My Home Controller and routes.rb:
class HomeController < ApplicationController
def contact_us
puts "GETTING THE PAGE !!!!!!!!!!!!!!!!!!!"
end
def send_email_to_server
#emailDetails = params[:emailDetails]
puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
puts " Store email details on server"
puts #emailDetails['email']
puts #emailDetails['subject']
puts #emailDetails['content']
puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
ContactUsMailer.notify_server_via_email(#emailDetails['email'], #emailDetails['subject']).deliver
puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
end
end
Rails.application.routes.draw do
get 'home/contact_us'
post 'home/send_email_to_server'
end
STEP 3: Modified application_mailer.rb to have a default from-email:
class ApplicationMailer < ActionMailer::Base
default from: "my-email-address#email.com"
layout 'mailer'
end
STEP 4: Modified contact_us_mailer.rb to handle the request with the captured parameters:
class ContactUsMailer < ApplicationMailer
def notify_server_via_email(toEmail, aSubject)
puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
puts " Trying to send an email . . . "
#email = toEmail
#subject = aSubject
puts #email
puts #subject
puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
mail(
to: #email,
subject: #subject
)
end
end
STEP 4: Then in the views/contact_us_mailer I created a new file called notify_server_via_email.html.erb and added the following content:
<h1> hello world </h1>
So here is what happens in order:
User fills form and submits the button.
AJAX POST REQUEST to /home/send_email_to_server
Server receives request and catches parameters and executes mail() function
However I'm getting the following error:
Started POST "/home/send_email_to_server" for ::1 at 2021-07-03 18:01:00 +0300
Processing by HomeController#send_email_to_server as */*
Parameters: {"emailDetails"=>{"email"=>"my-email-address#email.com", "subject"=>"some new subject", "content"=>"a text example"}}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Store email details on server
my-email-address#email.com
some new subject
a text example
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Trying to send an email . . .
my-email-address#email.com
some new subject
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Rendering layout layouts/mailer.html.erb
Rendering contact_us_mailer/notify_server_via_email.html.erb within layouts/mailer
Rendered contact_us_mailer/notify_server_via_email.html.erb within layouts/mailer (Duration: 0.5ms | Allocations: 70)
Rendered layout layouts/mailer.html.erb (Duration: 1.5ms | Allocations: 241)
ContactUsMailer#notify_server_via_email: processed outbound mail in 14.0ms
Delivered mail 60e07bace69f9_27544024-497#DESKTOP-MQJ3IGG.mail (30045.8ms)
Date: Sat, 03 Jul 2021 18:01:00 +0300
From: my-email-address#email.com
To: my-email-address#email.com
Message-ID: <60e07bace69f9_27544024-497#DESKTOP-MQJ3IGG.mail>
Subject: some new subject
Mime-Version: 1.0
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Email styles need to be inline */
</style>
</head>
<body>
<h1> hello world </h1>
</body>
</html>
Completed 500 Internal Server Error in 30095ms (ActiveRecord: 0.0ms | Allocations: 11373)
EOFError (end of file reached):
app/controllers/home_controller.rb:35:in `send_email_to_server'
I have no idea what is causing the 500 Internal server error. I'm currently working on the development side and I'm aware that I shouldn't be doing this but it's just for testing purposes, I'm not aiming to keep this configuration forever. Also, I came across this StackOverflow Question which is similar with my issue, but there is no clear answer since that was the university wifi preventing an smtp request from working. I'm trying from a home wifi.
Also for additional reference here is my development.rb commands for action_mailer:
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
:address => 'localhost',
:port => 3000
}
config.action_mailer.default_url_options = {host: 'localhost:3000'}
config.action_mailer.perform_caching = false
Error is raised by library net/protocol.rb#227. If you open browser developer tools, you can see it under subtab response for request with status 500 under tab Network.
Error reason: library can not connect to your smtp server that according to your development.rb config config.action_mailer.smtp_settings is located at localhost:3000. At port 3000 is located your web-server, smtp usually is located at port 25, 587, 2525 (if it is running).
You need running smtp server and correctly configured config.action_mailer.smtp_settings on your local computer if you wish to send mail to my-email-address#email.com in development environment.
If you wish to check email, you can look at console or log. Or use gem letter_opener, or use ActionMailer::Preview. See answer at StackOverflow.
I'm currently writing a simple prototype Vert.x 3.3 application (Latest Github build using Jitpack.io) that tries to secure http endpoints and the eventbus with Keycloak.
I have two verticals and static index page with a button to send messages. Everything works fine unsecured but I don't know how to secure the SockJS eventbus bridge with Keycloak. Securing the http endpoint works fine.
Since Vert.x 3.3 is not officially released yet I haven't been able to find very much information.
http://vertx.io/blog/vertx-3-and-keycloak-tutorial/ only covered securing the http endpoint and the 3.21 documentation on requiring authorization is specifically for using the BasicAuthHandler which I'm not sure how to modify to work with Keycloak:
http://vertx.io/docs/vertx-web/java/#_requiring_authorisation_for_messages
Currently I have the following code:
public class VertxEventBusTest extends AbstractVerticle {
#Override
public void start(Future<Void> startFuture) throws Exception {
System.out.println("Primary Verticle Started");
Router router = Router.router(vertx);
HttpServer server = vertx.createHttpServer().requestHandler(router::accept);
OAuth2Auth oAuth2Auth = OAuth2Auth.createKeycloak(vertx, OAuth2FlowType.AUTH_CODE, getKeycloakJson());
OAuth2AuthHandler oAuth2AuthHandler = OAuth2AuthHandler.create(oAuth2Auth, "http://localhost:8091");
oAuth2AuthHandler.setupCallback(router.get("/callback"));
SockJSHandlerOptions options = new SockJSHandlerOptions().setHeartbeatInterval(2000);
BridgeOptions bridgeOptions = new BridgeOptions();
bridgeOptions.addInboundPermitted(new PermittedOptions().setAddress("click"));
bridgeOptions.addOutboundPermitted(new PermittedOptions().setAddress("click"));
SockJSHandler sockJSHandler = SockJSHandler.create(vertx, options).bridge(bridgeOptions);
router.route("/").handler(oAuth2AuthHandler);
router.route("/eventbus/*").handler(oAuth2AuthHandler);
router.route("/eventbus/*").handler(sockJSHandler);
vertx.eventBus().<JsonObject>consumer("click", msg -> System.out.println("Msg Received on Verticle1: " + msg.body()));
router.route().handler(StaticHandler.create().setWebRoot("webroot"));
server.listen(8091, result -> {
if (result.succeeded()) {
startFuture.complete();
} else {
startFuture.fail(result.cause());
}
});
}
private JsonObject getKeycloakJson() {
return new JsonObject("{\n" +
" \"realm\": \"Prototype\",\n" +
" \"realm-public-key\": \"<public key>",\n" +
" \"auth-server-url\": \"http://localhost:8180/auth\",\n" +
" \"ssl-required\": \"external\",\n" +
" \"resource\": \"prototype-eventbus\",\n" +
" \"credentials\": {\n" +
" \"secret\": \"<secret>\"\n" +
" }\n" +
"}");
}
}
My Static Html is:
<head>
<meta charset="UTF-8">
<script src='sockjs-0.3.4.js'></script>
<script src='vertx-eventbus.js'></script>
<script src='index-js.js'></script>
<title>VERT.X Test</title>
</head>
<body onload="load()">
<!-- JavaScript includes. -->
<button onclick="zclicker()">Test Click</button>
</body>
</html>
Javascript:
var eb = new EventBus('http://localhost:8091/eventbus');
function load() {
eb.onopen = function () {
eb.registerHandler('click', function (data) {})
};
}
function zclicker() {
eb.publish('click', {
'clicked': true
});
}
When running the vertical there are no errors but chrome developer tools shows the following error after login through keycloak:
XMLHttpRequest cannot load http://localhost:8180/auth/realms/Prototype/protocol/openid-connect/auth?re…direct_uri%3D%2Feventbus%2Finfo&state=&client_id=prototype-eventbus&scope=. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8091' is therefore not allowed access.
That looks like a CORS issue.
By the looks of your error you might just be able to add the following header in the response:
Access-Control-Allow-Origin: *
or
Access-Control-Allow-Origin: http://localhost:8091
depending on how much you care about security.
That's usually sufficient for simple GET for text/html, but if you want to handle other content types (eg JSON) or PUT / or DELETE methods, then you'll have to add more headers to allow them.
Some resources:
http://enable-cors.org/server.html
http://www.html5rocks.com/en/tutorials/cors/
I am trying to send string/text data from browser client to python server and simply print it out. I have followed several examples on the internet, and all are the same: by using javascript
web_socket.send("text to be sent")
and (python)
data = web_socket.recv(1024)
print data
they receive what they want, what is clear and nice printout "text to be sent" on server site.
You can find my .html and .py below:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test</title>
<script src="jquery.js"></script>
<script type="application/javascript">
var ws;
function init() {
var servermsg = document.getElementById("servermsg");
ws = new WebSocket("ws://127.0.0.1:9877/");
ws.onopen = function(){
servermsg.innerHTML = servermsg.innerHTML + "<br>Server connected";
};
ws.onmessage = function(e){
servermsg.innerHTML = servermsg.innerHTML + "<br><< Recieved data: " + e.data;
};
ws.onclose = function(){
servermsg.innerHTML = servermsg.innerHTML + "<br>Server disconnected";
};
}
function postmsg(){
var text = document.getElementById("message").value;
ws.send(text);
servermsg.innerHTML = servermsg.innerHTML + "<br>>> Data sent: " + text;
}
//$(function(){
// var text = document.getElementById("message").value;
// ws.send(text);
// servermsg.innerHTML = servermsg.innerHTML + "<br>Sent: " + text;
//});
</script>
</head>
<body onload="init();">
<form action="" onSubmit="postmsg();return false;">
<input type="text" name="message" value="" id="message">
<input type="submit" name="submit" value="" id="submit">
</form>
<div id="servermsg"><h1>Message log:</h1></div>
</body>
</html>
Server:
#!/usr/bin/env python
import socket
import threading
import struct
import hashlib
import base64
PORT = 9877
_address = ""
def create_handshake_resp(handshake):
final_line = ""
lines = handshake.splitlines()
for line in lines:
parts = line.partition(": ")
if parts[0] == "Sec-WebSocket-Key":
key = parts[2]
magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
accept_key = base64.b64encode(hashlib.sha1(key+magic).digest())
return (
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: " + accept_key + "\r\n\r\n")
def handle(s, addr):
data = s.recv(1024)
response = create_handshake_resp(data)
s.sendto(response, addr)
lock = threading.Lock()
while 1:
print "Waiting for data from", addr
data = s.recv(1024)
print "Done"
if not data:
print "No data"
break
print 'Data from', addr, ':', data
print 'Client closed:', addr
lock.acquire()
clients.remove(s)
lock.release()
s.close()
def start_server():
print 'STARTING SERVER...'
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', PORT))
s.listen(1)
print 'SERVER STARTED'
while 1:
conn, addr = s.accept()
print 'NEW CONNECTION ['+str(len(clients))+'], connected by ', addr
clients.append(conn)
threading.Thread(target = handle, args = (conn, addr)).start()
clients = []
start_server()
And server printout (when input was like "AA", or "ABC"):
STARTING SERVER...
SERVER STARTED
NEW CONNECTION [0], connected by ('127.0.0.1', 43877)
Waiting for data from ('127.0.0.1', 43877)
Done
Data from ('127.0.0.1', 43877) : ����w�q
Waiting for data from ('127.0.0.1', 43877)
Done
Data from ('127.0.0.1', 43877) : ��)B�h
Waiting for data from ('127.0.0.1', 43877)
I'm working on something similar myself. The Websocket protocol mandates that the client sends all its data using a mask. This is why you see 'garbage' - it's the masked text.
https://www.rfc-editor.org/rfc/rfc6455#section-5
"a client MUST mask all frames that it
sends to the server"
Read section 5 of the protocol and all will become clear. The browser (ie the client) is just implementing the protocol as it should (when you call ws.send). You need to do your bit.
Note also that when the sever sends data to the client it must NOT mask. But it still has to supply other info before the actual data (type, length etc).
To send a message from server side to websocket client you need to do as follows:
message = bytearray([0b10000001, len(original_msg)])
for byte in bytearray(original_msg):
message.append(byte)
See a stable server to client unidirectional socket library at https://github.com/westial/SingleSocket
The problem with the junk data was the javascript code sends the masked data and you must unmask it on the server side and the server sendes the unmasked data to client side.To solve this problem see my git-hub page
[][1]https://github.com/mohanbe/web-chat
I'm doing a study on WebSocket protocol and trying to implement a simple ECHO service for now with Python on the backend.
It seems to work fine but the connection drops right after being established.
Here is my client:
<!doctype html>
<head>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
function Client()
{
//var ws = new WebSocket("ws://echo.websocket.org"); // this works fine
var ws = new WebSocket("ws://localhost:8000");
ws.onopen = function(e){ $("#response").append(">> Connected<br />"); }
ws.onclose = function(e){ $("#response").append(">> Disconnected<br />"); }
ws.onerror = function(e){ $("#response").append(">> ERROR: " + e.data + "<br />"); }
ws.onmessage = function(e){ $("#response").append("> " + e.data + "<br />"); }
this.sendCmd = function()
{
var message = $("#cmd").val();
$("#response").append(message + "<br />");
ws.send(message);
return false;
}
this.disconnect = function()
{
ws.close();
}
}
// onload
$(function() {
$("#response").append(">> Connecting<br />");
client = new Client();
$("#send").click(client.sendCmd);
$("#disconnect").click(client.disconnect);
});
</script>
</head>
<body>
<input type="text" name="cmd" id="cmd" /> | Send | Disconnect<br />
<hr />
<span id="response"></span>
</body>
</html>
Here is the server:
import SocketServer
import socket
from hashlib import sha1
from base64 import b64encode
PORT = 8000
MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
class Handler(SocketServer.BaseRequestHandler):
# incoming connection
def setup(self):
self.data = self.request.recv(1024).strip()
print "connection established", self.client_address
self.headers = self.headsToDict(self.data.split("\n"))
# incoming message
def handle(self):
# its a handshake
if "Upgrade" in self.headers and self.headers["Upgrade"] == "websocket":
key = self.headers["Sec-WebSocket-Key"]
accept = b64encode(sha1(key + MAGIC).hexdigest().decode('hex'))
response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" # "HTTP/1.1 101 Switching Protocols\r\n"
print "< HTTP/1.1 101 Web Socket Protocol Handshake" # "HTTP/1.1 101 Switching Protocols\r\n"
response += "Upgrade: websocket\r\n"
print "< Upgrade: websocket"
response += "Connection: Upgrade\r\n"
print "< Connection: Upgrade"
response += "Sec-WebSocket-Accept: "+accept+"\r\n\r\n"
print "< Sec-WebSocket-Accept: "+accept
self.request.send(response)
# its a normal message, echo it back
else:
print self.data
self.request.send(self.data)
# connection dropped
def finish(self):
print "connection lost", self.client_address
# convert a list of headers to a dictionary for convenience
def headsToDict(self, hdata):
rzygi = {}
for item in hdata:
print '>', item
item = item.split(':')
if len(item) > 1:
rzygi[item[0].strip()] = item[1].strip()
return rzygi
server = SocketServer.TCPServer(("", PORT), Handler)
server.socket_type = socket.SOCK_STREAM # didnt help
print "serving at port", PORT
try:
server.serve_forever()
except KeyboardInterrupt:
pass
server.server_close()
As mentioned, the connection is established successfully but then drops straight away, which makes me think the code is correct but there is something missing to keep the socket open. Here is the server output:
serving at port 8000
connection established ('127.0.0.1', 52633)
> GET / HTTP/1.1
> Upgrade: websocket
> Connection: Upgrade
> Host: localhost:8000
> Sec-WebSocket-Origin: http://localhost
> Sec-WebSocket-Key: qWGnhdFQ6l8Xs9awgQURfA==
> Sec-WebSocket-Version: 8
< HTTP/1.1 101 Web Socket Protocol Handshake
< Upgrade: websocket
< Connection: Upgrade
< Sec-WebSocket-Accept: fei4E4LQvPnf4y2ilebVsxRofvc=
connection lost ('127.0.0.1', 52633)
How do I keep the socket open?
edit: server code comments
The connection is closed each time after handle. You should rather stay there reading incoming data:
# incoming connection
def setup(self):
print "connection established", self.client_address
def handle(self):
while 1:
try:
self.data = self.request.recv(1024).strip()
# incoming message
self.headers = self.headsToDict(self.data.split("\r\n"))
# its a handshake
if "Upgrade" in self.headers and self.headers["Upgrade"] == "websocket":
key = self.headers["Sec-WebSocket-Key"]
accept = b64encode(sha1(key + MAGIC).hexdigest().decode('hex'))
response = "HTTP/1.1 101 Switching Protocols\r\n"
response += "Upgrade: websocket\r\n"
response += "Connection: Upgrade\r\n"
response += "Sec-WebSocket-Accept: "+accept+"\r\n\r\n"
print response
self.request.send(response)
# its a normal message, echo it back
else:
print self.data
self.request.send(self.data)
except:
print "except"
break