I am trying to develop a consumer (AsyncWebsocketConsumer type) which will be connected with a websocket and make changes to the frontend using JavaScript.
The first thing that I am failing to implement is the consumer's functions (connect, send, disconnect). Also , using Redis.
my settings.py is
ASGI_APPLICATION = "myapp.routing.application"
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': [('localhost', 6379)],
},
}
}
and my routing.py is
application = ProtocolTypeRouter({
"channel": ChannelNameRouter({
"example": ExampleConsumer,
}),
})
Last, my consumers.py is
class ExampleConsumer(AsyncWebsocketConsumer):
async def connect(self,msg):
# Called on connection.
# To accept the connection call:
await self.accept()
print('Channel connected')
When I tried the :
channel_layer = get_channel_layer()
async_to_sync(channel_layer.send)('example', {'type': 'connect'})
so that I could call the connect and see the connected-message that will let me know that the socket is connected , and then continue by sending a message , I get the :
raise NotImplementedError("You must implement application_send()")
You must implement application_send()
I am pretty sure that I have misunderstood so many things but I am looking how to solve this problem for a long time and I couldn't find anything useful for my case , like an example or good documentation , so whatever helps will be appreciated!
You are using ChannelLayers wrong. They are for communicating between different instances of an application. Not for actual establishing WebSocket connections.
Try this:
settings.py
ASGI_APPLICATION = "myapp.routing.application" # make sure your project is called 'myapp' ;-)
routing.py
application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(
URLRouter([
path('ws/example/', consumers.ExampleConsumer),
])
),
})
consumers.py
class ExampleConsumer(AsyncWebsocketConsumer):
async def connect(self,msg):
# Called on connection.
# To accept the connection call:
await self.accept()
print('Channel connected')
async def receive(self, data):
# do something with data
print(data)
# send response back to connected client
await self.send('We received your message')
You can use a Simple Websocket Client to test your ws-endpoint.
Connect to http://localhost:8000/ws/example/: You should see "Channel connected" on the console, as well as a message that a client connected.
Send a request with some data. This data should get logged in the console and you will get a response back to your client.
Hope that helps to get you started.
Related
I am developing a watchOS app and I want to send push notifications to my users. I have enabled "Push Notifications" in signing and capabilities and a file called "app_name WatchKit Extension" was generated containing one entitlement called APS environment whose value is set to "development".
I have also generated a .p8 file in the Apple Developer Website with the authentication key and that also gives me the key id.
I have created a Swift class called App Delegate that conforms to UNUserNotificationCenterDelegate. I have implemented the method applicationDidFinishLaunching from where I call WKExtension.shared().registerForRemoteNotifications(). Then I implemented the didRegisterForRemoteNotifications where I receive the device token and convert it into a string by executing these lines of code:
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
Then I send the token to the server. In the server I have a Python script that makes the post request to https://api.sandbox.push.apple.com:443 with the headers and notification payload. However, I always get a 404 error ({"reason":"BadPath"}).
I don't know what I am doing wrong. Am I configuring anything wrong? This is the Python script I am using:
import time
import httpx
import asyncio
import jwt
ALGORITHM = 'ES256'
APNS_AUTH_KEY = "path to .p8 file"
f = open(APNS_AUTH_KEY)
secret = f.read()
apns_token = jwt.encode(
{
'iss': 'cert_id',
'iat': time.time()
},
secret,
algorithm=ALGORITHM,
headers={
'alg':ALGORITHM,
'kid':'key_id'
}
)
dev_server = "https://api.sandbox.push.apple.com:443"
device_token = "9fe2814b6586bbb683b1a3efabdbe1ddd7c6918f51a3b83e90fce038dc058550"
headers = {
'method': 'POST',
'path': '/3/device/{0}'.format(device_token),
'autorization': 'bearer {0}'.format(apns_token),
'apns-push-type': 'myCategory',
'apns-expiration': '0',
'apns-priority': '10',
}
payload = {
"aps" : {
"alert" : {
"title" : "Hello Push",
"message": "This is a notification!"
},
"category": "myCategory"
}
}
async def test():
async with httpx.AsyncClient(http2=True) as client:
client = httpx.AsyncClient(http2=True)
r = await client.post(dev_server, headers=headers, data=payload)
print(r.text)
print(r)
asyncio.run(test())
Is there anything wrong with the way I am setting things up or performing the post request?
Thank you for your help!
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'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.
I am writing an application that integrates into a website that is already written in Meteor (I can't change that but I can add on to it). I am trying to send information from the Meteor application to my Flask server.
To do this I am using MeteorJs's HTTP module.
The code for this:
HTTP.post('http://127.0.0.1:5000/path', {
"content" : {"headers" : {"Content-Type": "application/json"}, "data": {time: getTime, data: getData()}}
},
(error, result) => {
if(error){
console.log(error);
console.log({time: getTime(), data: getData()})
}
else {
console.log(result);
}
}
)
getTime() and getData() both work independently outside this function, so they shouldn't be the source of error.
When I look at the JS console for when the event is being fired I receive the following message:
Error: Connection lost at XMLHttpRequest.xhr.onreadystateexchange and what was supposed to be sent to the Flask server.
When I look at the Flask server I see that it is receiving the post request with status code 200, but it seems like there is no data actually being received.
The code on the python end:
#app.route(r'path', methods=["POST"])
def get_data():
print(request.data)
print(request.args)
return "Hello World"
The print statements come out empty with this being shown on the console b'[object Object]' or ImmutableMultiDict([])
The Meteor app and the Flask app are both on different ports.
The problem I believe is on the MeteorJS side, since I used the curl linux function it works properly when I ping the flask server from there.
Is there a way to fix this error? If so how?
Hi "parameters" should be "data".
You can find all valid options in the docs.
Let me know if it works for you.
HTTP.post('http://127.0.0.1:5000/path', {
data : {time: getTime(), data: getData()}
}, (error, result) => {
if(error){
console.log(error);
console.log({time: getTime(), data: getData()})
} else {
console.log(result);
}
}
)
I'm trying to connect to a WAMP bus from a different application that has certain roles configured. The roles are authenticated with a static ticket, so I believe that I need to declare what role I want to connect as and what the associated ticket is. I'm writing this in Python and have most of the component set up, but I can't find any documentation about how to do this sort of authentication.
from autobahn.twisted.component import Component, run
COMP = Component(
realm=u"the-realm-to-connect",
transports=u"wss://this.is.my.url/topic",
authentication={
# This is where I need help
# u"ticket"?
# u"authid"?
}
)
Without the authentication, I'm able to connect to and publish to the WAMP bus when it is running locally on my computer, but that one is configured to allow anonymous users to publish. My production WAMP bus does not allow anonymous users to publish, so I need to authenticate what role this is connecting as. The Autobahn|Python documentation implies that it can be done in Python, but I've only been able to find examples of how to do it in JavaScript/JSON in Crossbar.io's documentation.
the documentation is not very up to date.
With the Component it is necessary to do like that for tickets:
from autobahn.twisted.component import Component, run
component = Component(
realm=u"the-realm-to-connect",
transports=u"wss://this.is.my.url/topic",
authentication={
"ticket": {
"authid": "username",
"ticket": "secrettoken"
}
},
)
Here is some example that can be helpful for you:
https://github.com/crossbario/crossbar-examples/tree/master/authentication
I think you need to use WAMP-Ticket Dynamic Authentication method.
WAMP-Ticket dynamic authentication is a simple cleartext challenge
scheme. A client connects to a realm under some authid and requests
authmethod = ticket. Crossbar.io will "challenge" the client, asking
for a ticket. The client sends the ticket, and Crossbar.io will in
turn call a user implemented WAMP procedure for the actual
verification of the ticket.
So you need to create an additional component to Authenticate users:
from autobahn.twisted.wamp import ApplicationSession
from autobahn.wamp.exception import ApplicationError
class AuthenticatorSession(ApplicationSession):
#inlineCallbacks
def onJoin(self, details):
def authenticate(realm, authid, details):
ticket = details['ticket']
print("WAMP-Ticket dynamic authenticator invoked: realm='{}', authid='{}', ticket='{}'".format(realm, authid, ticket))
pprint(details)
if authid in PRINCIPALS_DB:
if ticket == PRINCIPALS_DB[authid]['ticket']:
return PRINCIPALS_DB[authid]['role']
else:
raise ApplicationError("com.example.invalid_ticket", "could not authenticate session - invalid ticket '{}' for principal {}".format(ticket, authid))
else:
raise ApplicationError("com.example.no_such_user", "could not authenticate session - no such principal {}".format(authid))
try:
yield self.register(authenticate, 'com.example.authenticate')
print("WAMP-Ticket dynamic authenticator registered!")
except Exception as e:
print("Failed to register dynamic authenticator: {0}".format(e))
and add Authentication method in the configuration:
"transports": [
{
"type": "web",
"endpoint": {
"type": "tcp",
"port": 8080
},
"paths": {
"ws": {
"type": "websocket",
"serializers": [
"json"
],
"auth": {
"ticket": {
"type": "dynamic",
"authenticator": "com.example.authenticate"
}
}
}
}
}
]