I wanted to be able to receive multiple response from a server after sending one single request. This is implemented all in twisted.
The Server:
class HandleReq(resource.Resource):
def __init__(self):
resource.Resource.__init__(self)
def render_GET(self, request):
"""
Here I basically connect to another server and get multiple
responses"""
d = defer.Deferred()
interface = RemoteService(request, i_deferred)
self._connect_to_RemoteService(bf_command, interface)
self.handleCallbacks(i_deferred, request)
return server.NOT_DONE_YET
def render_POST(self, request):
'''to make sure both GET/POST are handled'''
return self.render_GET(request)
def handleCallbacks(self, d, req):
msg = d.addCallback(self.getEvent)
d.addCallback(self.postResponse(req, msg))
return None
def getEvent(self, msg):
return msg
def postResponse(self, request, response):
def post(event):
request.setHeader('Content-Type', 'application/json')
request.write(response)
request.finish()
self.postResponse(request, response)
return server.NOT_DONE_YET
return post
And the Client:
from urllib2 import URLError, HTTPError
api_req = 'http://localhost:8000/req' + '?' + urllib.urlencode({"request": request})
req = urllib2.Request(api_req)
try:
response = urllib2.urlopen(api_req)
except HTTPError, e:
print 'Problem with the request'
print 'Error code: ', e.code
except URLError, e:
print 'Reason: ', e.reason
else:
j_response = json.loads(response.read())
Basically what I want is that the server not to close the connection (request.finish()), but instead to continue sending responses; and the client should be able to receive those messages.
HTTP does not work this way. An HTTP request has exactly one response. Twisted Web will not let you send more than one response, because that would be against the HTTP specification and no HTTP clients would be able to figure out what was going on.
There may be another way to accomplish your underlying goals, but whatever it is, it won't involve sending more than one HTTP response to a single HTTP request.
Related
Hello I'm trying to do a real time friend request notification system and having this weird looking json response. I'm new to backend development and django (1st year software engineering student). Im just wondering if this is normal since i havent seen anything like this and if theres a way to fix it. Ive worked on a chat app before but it was just all text messages and so I got confused when it comes to django models. I have tried multiple ways I found but only this works. I think it might be because I called json.dumps twice but if i remove either of them, it wont work. Thank you
When a user sends a friend request, this is what i got back from the web socket(with double \ for each field)
Heres the code
//views.py
class SendRequestView(views.APIView):
permission_class = (permissions.IsAuthenticated,)
def post(self, request, *args, **kwargs):
receiver_username = self.kwargs['receiver_username']
if receiver_username is not None:
receiver = get_object_or_404(User, username=receiver_username)
request = ConnectRequest.objects.create(sender=self.request.user, receiver=receiver)
notification = ConnectNotification.objects.create(type='connect request', receiver=receiver, initiated_by=self.request.user)
channel_layer = get_channel_layer()
channel = f'notifications_{receiver.username}'
async_to_sync(channel_layer.group_send)(
channel, {
'type': 'notify',
'notification': json.dumps(ConnectNotificationSerializer(notification).data, cls=DjangoJSONEncoder),
}
)
data = {
'status': True,
'message': 'Success',
}
return JsonResponse(data)
// consumer.py
class ConnectNotificationConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
user = self.scope['user']
group_layer = f'notifications_{user.username}'
await self.accept()
await self.channel_layer.group_add(group_layer, self.channel_name)
async def disconnect(self, close_code):
user = self.scope['user']
group_layer = f'notifications_{user.username}'
await self.channel_layer.group_discard(group_layer, self.channel_name)
async def notify(self, event):
notification = event['notification']
await self.send(text_data=json.dumps({
'notification': notification
})
)
I'm hoping to avoid any use of Celery at the moment. In Starlette's docs they give two ways to add background tasks:
Via Graphene: https://www.starlette.io/graphql/
class Query(graphene.ObjectType):
user_agent = graphene.String()
def resolve_user_agent(self, info):
"""
Return the User-Agent of the incoming request.
"""
user_agent = request.headers.get("User-Agent", "<unknown>")
background = info.context["background"]
background.add_task(log_user_agent, user_agent=user_agent)
return user_agent
Via a JSON response: https://www.starlette.io/background/
async def signup(request):
data = await request.json()
username = data['username']
email = data['email']
task = BackgroundTask(send_welcome_email, to_address=email)
message = {'status': 'Signup successful'}
return JSONResponse(message, background=task)
Does anyone know of a way to add tasks to Starlette's background with Ariadne? I am unable to return a JSONResponse in my resolver, and I do not have access to a info.context["background"]. The only thing I have attached to my context is my request object.
Solved!
Starlette Middleware:
class BackgroundTaskMiddleware(BaseHTTPMiddleware):
async def dispatch(
self, request: Request, call_next: RequestResponseEndpoint
) -> Response:
request.state.background = None
response = await call_next(request)
if request.state.background:
response.background = request.state.background
return response
Ariadne Resolver:
#query.field("getUser")
#check_authentication
async def resolve_get_user(user, obj, info):
task = BackgroundTasks()
task.add_task(test_func)
task.add_task(testing_func_two, "I work now")
request = info.context["request"]
request.state.background = task
return True
async def test_func():
await asyncio.sleep(10)
print("once!!")
async def testing_func_two(message: str):
print(message)
The functions still execute synchronously, but because they're background tasks I'm not too worried.
More discussion here.
The above which is marked as a solution does not work for me since BackgroundTask does not work properly when you use a middleware that subclasses BaseHTTPMiddleware see here:
https://github.com/encode/starlette/issues/919
In my case basically the task is not ran in the background and it is awaited to be completed, also I am not using Ariadne, but this should let you do the job and run a task in the background
Edit:
This worked for me.
executor = ProcessPoolExecutor()
main.executor.submit(
bg_process_funcs,
export_file_format,
export_headers,
data,
alert_type,
floor_subtitle,
date_subtitle,
pref_datetime,
pref_timezone,
export_file_name,
export_limit,)
executor.shutdown()
logger.info("Process Pool Shutdown")
I have written a service which takes a json as input. I am using the website hurl.it to send post requests to check. Below is my code snippet:
class BatchSemanticSimilarityHandler(tornado.web.RequestHandler):
def post(self):
self.set_header('Access-Control-Allow-Origin', '*')
self.set_header('Access-Control-Allow-Credentials', 'true')
self.set_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
self.set_header('Access-Control-Allow-Headers','Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token')
data = json.loads(self.request.body)
apikey = data["apikey"]
try:
UA = self.request.headers["User-Agent"]
except:
UA = "NA"
if bool(usercoll.find_one({"apikey":apikey})) == True:
sentence = data["sentence"]
sentence_array = data["sentence_array"]
n = data["num_of_results"]
if sentence is None or sentence_array is [] or apikey is None or n is None:
self.set_status(200)
output = {"error":[{"code":334,"message":"Bad Input data"}]}
misscoll.insert({"apitype":"batchsemanticsimilarity","timestamp":datetime.datetime.now(), "ip":self.request.remote_ip, "useragent":UA, "uri":self.request.uri,"apikey":apikey, "output":output, "input":{"s1":sentence,"s2":sentence_array}})
self.write(output)
return
results = nb.get_similar(sentence, sentence_array, apikey, n)
print "results is",results
output = {"similar_sentences": results, 'credits':'ParallelDots'}
hitscoll.insert({"apitype":"batchsemanticsimilarity","timestamp":datetime.datetime.now(), "ip":self.request.remote_ip, "useragent":UA, "uri":self.request.uri,"apikey":apikey, "output":output, "input":{"s1":sentence,"s2":sentence_array}})
self.write(output)
return
else:
rejectcoll.insert({"apitype":"batchsemanticsimilarity","apikey":apikey,"timestamp":datetime.datetime.now(), "ip":self.request.remote_ip, "useragent":UA, "url":self.request.uri})
self.write({"error":[{"code":333,"message": "Bad Authentication data"}]})
return
The json that I am giving as the body of the request is as below:
{
"sentence": "BJP leads in Bengaluru civic body`s poll, all eyes on JD(S)",
"sentence_array": [
"Narendra Modi is the prime minister",
"Sonia Gandhi runs Congress",
"Sachin is a good batsman"
],
"apikey": "DyMe1gSNhvMV1I1b20a7KARYIwuQX5GAQ",
"num_of_results": 2
}
I have verified on jsonlint that this is a valid JSON.
However while sending the request it gives me below error:
ValueError: No JSON object could be decoded
Can anyone please help me sort this out!!
The JSON object that you are passing in POST request is encoded into the url.
JSON library cannot read the encoded data.So you need to decode the url first.
Decoding of url can be done using urlparse library in python.so you need something like this.
post_data=urlparse.parse_qsl(self.request.body)
According to your need of final format to read there are various methods in urlparse.check this
or
As specified in the docs you can override a method to enable JSON parsing
def prepare(self):
if self.request.headers["Content-Type"].startswith("application/json"):
self.json_args = json.loads(self.request.body)
else:
self.json_args = None
check this
i want to have custom json response after data post sendind to my Tastypie API models django.
class MyModelResource(ModelResource):
my_field=""
class Meta:
queryset = MyModel.objects.all()
resource_name = 'nick_name'
authentication = ApiKeyAuthentication()
authorization = DjangoAuthorization()
def hydrate(self, bundle):
#on recupere les donnée injectée par bundle.data['title']
#et on inject les donnée via bundle.obj.title
#bundle.data['my_field'] ="1234"
bundle.obj.my_field=bundle.data['my_field']
self.my_field = bundle.data['my_field']
return bundle
def wrap_view(self, view):
"""
Wraps views to return custom error codes instead of generic 500's
"""
#csrf_exempt
def wrapper(request, *args, **kwargs):
try:
callback = getattr(self, view)
response = callback(request, *args, **kwargs)
if request.is_ajax():
patch_cache_control(response, no_cache=True)
lst_dic=[]
mon_dic = dict(success=True, my_field=self.my_field
)
# response is a HttpResponse object, so follow Django's instructions
# to change it to your needs before you return it.
# https://docs.djangoproject.com/en/dev/ref/request-response/
lst_dic.append(mon_dic)
response = HttpResponse(simplejson.dumps(lst_dic), content_type='application/json')
return response
except (BadRequest, fields.ApiFieldError), e:
return HttpBadRequest({'success':False,'code': 666, 'message':e.args[0]})
except ValidationError, e:
# Or do some JSON wrapping around the standard 500
return HttpBadRequest({'success':False,'code': 777, 'message':', '.join(e.messages)})
except Exception, e:
# Rather than re-raising, we're going to things similar to
# what Django does. The difference is returning a serialized
# error message.
return self._handle_500(request, e)
return wrapper
My problem here, i can't grab the self.my_field value to put in mon_dic, i always have data object, not value...
thx for help
EDIT : Add my_field global variable, and then grab value from bundle that's it ;)
Maybe I am not understanding what you want to do here. But wrap_view is for handling customer error responses. If all you want to do is return the data that was posted, you can set always_return_data to true in your Meta:
class Meta:
always_return_data = True
Or if you want to control what data gets sent back, you can use the dehydrate method:
def dehydrate(self, bundle):
bundle.data['custom_field'] = "Whatever you want"
return bundle
I am using webapp2 for development in App Engine. What I would like to do is to send a custom JSON formatted response in case of an error. For example when the request length is larger that a threshold, to respond with HTTP 400 and response body
{'error':'InvalidMessageLength'}
In webapp2, there is the option to assign error handlers for certain exceptions. For example:
app.error_handlers[400] = handle_error_400
Where handle_error_400 is the following:
def handle_error_400(request, response, exception):
response.write(exception)
response.set_status(400)
When webapp2.RequestHandler.abort(400) is executed, the above code is executed.
How is it possible to have different response formats (HTML and JSON) dynamically based on the above setup? That is, how it is possible to call different versions of handle_error_400 function?
Here is a fully working example that demonstrates how to have the same error handler for all kind of errors and if your URL starts with /json then the response will be an application/json (use your imagination on how you could make a good use of the request object to decide what kind of response you should provide):
import webapp2
import json
def handle_error(request, response, exception):
if request.path.startswith('/json'):
response.headers.add_header('Content-Type', 'application/json')
result = {
'status': 'error',
'status_code': exception.code,
'error_message': exception.explanation,
}
response.write(json.dumps(result))
else:
response.write(exception)
response.set_status(exception.code)
app = webapp2.WSGIApplication()
app.error_handlers[404] = handle_error
app.error_handlers[400] = handle_error
In the above example you can easily test the different behaviours by visting the following URLs that will return a 404 which is the easiest error to test:
http://localhost:8080/404
http://localhost:8080/json/404