I wanted to apply the official django channels tutorial from https://channels.readthedocs.io/en/latest/tutorial/part_2.html to a simple android app.
At https://medium.com/#ssaurel/learn-to-use-websockets-on-android-with-okhttp-ba5f00aea988 I found a simple project but that uses the Echo WebSocket Server available at http://www.websocket.org/echo.html.
I copy-pasted the same project but replaced the Echo WebSocket Server with my own websocket server using django channels.
Here is the code:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Button start;
private TextView output;
private OkHttpClient client;
private final class EchoWebSocketListener extends WebSocketListener {
private static final int NORMAL_CLOSURE_STATUS = 1000;
#Override
public void onOpen(WebSocket webSocket, Response response) {
Log.d(TAG, "onOpen() is called.");
JSONObject obj = new JSONObject();
JSONObject obj2 = new JSONObject();
try {
obj.put("message" , "Hello");
obj2.put("message", "Goodbye!");
} catch (JSONException e) {
e.printStackTrace();
}
webSocket.send(obj.toString());
//webSocket.send("What's up ?");
//webSocket.send(ByteString.decodeHex("deadbeef"));
webSocket.close(NORMAL_CLOSURE_STATUS, obj2.toString());
}
#Override
public void onMessage(WebSocket webSocket, String text) {
Log.d(TAG, "onMessage() for String is called.");
output("Receiving : " + text);
}
#Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
Log.d(TAG, "onMessage() for ByteString is called.");
output("Receiving bytes : " + bytes.hex());
}
#Override
public void onClosing(WebSocket webSocket, int code, String reason) {
Log.d(TAG, "onClosing() is called.");
webSocket.close(NORMAL_CLOSURE_STATUS, null);
output("Closing : " + code + " / " + reason);
}
#Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
Log.d(TAG, "onFailure() is called.");
output("Error : " + t.getMessage());
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start = (Button) findViewById(R.id.start);
output = (TextView) findViewById(R.id.output);
client = new OkHttpClient();
start.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
start();
}
});
}
void start() {
Request request = new Request.Builder().url("ws://192.168.122.1:8080/ws/chat/lobby/").build();
EchoWebSocketListener listener = new EchoWebSocketListener();
WebSocket ws = client.newWebSocket(request, listener);
client.dispatcher().executorService().shutdown();
}
private void output(final String txt) {
runOnUiThread(new Runnable() {
#Override
public void run() {
output.setText(output.getText().toString() + "\n\n" + txt);
}
});
}
}
To send & receive messages to/from the server, they use WebSocketListener from the okhttp3 library.
My consumers.py file is the same as in the Django channels tutorial. More specifically, I used the same setup.
Nevertheless, to be on the same page, I post how my consumers.py file look like:
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
import json
class SignallingConsumer(WebsocketConsumer):
def connect(self):
print("connect() is called.")
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
print("disconnect() is called.")
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
print("receive() is called with " + text_data)
text_data_json = json.loads(text_data)
message = text_data_json['message']
print("message contains: " + message)
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
def chat_message(self, event):
print("the message from the event is: " + event['message'])
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))
I added some prints to understand better what is going under the hood and changed the name of the consumer since I want to use it later as a signalling server for another project when I understand the basics.
On the console, I get the following:
System check identified no issues (0 silenced).
July 25, 2019 - 10:34:24
Django version 2.2.3, using settings 'signalingserver.settings'
Starting ASGI/Channels version 2.2.0 development server at http://192.168.122.1:8080/
Quit the server with CONTROL-C.
WebSocket HANDSHAKING /ws/chat/lobby/ [192.168.122.1:54194]
connect() is called.
WebSocket CONNECT /ws/chat/lobby/ [192.168.122.1:54194]
receive() is called with {"message":"Hello"}
message contains: Hello
WebSocket DISCONNECT /ws/chat/lobby/ [192.168.122.1:54194]
the message from the event is: Hello
disconnect() is called.
My LogCat output is the following:
07-25 12:35:22.754 5297-5319/com.celik.abdullah.simplewebsocketproject D/MainActivity: onOpen() is called.
07-25 12:35:22.810 5297-5319/com.celik.abdullah.simplewebsocketproject D/MainActivity: onClosing() is called.
From these outputs I assume that the connection & sending part is functioning. But the receiving of data from the websocket server is not working because the onMessage() on my client side (android app) is not called.
How, I can fix that ?
Thank you in advance.
In case anyone else sees this, the issue is because you are closing the web socket in your onOpen method before you get a chance to receive anything back from the server. You should remove the line:
webSocket.close(NORMAL_CLOSURE_STATUS, obj2.toString());
And call close at a later time when you are no longer using the web socket connection.
Everything else looks great, and works as written.
Related
I have a Python client that uses websocket for retrieving messages from a spring boot server.
I am able to establish a connection to the server, but the Python client are receiving any messages. No errors whatsoever to help med understand what is going wrong. FYI, I am able to use the same subscription url with an Angular application, and it is able to receive messages.
Spring-boot code:
WebsocketConfig.java
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/ws");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/stomp-endpoint")
.setAllowedOriginPatterns("*")
.withSockJS();
}
}
WebSocketService.java
#Service
public class WebSocketService {
private final SimpMessagingTemplate messagingTemplate;
#Autowired
public WebSocketService(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
// THIS IS THE METHOD THAT IS CALLED WHEN TO SEND TO CLIENTS THAT ARE SUBSCRIBING
public void sendPeopleCounter(PeopleCounter peopleCounter) {
messagingTemplate.convertAndSend("/topic/people-counter", peopleCounter);
}
}
Python-client code:
import websocket
import jwt
import random
import datetime
import stomper
def generate_jwt_token():
json_data = {
"sender": "This is sender",
"message": "This is a message",
"date": str(datetime.datetime.now())
}
return jwt.encode(payload=json_data, key="secret_key", algorithm="HS256")
def on_msg(ws, msg):
print("received msg")
print(msg)
def on_error(ws, err):
print(err)
def on_closed(ws, close_status_code, close_msg):
print("### Closed ###")
print(close_msg)
def on_open(ws):
sub = stomper.subscribe("/topic/people-counter", str(random.randint(1, 5000)), ack="auto")
print(sub)
def db_daemon_listener():
token = generate_jwt_token()
uri = "ws://localhost:8080/stomp-endpoint/websocket"
headers = {"Authorization": "Bearer " + token}
websocket.enableTrace(True)
ws = websocket.WebSocketApp(uri, header=headers, on_error=on_error, on_message=on_msg, on_close=on_closed)
ws.on_open = on_open
ws.run_forever()
def main():
db_daemon_listener()
if __name__ == "__main__":
main()
I'm trying to send a data from RPI to iOS application
the message is showing on the RPI but its not in iOS
//this is the code on RPI
import paho.mqtt.client as MQTT
def connectionStatus(client, userdata, flag, rc):
print("Connecting...")
mqttClient.subscribe("rpi/hello")
def messageDecoder(client, userdata, msg):
message = msg.payload.decode(encoding='UTF-8')
if message == "this is iPhone":
print("Sending reply...")
mqttClient.publish("rpi/world", "this is 3b+")
clientName = "Pi"
serverAddress = "192.168.1.101"
mqttClient = MQTT.Client(clientName)
mqttClient.on_connect = connectionStatus
mqttClient.on_message = messageDecoder
mqttClient.connect(serverAddress)
mqttClient.loop_forever()
here and the code on swift I've tried a lot of ways here can someone please help me solve this problem !
import UIKit
import CocoaMQTT
protocol didReceiveMessageDelegate {
func setMessage(message: String)}
class ViewController: UIViewController {
let mqttClient = CocoaMQTT(clientID: "iOS Device", host: "192.168.1.101", port: 1883)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func connect(_ sender: UIButton) {
mqttClient.connect()
}
#IBAction func disconnect(_ sender: UIButton) {
mqttClient.disconnect()
}
#IBOutlet weak var labelshow: UILabel!
#IBAction func send(_ sender: UIButton) {
mqttClient.publish("rpi/hello", withString: "this is iphone")
mqttClient.subscribe("rpi/world")
}
func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16 ) {
let messageDecoded = String(bytes: message.payload, encoding: .utf8)
print("Did receive a message: \(messageDecoded!)")
}
func setMessage(message: String) {
labelshow.text = message}
}
You need to set your view controller as the MQTT client's delegate otherwise the didReceiveMessage delegate function won't be called.
You need to state that your class conforms to CocoaMQTTDelegate and then assign the delegate
class ViewController: UIViewController, CocoaMQTTDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.mqttClient.delegate = self
}
Hi i am copying parts of the github project multichat from the creator of django channels.
I am making slight changes to the code like not using jquery, renaming of some consumers and such.
I have literally no errors when running the code however when i join the page and the JS creates a websocket it says simply
[2017/08/03 13:13:48] WebSocket HANDSHAKING /chat/stream [127.0.0.1:37070]
[2017/08/03 13:13:48] WebSocket CONNECT /chat/stream [127.0.0.1:37070]
Which one would think is fine ofcourse... However i'n my connect function i have a print("********CONNECTED**********"), wich is nowhere to be seen in the console. It simply doesn't run the function i have told it to when someone connects but it still says the person connected and it throws no errors.
This is the main routing:
channel_routing = [
include("crypto_chat.routing.websocket_routing", path=r"^/chat-stream/$"),
include("crypto_chat.routing.chat_routing"),
]
Routing from app:
websocket_routing = [
route("websocket.connect", ws_connect),
route("websocket.receive", ws_receive),
route("websocket.disconnect", ws_disconnect),
]
chat_routing = [
route("chat.receive", chat_send, command="^send$"),
route("chat.receive", user_online, command="^online$"),
Connect Consumer:
#channel_session_user_from_http
def ws_connect(message):
# only accept connection if you have any rooms to join
print("******************CONNECT*************************''")
message.reply_channel.send({"accept": True})
# init rooms - add user to the groups and pk num to the session
message.channel_session['rooms'] = []
for room in Room.objects.get(users=message.user):
room.websocket_group.add(message.reply_channel)
message.channel_session['rooms'].append(room.pk)
print(message.channel_session['rooms'])
Heres JS (note: i am using the JS extension that is available on the project website also):
function send_msg(){
var msg=document.getElementById('msg_input').value;
console.log("sending msg" + msg);
webSocketBridge.send({
"command": "send",
"room": "1",
"message": msg
});
}
// logging
var ws_path = "/chat/stream";
console.log("connecting to " + ws_path);
// connect
var webSocketBridge = new channels.WebSocketBridge();
webSocketBridge.connect(ws_path);
// listen loop
webSocketBridge.listen(function(data)
{
// read json file and act accordingly
if(data.error){
// post error message in chat
console.log("Error - " + data.error);
return;
}
// handle if the user comes back online
if(data.online){
console.log("User is online");
}
else if(data.offline){
console.log("User offline");
}
else if(data.message){
console.log("Got message");
}
else{ console.log("Unknown message type"); }
});
// Helpful debugging
webSocketBridge.socket.onopen = function () {
console.log("Connected to chat socket");
};
webSocketBridge.socket.onclose = function () {
console.log("Disconnected from chat socket");
}
Websocket paths should match on server and client side. On server side, you have /chat-stream/ and on client side /chat/stream. These should match. Also, make sure you don't forget the trailing slash as django explicitly requires it.
I have made a program which allow me to send a message each time I pressed a space in a unity, and send it to python server using cherrpy.
The problem is though my 'websocket.py' successfully serving on localhost as followed, once i started unity player it just Close instantly with the error.Hope some one help!!
websocket.py
import cherrypy
from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
from ws4py.websocket import WebSocket
class Root(object):
#cherrypy.expose
def index(self):
return 'some HTML with a websocket javascript connection'
#cherrypy.expose
def ws(self):
# you can access the class instance through
handler = cherrypy.request.ws_handler
class AgentServer(WebSocket):
def opened(self):
print("Opened!")
def closed(self, code, reason):
print("Closed!")
def received_message(self,m):
self.send(m.data,m.is_binary)
if __name__ == "__main__":
cherrypy.config.update({
"server.socket_host": "127.0.0.1",
"server.socket_port": 3000,
})
WebSocketPlugin(cherrypy.engine).subscribe()
cherrypy.tools.websocket = WebSocketTool()
cherrypy.quickstart(Root(), "/", config={
"/ws": {
"tools.websocket.on": True,
"tools.websocket.handler_cls": AgentServer,
}
})
Client.cs
using UnityEngine;
using System.Collections;
using WebSocketSharp;
public class Client : MonoBehaviour {
private WebSocket ws;
// Use this for initialization
void Start () {
this.ws = new WebSocket("ws://127.0.0.1:3000");
this.ws.OnOpen += (sender, e) => {
Debug.Log("Opened");
};
ws.OnMessage += (sender, e) =>
{
Debug.Log("WebSocket Message Type: " + e.Type + ", Data: " + e.Data);
};
this.ws.OnClose += (sender, e) => {
Debug.Log("Closed");
};
this.ws.Connect();
}
// Update is called once per frame
void Update () {
if (Input.GetKeyUp(KeyCode.Space))
{
Debug.Log ("Pressed");
ws.Send("Test Message");
}
}
void OnDestroy()
{
ws.Close();
ws = null;
}
}
ErrorCode in Unity
Closed
UnityEngine.Debug:Log(Object)
client:<Start>m__3E(Object, CloseEventArgs) (at Assets/client.cs:18)
WebSocketSharp.Ext:Emit(EventHandler`1, Object, CloseEventArgs) (at Assets/Packages/websocket-sharp/Ext.cs:1101)
WebSocketSharp.WebSocket:close(CloseEventArgs, Boolean, Boolean, Boolean) (at Assets/Packages/websocket-sharp/WebSocket.cs:917)
WebSocketSharp.WebSocket:fatal(String, CloseStatusCode) (at Assets/Packages/websocket-sharp/WebSocket.cs:1128)
WebSocketSharp.WebSocket:doHandshake() (at Assets/Packages/websocket-sharp/WebSocket.cs:1085)
WebSocketSharp.WebSocket:connect() (at Assets/Packages/websocket-sharp/WebSocket.cs:958)
WebSocketSharp.WebSocket:Connect() (at Assets/Packages/websocket-sharp/WebSocket.cs:2472)
client:Start() (at Assets/client.cs:20)
Anaconda Prompt -python websocket.py
[03/Oct/2016:22:11:59] ENGINE Listening for SIGTERM.
[03/Oct/2016:22:11:59] ENGINE Bus STARTING
[03/Oct/2016:22:11:59] ENGINE Set handler for console events.
[03/Oct/2016:22:11:59] ENGINE Starting WebSocket processing
[03/Oct/2016:22:11:59] ENGINE Started monitor thread '_TimeoutMonitor'.
[03/Oct/2016:22:11:59] ENGINE Started monitor thread 'Autoreloader'.
[03/Oct/2016:22:11:59] ENGINE Serving on http://127.0.0.1:3000
[03/Oct/2016:22:11:59] ENGINE Bus STARTED
127.0.0.1 - - [03/Oct/2016:22:12:03] "GET / HTTP/1.1" 200 48 "" "websocket-sharp/1.0"
I'm trying to create a WS connection with my tornado server. The server code is simple:
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
print("WebSocket opened")
def on_message(self, message):
self.write_message(u"You said: " + message)
def on_close(self):
print("WebSocket closed")
def main():
settings = {
"static_path": os.path.join(os.path.dirname(__file__), "static")
}
app = tornado.web.Application([
(r'/ws', WebSocketHandler),
(r"/()$", tornado.web.StaticFileHandler, {'path':'static/index.html'}),
], **settings)
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
I copy pasted the client code from here:
$(document).ready(function () {
if ("WebSocket" in window) {
console.log('WebSocket is supported by your browser.');
var serviceUrl = 'ws://localhost:8888/ws';
var protocol = 'Chat-1.0';
var socket = new WebSocket(serviceUrl, protocol);
socket.onopen = function () {
console.log('Connection Established!');
};
socket.onclose = function () {
console.log('Connection Closed!');
};
socket.onerror = function (error) {
console.log('Error Occured: ' + error);
};
socket.onmessage = function (e) {
if (typeof e.data === "string") {
console.log('String message received: ' + e.data);
}
else if (e.data instanceof ArrayBuffer) {
console.log('ArrayBuffer received: ' + e.data);
}
else if (e.data instanceof Blob) {
console.log('Blob received: ' + e.data);
}
};
socket.send("Hello WebSocket!");
socket.close();
}
});
When it tries to connect i get the following output on the browser's console:
WebSocket connection to 'ws://localhost:8888/ws' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
Why is that?
As pointed out in whatwg.org's Websocket documentation (it's a copy from the standard's draft):
The WebSocket(url, protocols) constructor takes one or two arguments. The first argument, url, specifies the URL to which to connect. The second, protocols, if present, is either a string or an array of strings. If it is a string, it is equivalent to an array consisting of just that string; if it is omitted, it is equivalent to the empty array. Each string in the array is a subprotocol name. The connection will only be established if the server reports that it has selected one of these subprotocols. The subprotocol names must all be strings that match the requirements for elements that comprise the value of Sec-WebSocket-Protocol fields as defined by the WebSocket protocol specification.
Your server answers the websocket connection request with an empty Sec-WebSocket-Protocol header, since it doesn't support the Chat-1 subprotocol.
Since you're writing both the server side and the client side (and unless your writing an API you intend to share), it shouldn't be super important to set a specific subprotocol name.
You can fix this by either removing the subprotocol name from the javascript connection:
var socket = new WebSocket(serviceUrl);
Or by modifying your server to support the protocol requested.
I could give a Ruby example, but I can't give a Python example since I don't have enough information.
EDIT (Ruby example)
Since I was asked in the comments, here's a Ruby example.
This example requires the iodine HTTP/WebSockets server, since it supports the rack.upgrade specification draft (concept detailed here) and adds a pub/sub API.
The server code can be either executed through the terminal or as a Rack application in a config.ru file (run iodine from the command line to start the server):
# frozen_string_literal: true
class ChatClient
def on_open client
#nickname = client.env['PATH_INFO'].to_s.split('/')[1] || "Guest"
client.subscribe :chat
client.publish :chat , "#{#nickname} joined the chat."
if client.env['my_websocket.protocol']
client.write "You're using the #{client.env['my_websocket.protocol']} protocol"
else
client.write "You're not using a protocol, but we let it slide"
end
end
def on_close client
client.publish :chat , "#{#nickname} left the chat."
end
def on_message client, message
client.publish :chat , "#{#nickname}: #{message}"
end
end
module APP
# the Rack application
def self.call env
return [200, {}, ["Hello World"]] unless env["rack.upgrade?"]
env["rack.upgrade"] = ChatClient.new
protocol = select_protocol(env)
if protocol
# we will use the same client for all protocols, because it's a toy example
env['my_websocket.protocol'] = protocol # <= used by the client
[101, { "Sec-Websocket-Protocol" => protocol }, []]
else
# we can either refuse the connection, or allow it without a match
# here, it is allowed
[101, {}, []]
end
end
# the allowed protocols
PROTOCOLS = %w{ chat-1.0 soap raw }
def select_protocol(env)
request_protocols = env["HTTP_SEC_WEBSOCKET_PROTOCOL"]
unless request_protocols.nil?
request_protocols = request_protocols.split(/,\s?/) if request_protocols.is_a?(String)
request_protocols.detect { |request_protocol| PROTOCOLS.include? request_protocol }
end # either `nil` or the result of `request_protocols.detect` are returned
end
# make functions available as a singleton module
extend self
end
# config.ru
if __FILE__.end_with? ".ru"
run APP
else
# terminal?
require 'iodine'
Iodine.threads = 1
Iodine.listen2http app: APP, log: true
Iodine.start
end
To test the code, the following JavaScript should work:
ws = new WebSocket("ws://localhost:3000/Mitchel", "chat-1.0");
ws.onmessage = function(e) { console.log(e.data); };
ws.onclose = function(e) { console.log("Closed"); };
ws.onopen = function(e) { e.target.send("Yo!"); };
For those who use cloudformation templates, AWS has a nice example here.
UPDATE
The key thing is the response in the connection function. On the abovementioned AWS shows how this can be done:
exports.handler = async (event) => {
if (event.headers != undefined) {
const headers = toLowerCaseProperties(event.headers);
if (headers['sec-websocket-protocol'] != undefined) {
const subprotocolHeader = headers['sec-websocket-protocol'];
const subprotocols = subprotocolHeader.split(',');
if (subprotocols.indexOf('myprotocol') >= 0) {
const response = {
statusCode: 200,
headers: {
"Sec-WebSocket-Protocol" : "myprotocol"
}
};
return response;
}
}
}
const response = {
statusCode: 400
};
return response;
};
function toLowerCaseProperties(obj) {
var wrapper = {};
for (var key in obj) {
wrapper[key.toLowerCase()] = obj[key];
}
return wrapper;
}
Please note the header settings in the response. Also this response must be delivered to the requester, for this response integration must be configured.
In the AWS example consider the code:
MyIntegration:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId: !Ref MyAPI
IntegrationType: AWS_PROXY
IntegrationUri: !GetAtt MyLambdaFunction.Arn
IntegrationMethod: POST
ConnectionType: INTERNET
The most important are the last two lines.