I have generated a server using swagger editor. Then I move to using Tornado as http-server like:
def main():
app = App(system_manager=system_manager, import_name=__name__,
specification_dir='./swagger/', server='tornado')
app.app.json_encoder = encoder.JSONEncoder
app.add_api('swagger.yaml', arguments={
'title': 'API'}, pythonic_params=True)
app.run(port=8085)
Where App is:
class App(connexion.App):
def __init__(self, system_manager, import_name, server='tornado', **kwargs):
super(App, self).__init__(import_name, server=server, **kwargs)
if not issubclass(type(system_manager), SystemManager):
raise ValueError(
"App.init: 'system_manager' is not a subclass of 'SystemManager'")
self.__system_manager = system_manager
def run(self, port=None, server=None, debug=None, host=None, **options):
server_type = server or self.server
if server_type != 'tornado':
super(App, self).run(port=port, server=server,
debug=debug, host=host, **options)
return None
if port is not None:
self.port = port
elif self.port is None:
self.port = 5000
self.host = host or self.host or '0.0.0.0'
if server is not None:
self.server = server
if debug is not None:
self.debug = debug
wsgi_container = tornado.wsgi.WSGIContainer(self.app)
http_server = tornado.web.Application([
(r'/websocket/.*', WebSocket, dict(system_manager=self.__system_manager)),
(r'^/v1/wifi(/all)*$', AsyncFallbackHandler,
dict(fallback=wsgi_container)),
(r'.*', tornado.web.FallbackHandler, dict(fallback=wsgi_container))
], websocket_ping_interval=5)
http_server.listen(self.port, address=self.host)
tornado.ioloop.IOLoop.instance().start()
For some reasons I have some endpoints that are taking ~30secs to respond and because I am using WSGIContainer all the requests are synchronous. Which means that every request comming after those ones will be dealyed until they are done. Cite from documentation:
WSGI is a synchronous interface, while Tornado's concurrency model is
based on single-threaded asynchronous execution. This means that
running a WSGI app with Tornado's WSGIContainer is less scalable than
running the same app in a multi-threaded WSGI server like gunicorn or
uwsgi.
I have tried to:
Keep using WSGIContainer but in a handler that will make calls async. Didn't work out. I'm getting: RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-0_0'
class AsyncFallbackHandler(tornado.web.RequestHandler):
def initialize(
self, fallback: Callable[[httputil.HTTPServerRequest], None]
) -> None:
self.fallback = fallback
async def prepare(self, *args, **kwargs):
await self.run_in_executor()
self._finished = True
self.on_finish()
async def run_in_executor(self):
loop = tornado.ioloop.IOLoop.instance().asyncio_loop
done, pending = await asyncio.wait(
fs=[loop.run_in_executor(None, self.fallback, self.request)],
return_when=asyncio.ALL_COMPLETED
)
Create another RequestHandler which is not using WSGIContainer. But here when the request is 404 it fails to json encode ConnexionResponse. Neither I can write a ConnexionResponse to pipe as it has to be string/bytes/dict.
class WifiRequestHandler(tornado.web.RequestHandler):
async def get(self, *args, **kwargs):
# await tornado.gen.sleep(20)
ids = self.get_arguments('ids')
method = get_wifi
if self.request.path.startswith('/v1/wifi/all'):
method = get_wifi_all
self.write(await self.run_in_executor(method, ids))
self.set_header('Content-Type', 'application/json')
async def post(self, *args, **kwargs):
logger.info(kwargs)
body = tornado.escape.json_decode(self.request.body)
self.write(await self.run_in_executor(update_wifi, body))
self.set_header('Content-Type', 'application/json')
async def run_in_executor(self, method, *args):
loop = tornado.ioloop.IOLoop.instance().asyncio_loop
done, pending = await asyncio.wait(
fs=[loop.run_in_executor(None, method, args)],
return_when=asyncio.ALL_COMPLETED
)
result = done.pop().result()
if type(result) is ConnexionResponse:
return result
enc = JSONEncoder()
return enc.encode(result)
Please help me find a way to make some of my endpoints asynchronous
I made my second solution to work by unpacking the ConnexionResponse and setting the status_code. This is how it looks after fix:
async def run_in_executor(self, method, *args):
loop = tornado.ioloop.IOLoop.instance().asyncio_loop
done, pending = await asyncio.wait(
fs=[loop.run_in_executor(None, method, args)],
return_when=asyncio.ALL_COMPLETED
)
result = done.pop().result()
if type(result) is ConnexionResponse:
self.set_status(result.status_code)
return result.body
enc = JSONEncoder()
return enc.encode(result)
Related
Is there a way to call a consumer by post_save signal?
that's my consumer and i don't know how to call it, what should i put in the parameters of it?
in models:
#receiver(post_save, sender=Message)
def SendMessage(sender, instance, **kwargs):
consumer = ChatConsumer(room_id=instance.receiver.pk)
ChatConsumer.receive(self=consumer, pk=instance.pk)
my consumer:
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_id = self.scope['url_route']['kwargs']['pk']
self.room_group_name = 'chat_%s' % self.room_id
# Join room
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from web socket
async def receive(self, pk):
pk = pk
print(pk)
self.room_id = pk
self.room_group_name = 'chat_%s' % self.room_id
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'pk': pk,
}
)
# Receive message from room group
async def chat_message(self, event):
pk = event['pk']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'pk': pk
}))
when i try this, i get this error:
object.init() takes exactly one argument (the instance to initialize)
if anyone still looking, I solved it here's my consumers full code
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from asgiref.sync import sync_to_async, async_to_sync
from django.db.models.signals import *
from django.dispatch import receiver
from .models import Message
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_id = self.scope['url_route']['kwargs']['pk']
self.room_group_name = 'chat_%s' % self.room_id
# Join room
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from room group
async def chat_message(self, event):
message_pk = event["message_pk"]
print("received" + str(message_pk))
# Send message to WebSocket
await self.send(text_data=json.dumps({'pk': message_pk}))
#receiver(post_save, sender=Message)
def SendMessage(sender, instance, **kwargs):
pk = instance.receiver.pk
message_pk = instance.pk
room_group_name = 'chat_%s' % pk
async_to_sync(channel_layer.group_send)(
room_group_name,
{'type': 'chat_message','message_pk': message_pk,})
I'm trying to send messages to specific websocket instances, but neither channel_layer.send, nor using channel_layer.group_send with unique groups for each instance seems to be working, no errors are being raised, they just aren't received by the instances.
The function that sends the message is:
def listRequest(auth_user, city):
request_country = city["country_name"]
request_city = city["city"]
request_location = request_city +", "+request_country
concatenate_service_email = auth_user.service + "-" + auth_user.email
this_request = LoginRequest(service_and_email=concatenate_service_email, location=request_location)
this_request.generate_challenge()
this_request.set_expiry(timezone.now() + timezone.timedelta(minutes=5))
this_request.save()
channel_layer = get_channel_layer()
print(auth_user.current_socket)
async_to_sync(channel_layer.group_send)(
auth_user.current_socket,{
"type": "new.request",
"service_and_email" : concatenate_service_email
},
)
My current working consumers.py (receive and scanrequest don't have anything that's likely to be relevant to the issue):
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from .helpers import verify_identity, unique_group
from django.utils import timezone
from .models import Authentication, LoginRequest
import json
import time
class AuthConsumer(WebsocketConsumer):
account_list =[]
def connect(self):
print("Connect attempted")
print(self.channel_name)
print(unique_group(self.channel_name))
async_to_sync(self.channel_layer.group_add)(unique_group(self.channel_name), self.channel_name)
self.accept()
def disconnect(self, close_code):
print("Disconnect attempted")
async_to_sync(self.channel_layer.group_discard)(unique_group(self.channel_name), self.channel_name)
for i in self.account_list:
serviceEmailSplit = i.split("-")
try:
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
auth_user.set_socket("NONE")
auth_user.save()
except:
print("Error user %s does not exist" %i)
pass
def receive(self, text_data):
print("Receiving data")
if text_data[0:7] == "APPROVE":
data_as_list = text_data.split(",")
serviceEmailSplit = data_as_list[1].split("-")
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
this_request = LoginRequest.objects.get(service_and_email=data_as_list[1],approved=False, expiry__gt=timezone.now())
if verify_identity(auth_user.public_key, data_as_list[2], this_request.challenge):
this_request.set_approved()
self.send("Request Approved!")
else:
self.send("ERROR: User verification failed")
else:
self.account_list = text_data.split(",")
self.account_list.pop(-1)
print(self.account_list)
for i in self.account_list:
serviceEmailSplit = i.split("-")
try:
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
auth_user.set_socket(unique_group(self.channel_name))
auth_user.save()
except:
self.send("Error user %s does not exist" %i)
self.scanRequest()
def scanRequest(self):
requestSet = LoginRequest.objects.filter(service_and_email__in = self.account_list, approved = False, request_expiry__gt = timezone.now())
if requestSet.count() > 0:
for request in requestSet:
self.send(request.service_and_email+","+request.location+","+str(request.challenge))
else:
self.send("NOREQUESTS")
def new_request(self,event):
print("NEW REQUEST!")
this_request = LoginRequest.objects.filter(service_and_email = event["service_and_email"]).latest('request_expiry')
self.send(this_request.service_and_email+","+this_request.location+","+str(this_request.challenge))
And my routing.py:
from django.urls import re_path
from . import consumers
from django.conf.urls import url
websocket_urlpatterns = [
url(r"^ws/$", consumers.AuthConsumer.as_asgi()),
]
"NEW REQUEST!" is never printed, having tried to call it both by sending a message directly, and neither does using groups like I have written above.
My redis server appears to be working from testing like the documentation for the channels tutorial suggests:
https://channels.readthedocs.io/en/stable/tutorial/part_2.html
I'm pretty stumped after attempts to fix it, and I've looked at the other posts on stackoverflow with the same/similar issues and I'm already following whatever solutions they have in my code.
I want to send a message from server to client at 1 second intervals,
writing the string 'send clock' to console before the message is sent.
However, client does not receive "clock" message.
I have got the echo message is working fine, so it's not a connection issue.
How can I send a message at 1 second intervals?
Server code(python):
import threading
import time
import socketio
import eventlet
socket = socketio.Server(async_mode='eventlet')
app = socketio.WSGIApp(socket)
#socket.on('echo')
def echo(sid, message):
socket.emit('echo', message)
def worker1():
eventlet.wsgi.server(eventlet.listen(('', 5030)), app)
def worker2():
while(1):
print("send clock")
socket.emit('clock', '1 sec')
time.sleep(1)
def main():
t1 = threading.Thread(target=worker1)
t2 = threading.Thread(target=worker2)
t1.start()
t2.start()
if __name__ == '__main__':
main()
Client code(nodejs):
const io = require("socket.io-client");
socket = io("http://xxx.xxx.xxx.xxx:5030", {
transports: ["websocket"],
});
socket.emit("echo", "test");
socket.on("echo", (data) => {
console.log(data);
});
socket.on("clock", (data) => {
console.log("receive clock");
});
My server side environment:
python: 3.7.3
python-socketio: 4.5.1
eventlet: 0.25.1
Thank you for reading it until the very end. <3
You can't combine threads or the sleep() function with eventlet, you should use greenlet based equivalents instead. The easiest way to do this is to start your background tasks with the socket.start_background_task() helper function, and sleep with socket.sleep(). I'm writing this by memory so there might be small mistakes, but your application could do something like the following:
import socketio
import eventlet
socket = socketio.Server(async_mode='eventlet')
app = socketio.WSGIApp(socket)
#socket.on('echo')
def echo(sid, message):
socket.emit('echo', message)
def worker1():
eventlet.wsgi.server(eventlet.listen(('', 5030)), app)
def worker2():
while(1):
print("send clock")
socket.emit('clock', '1 sec')
socket.sleep(1)
def main():
socket.start_background_task(worker2)
worker1()
if __name__ == '__main__':
main()
I need to set a header in a post request: ["Authorization": request.token]
I have tried with wslite and with groovyx.net.http.HTTPBuilder but I always get a 401-Not authorized which means that I do cannot set the header right.
I have also thought of logging my request to debug it but I am not able to do that either.
With wslite this is what I do
Map<String, String> headers = new HashMap<String, String>(["Authorization": request.token])
TreeMap responseMap
def body = [amount: request.amount]
log.info(body)
try {
Response response = getRestClient().post(path: url, headers: headers) {
json body
}
responseMap = parseResponse(response)
} catch (RESTClientException e) {
log.error("Exception !: ${e.message}")
}
Regarding the groovyx.net.http.HTTPBuilder, I am reading this example https://github.com/jgritman/httpbuilder/wiki/POST-Examples but I do not see any header setting...
Can you please give me some advice on that?
I'm surprised that specifying the headers map in the post() method itself isn't working. However, here is how I've done this kind of thing in the past.
def username = ...
def password = ...
def questionId = ...
def responseText = ...
def client = new RestClient('https://myhost:1234/api/')
client.headers['Authorization'] = "Basic ${"$username:$password".bytes.encodeBase64()}"
def response = client.post(
path: "/question/$questionId/response/",
body: [text: responseText],
contentType: MediaType.APPLICATION_JSON_VALUE
)
...
Hope this helps.
Here is the method that uses Apache HTTPBuilder and that worked for me:
String encodedTokenString = "Basic " + base64EncodedCredentials
// build HTTP POST
def post = new HttpPost(bcTokenUrlString)
post.addHeader("Content-Type", "application/x-www-form-urlencoded")
post.addHeader("Authorization", encodedTokenString)
// execute
def client = HttpClientBuilder.create().build()
def response = client.execute(post)
def bufferedReader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))
def authResponse = new JsonSlurper().parseText(bufferedReader.getText())
I am using grails-jaxrs for exposing an api which accepts multipart/form-data..
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(['application/json'])
#Path('/addMessage')
ChatMessage addChatMessageWithAttachment(#Context HttpServletRequest req) {
log.debug "> addChatMessageWithAttachment"
def fileStores = []
def chatMessage
GrailsWebRequest request = WebUtils.retrieveGrailsWebRequest()
def params = request.getParams()
if (!(params.messageBody.length() > 0)) {
log.error("Empty message body")
throw new LocalisedException(ErrorCode.CHAT_MESSAGE_CREATE_FAILED)
}
}
The implementation is working properly as expected. I am able to send form-data (with file and other parameters successfully.
Now I am trying to implement integration test for above logic.
I am using IntegrationTestCase to achieve this.. so my code snippet is as below:
class ChatMessageResourceV1Tests extends IntegrationTestCase{
//other implementation and setup ommited
#Test
void "Create new chat message for event id - customer user"() {
def headers = ['Content-Type': 'multipart/form-data', 'Accept': 'application/json']
def data = "My message..."
def cm = new ChatMessage(messageBody: data)
def content = "{'eventId':'$event.id','messageBody':'My message...'}"
sendRequest("/api/v1/chatMessage/addMessage", 'POST', headers, content.bytes)
assertEquals(200, response.status)
}
}
When I run the test.. I can see the call reaches inside the API method.. But however, the parameter messageBody is coming as null and exception is being thrown.
I have tried every possible combination of test.. But no luck.
Any help would be appreciated.