I'm new to websocket and trying to use a client written in Dart/flutter to connect to a server written in Python using Autobahn.
Problem
Since Autobahn's server example documentation gives no case about using a ws:// address, I tried to create such a server myself based on its server factory docs, but it failed.
Code
server code looks like this
# from autobahn.twisted.websocket import WebSocketServerProtocol
from autobahn.asyncio.websocket import WebSocketServerProtocol
class MyServerProtocol(WebSocketServerProtocol):
def onConnect(self, request):
print("Client connecting: {}".format(request.peer))
def onOpen(self):
print("WebSocket connection open.")
def onMessage(self, payload, isBinary):
if isBinary:
print("Binary message received: {} bytes".format(len(payload)))
else:
print("Text message received: {}".format(payload.decode('utf8')))
# echo back message verbatim
self.sendMessage(payload, isBinary)
def onClose(self, wasClean, code, reason):
print("WebSocket connection closed: {}".format(reason))
if __name__ == '__main__':
import asyncio
from autobahn.asyncio.websocket import WebSocketServerFactory
factory = WebSocketServerFactory(url='ws://myhost.com/somepat:9001')
factory.protocol = MyServerProtocol
loop = asyncio.get_event_loop()
coro = loop.create_server(factory, '127.0.0.1', 9001)
server = loop.run_until_complete(coro)
try:
print('starting server ...')
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
server.close()
loop.close()
client code adapted from flutter documentation, only the channel is my own:
import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/io.dart';
import 'package:flutter/material.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final title = 'WebSocket Demo';
return MaterialApp(
title: title,
home: MyHomePage(
title: title,
// channel: IOWebSocketChannel.connect('ws://echo.websocket.org'),
channel: IOWebSocketChannel.connect('ws://myhost.com/somepat:9001'),
),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
final WebSocketChannel channel;
MyHomePage({Key key, #required this.title, #required this.channel})
: super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _controller = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Form(
child: TextFormField(
controller: _controller,
decoration: InputDecoration(labelText: 'Send a message'),
),
),
StreamBuilder(
stream: widget.channel.stream,
builder: (context, snapshot) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Text(snapshot.hasData ? '${snapshot.data}' : ''),
);
},
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _sendMessage,
tooltip: 'Send message',
child: Icon(Icons.send),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void _sendMessage() {
if (_controller.text.isNotEmpty) {
widget.channel.sink.add(_controller.text);
}
}
#override
void dispose() {
widget.channel.sink.close();
super.dispose();
}
}
Expected
Running the above server and client code, I expect to see any input string from the client get echoed back from the server and show on the UI below the text entry on the client smartphone.
Observation
Nothing comes back. The log from client side
Launching lib/main.dart on LIO AN00m in debug mode...
Running Gradle task 'assembleDebug'...
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk...
Debug service listening on ws://127.0.0.1:54635/rsYDNkqGhYI=/ws
Syncing files to device LIO AN00m...
I/DecorView[](28048): old windowMode:1 new windoMode:1
I/AwareBitmapCacher(28048): init lrucache size: 2097152 pid=28048
D/stylus (28048): init stylus touchlistener.
I/Hwaps (28048): APS: EventAnalyzed: initAPS: version is 11.0.0.4
D/Hwaps (28048): Fpsrequest create,type:EXACTLY_IDENTIFY
D/Hwaps (28048): Fpsrequest create,type:EXACTLY_IDENTIFY
D/Hwaps (28048): Fpsrequest create,type:OPENGL_SETTING
D/Hwaps (28048): FpsController create
D/Hwaps (28048): APS: EventAnalyzed: reInitFpsPara :mBaseFps = 60; mMaxFps = 60
W/Settings(28048): Setting device_provisioned has moved from android.provider.Settings.Secure to android.provider.Settings.Global.
V/HiTouch_HiTouchSensor(28048): User setup is finished.
W/HwApsManager(28048): HwApsManagerService, registerCallback, start !
D/Hwaps (28048): APS: EventAnalyzed: registerCallbackInApsManagerService, mPkgName:com.example.flutter_websocket; result = true
W/InputMethodManager(28048): startInputReason = 4
I/InputMethodManager(28048): showSoftInput
W/InputMethodManager(28048): startInputReason = 3
V/AudioManager(28048): querySoundEffectsEnabled...
Attempts
I tried a few different address strings, such as
'ws://127.0.0.1:9001'
'ws://localhost:9001'
But none of them work.
Question
It seems that I lack some fundamental knowledge to get started. I come from the ZeroMQ world where all addresses are pretty much tags hiding implementation details, which was friendly to network programming noobs like me, but also seems to prevent me from understanding the WebSocket protocol.
Where am I wrong and how to debug such a case? Any pointers are appreciated.
You need to enter the IP address of your pc/server in the python code
you can check that out by running ipconfig, you should enter the same IP on the dart app as well. localhost is a hostname that refers to the current device used to access it, other devices cant access other localhosts but can access their IP address.
Related
I'm trying to build a very simple React.js application that has a button which sends 'OK' to a back-end python script which responds with 'HELLO' using web sockets. Below is the code snippet for the python backend:
import asyncio
import websockets
async def hello(websocket, path):
message = await websocket.recv()
print(f"Received {message}")
if message == 'OK':
await websocket.send("HELLO")
start_server = websockets.serve(hello, "0.0.0.0", 5678)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
And I get the following error when I run the file:
DeprecationWarning: There is no current event loop
asyncio.get_event_loop().run_forever()
What is the correct way to implement this?
Here is the React.js code just in case:
import React, { useState, useEffect } from 'react';
import openSocket from 'socket.io-client';
const socket = openSocket('ws://localhost:5678');
function App() {
const [response, setResponse] = useState('');
useEffect(() => {
socket.on('message', message => {
setResponse(message);
});
}, []);
const handleClick = () => {
socket.emit('message', 'OK');
}
return (
<div>
<button onClick={handleClick}>OK</button>
<p>{response}</p>
</div>
);
}
export default App;
Sometimes it crashes after 54 seconds, sometimes 56 seconds. Output looks like:
Server started. Listening for incoming connections...
55 Received message: hello server
1/12/2023 7:38:25 AM|Fatal|WebSocket.<startReceiving>b__176_2|System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.Sockets.NetworkStream'.
at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
at WebSocketSharp.Ext.<>c__DisplayClass48_0.<ReadBytesAsync>b__0(IAsyncResult ar)
I tried to add a try/catch block but I am not sure where the error is happening. I am running a websocketsharp server in c# and sending it sample data via python:
C# SERVER CODE:
using System;
using WebSocketSharp;
using WebSocketSharp.Server;
public class Echo : WebSocketBehavior
{
protected override void OnMessage(MessageEventArgs e)
{
try {
Console.WriteLine("Received message: " + e.Data);
Sessions.Broadcast(e.Data);
}
catch{
Console.WriteLine("bad happened");
}
}
}
public class Program
{
public static void Main(string[] args)
{
var wssv = new WebSocketServer(8081);
wssv.AddWebSocketService<Echo>("/Echo");
wssv.Start();
Console.WriteLine("Server started. Listening for incoming connections...");
Console.ReadLine();
wssv.Stop();
}
}
PYTHON CLIENT CODE:
import time
import websocket
def send_words(ws,words):
data = words
ws.send(data)
ws = websocket.create_connection("ws://localhost:8081/Echo")
while True:
time.sleep(1)
data = "hello server"
send_words(ws,data)
apparently its a websocket-sharp specific issue. You just have to add
wssv.KeepClean = false;
before
wssv.Start();
I've set up a websocket in AWS using API gateway and a simple lambda function shown below:
import json
import boto3
def lambda_handler(event, context):
route = event["requestContext"]["routeKey"]
connectionId = event["requestContext"]["connectionId"]
client = boto3.client('apigatewaymanagementapi',
endpoint_url='https://testid.execute-api.ap-southeast-2.amazonaws.com/production')
if route == "$connect":
print('Connection occured')
elif route == "$disconnect":
print("Disconnected")
elif route == "message":
print("Message received")
api = client.post_to_connection(
Data=json.dumps({'result': 'success'}),
ConnectionId = connectionId
)
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
In flutter I connect to the websocket on startup using the web_socket_channel package (skipped some boilerplate code for simplicity)
import 'package:web_socket_channel/io.dart';
#override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((_) async {
channel = IOWebSocketChannel.connect('wss://testid.execute-api.ap-southeast-2.amazonaws.com/production');
channel.stream.listen((message) {
print(message);
});
}
}
When I check the AWS lambda logs I can see that a connection was definitely established.
I then use a button to trigger a function that sends a message to the message route.
void sendMessage(){
channel.sink.add({'action':'message'});
}
On running the sendMessage function, nothing happens and the connection gets disconnected. I'm actually not sure if that's the way to send a message to a custom route as I couldn't find anything in the docs for the web_socket_channel package. How can I get this working so that the connection stays alive and receives messages from the lambda?
Oh solved it. I just needed to send the json as a string.
void sendMessage(){
channel.sink.add(jsonEncode({'action':'message'}));
}
I have the following techstack
FastApi - backend
React - frontend
and want to implement socketio(not Websockets provided by FastApi). It lacks documentation in both FastApi and Socketio
As needed we are using python-socketio for backend socket server and on react we will be using socket.io-client.
After installation we need to setup a socket server.
Backend Implementation
# socket.py
def handle_connect(sid, environ):
logger.info(f"Socket connected with sid {sid}")
class SocketManager:
def __init__(self, origins: List[str]):
self.server = socketio.AsyncServer(
cors_allowed_origins=origins,
async_mode="asgi",
logger=True,
engineio_logger=True,
)
self.app = socketio.ASGIApp(self.server)
#property
def on(self):
return self.server.on
#property
def send(self):
return self.server.send
def mount_to(self, path: str, app: ASGIApp):
app.mount(path, self.app)
socket_manager = SocketManager(settings.origins)
socket_manager.on("connect", handler=handle_connect)
Double check your cors origins. Also you can add other handlers as well using socket_manager.on.
#main.py
from socket import
app = FastAPI(...)
socket_manager.mount_to("/ws", app)
Frontend Implementation
Basic code for integration is as simple as
import { io } from "socket.io-client";
const socket = io("ws://localhost:8000", {{ path: "/ws/socket.io/", transports: ['websocket', 'polling'] }});
socket.on("connect", () => { console.log("Connected", socket.id) });
socket.on("response", () => { console.log("Response", socket.id) });
socket.on("message", data => { console.log(data) });
For my project i created a context for this as follows
import React, { createContext, useContext, useEffect, useState } from 'react';
import { io } from "socket.io-client";
import { useToastContext } from './ToastContext';
export const SocketContext = createContext()
export const SocketProvider = ({ children, uri, options }) => {
const [socketIo, setSocketIo] = useState()
const { sendToast } = useToastContext();
useEffect(() => {
const socket = io(uri, options);
socket.on("connect", () => { console.log("Connected", socket.id) });
socket.on("response", () => { console.log("Response", socket.id) });
socket.on("message", data => {
sendToast(data)
});
setSocketIo(socket)
}, [])
return (
<SocketContext.Provider value={socketIo}>
{children}
</SocketContext.Provider>
)
}
export const useSocket = () => {
return useContext(SocketContext)
}
Now to finally send a message from your server to client you can do
socket_manager.send("Hello World")
Points worth noting
CORS origin should be exactly same if it is http://localhost:3000 from frontend then it should be http://localhost:3000 and not http://localhost:3000/. Look for the backslash
Also the socketio documentation says transports: ['websocket', 'polling'] is default but when i remove this. It gives me cors error. Documentation might be out of date.
I have a server written in python and an Android client written in Kotlin (in Android Studio IDE). I use sockets for maintaining this connection. After the client sends a message to the server, the server will need to send an answer to the client. I didn't find a way to receive data at the client by using sockets. I tried to do that:
var server = Socket(serverIP, serverPort)
server.outputStream.write(message.toByteArray())
var answer = server.inputStream.bufferedReader().use(BufferedReader::readText)
server.close()
but it seems not to work. If it matters, the sending at server side looks like that:
client.send(message.encode())
I suppose, you are using OkHttp for this. In my app I have the following code
private fun getClient(): OkHttpClient {
return try {
val builder = OkHttpClient.Builder()
builder.pingInterval(30, TimeUnit.SECONDS)
builder.build()
} catch (e: Exception) {
throw RuntimeException(e)
}
}
class WebSocketController(
val callback: SomeCallback
) {
private val url = "wss://your.url.com/"
private val socketClient = getClient()
private var serverSocket: WebSocket? = null
private val listener = object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
super.onOpen(webSocket, response)
val json = "" // your payload to send on connect
serverSocket = webSocket
serverSocket!!.send(json)
}
override fun onMessage(webSocket: WebSocket, text: String) {
super.onMessage(webSocket, text)
val data = Json.decodeFromString<YourModel>(text)
// here you can use your data from server
callback.makeSomething(data)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
super.onFailure(webSocket, t, response)
callback.onFailure(t)
}
}
fun establishConnection() {
socketClient.newWebSocket(requestBuilder().build(), listener)
}
fun disconnect() {
val json = "" // your payload on disconnect
serverSocket?.send(json)
}
private fun requestBuilder(): Request.Builder = Request.Builder().url(url)
}