To give you an idea of what I am trying to accomplish with Twisted Web and Autobahn websockets: my UI currently sends an initial HTTP GET request with an upgrade to a websocket in the header. Upon reading that in Twisted Web, the connection needs to switch from HTTP to a websocket protocol to pass data back and forth. Note that this websocket upgrade happens on the same port, port 8000.
Does anyone know how I can implement what I am trying to do? Thank you so much.
EDIT: updated code for working example. You can find it here: Payload from POST Request is Cutoff (Twisted Web)
Here is my code using Twisted Web:
class HttpResource(resource.Resource):
isLeaf = 1
def __init__(self):
self.children = {}
self.ws_port = None
print 'resource invoked'
def render_GET(self, request):
print 'render invoked'
if request.getHeader('Sec-WebSocket-Key'):
# Processing the Key as per RFC 6455
key = request.getHeader('Sec-WebSocket-Key')
h = hashlib.sha1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
request.setHeader('Sec-WebSocket-Accept', base64.b64encode(h.digest()))
# setting response headers
request.setHeader('Upgrade', 'websocket')
request.setHeader('Connection', 'Upgrade')
request.setResponseCode(101)
return ''
else:
log("Regular HTTP GET request.")
return "<html><body style='margin: 0; overflow: hidden;'><iframe style='width: 100%; height: 100%; border: none;' src='http://tsa-graphiql.herokuapp.com/'></iframe></body></html>"
def render_POST(self,request):
log("POST request")
request.setResponseCode(200)
def handle_single_query(self, queryData):
log("Handle single query data.")
return
class HttpWsChannel(http.HTTPChannel):
def dataReceived(self, data):
log('Data received:\n{}'.format(data))
if data.startswith('GET'):
# This will invoke the render method of resource provided
http.HTTPChannel.dataReceived(self, data)
if data.startswith('POST'):
http.HTTPChannel.dataReceived(self, data)
else:
"""
Pass binary data to websocket class.
"""
ws_protocol = self.site.ws_factory.protocol(self.site.ws_factory.connection_subscriptions)
log(ws_protocol)
#just echo for now
# self.transport.write(data)
class HttpFactory(Site):
"""
Factory which takes care of tracking which protocol
instances or request instances are responsible for which
named response channels, so incoming messages can be
routed appropriately.
"""
def __init__(self, resource):
http.HTTPFactory.__init__(self)
self.resource = resource
self.ws_factory = WsProtocolFactory("ws://127.0.0.1:8000")
self.ws_factory.protocol = WsProtocol
def buildProtocol(self, addr):
try:
channel = HttpWsChannel()
channel.requestFactory = self.requestFactory
channel.site = self
return channel
except Exception as e:
log("Could not build protocol: {}".format(e))
site = HttpFactory(HttpResource())
if __name__ == '__main__':
reactor.listenTCP(8000, site)
reactor.run()
EDIT 7/8/2017: Here is the new code I am trying below. The websocket messages are received successfully via the onMessage method. However the HTTP requests are not working. The current error I am getting on a GET request is:
<html>
<head><title>404 - No Such Resource</title></head>
<body>
<h1>No Such Resource</h1>
<p>No such child resource.</p>
</body>
</html>
Python code
from twisted.web.server import (
Site,
)
from twisted.internet import reactor
from twisted.web.resource import (
Resource,
)
from autobahn.twisted.websocket import (
WebSocketServerProtocol,
WebSocketServerFactory,
)
from autobahn.twisted.resource import (
WebSocketResource,
)
class WebSocketProtocol(WebSocketServerProtocol):
def onConnect(self, request):
print("WebSocket connection request: {}".format(request))
def onMessage(self, payload, isBinary):
print("onMessage: {}".format(payload))
if __name__ == '__main__':
factory = WebSocketServerFactory()
factory.protocol = WebSocketProtocol
resource = WebSocketResource(factory)
root = Resource()
root.putChild(b"ws", resource)
site = Site(root)
reactor.listenTCP(8000, site)
reactor.run()
Use WebSocketResource to expose a WebSocketServerFactory as part of a Site.
from twisted.web.server import (
Site,
)
from twisted.web.resource import (
Resource,
)
from autobahn.twisted.websocket import (
WebSocketServerProtocol,
WebSocketServerFactory,
)
from autobahn.twisted.resource import (
WebSocketResource,
)
class YourAppProtocol(WebSocketServerProtocol):
def onConnect(self, request):
...
...
def main():
factory = WebSocketRendezvousFactory()
factory.protocol = YourAppProtocol
resource = WebSocketResource(factory)
root = Resource()
root.putChild(b"some-path-segment", resource)
root.putChild(...)
site = Site(root)
reactor.listenTCP(8080, site)
reactor.run()
The problems with truncated request bodies is probably because your upgrade protocol implementation is buggy. There is no framing applied at the level of dataReceived so you can't expect checks like startswith("GET") to be reliable.
Using WebSocketResource and Site gives you the correct HTTP parsing code from Twisted Web and Autobahn while also allowing you to speak WebSocket to a particular URL and regular HTTP to others.
So after reading a little bit on Google, I found this website that explains how to upgrade the HTTP connection to a websocket connection via Autobahn Twisted: Read and Set request headers via Autobahn Twisted.
The code that I was able to get to work is shown below!
from twisted.web.server import (
Site,
)
from twisted.internet import reactor
from twisted.web.resource import (
Resource,
)
from autobahn.twisted.websocket import (
WebSocketServerProtocol,
WebSocketServerFactory,
)
from autobahn.twisted.resource import (
WebSocketResource,
)
class HttpResource(Resource):
isLeaf = True
def render_GET(self, request):
return "<html><body style='margin: 0; overflow: hidden;'><iframe style='width: 100%; height: 100%; border: none;' src='http://tsa-graphiql.herokuapp.com/'></iframe></body></html>"
class WebSocketProtocol(WebSocketServerProtocol):
def onConnect(self, request):
custom_header = {}
if request.headers['sec-websocket-key']:
custom_header['sec-websocket-protocol'] = 'graphql-ws'
return (None, custom_header)
def onMessage(self, payload, isBinary):
print("onMessage: {}".format(payload))
if __name__ == '__main__':
factory = WebSocketServerFactory()
factory.protocol = WebSocketProtocol
resource = WebSocketResource(factory)
root = Resource()
root.putChild("", HttpResource())
root.putChild(b"ws", ws_resource)
site = Site(root)
reactor.listenTCP(8000, site)
Related
I run this code the handshake and connection work fine but I don't get any results after I run the client.even though the code is working perfectly without any issues.
is there any idea to fix the problem?
note: I'm using graphql and Django framework in my project
import asyncio
import graphene
from channels.routing import ProtocolTypeRouter, URLRouter
class Query(graphene.ObjectType):
hello = graphene.String()
#staticmethod
def resolve_hello(obj, info, **kwargs):
return "world"
class Subscription(graphene.ObjectType):
"""Channels WebSocket consumer which provides GraphQL API."""
count_seconds = graphene.Float()
async def resolve_count_seconds(root, info):
for i in range(10):
yield i
await asyncio.sleep(1.)
schema = graphene.Schema(query=Query, subscription=Subscription)
class MyGraphqlWsConsumer(channels_graphql_ws.GraphqlWsConsumer):
"""Channels WebSocket consumer which provides GraphQL API."""
schema = schema
async def on_connect(self, payload):
pass
application = channels.routing.ProtocolTypeRouter({
"websocket": channels.routing.URLRouter([
django.urls.path("graphql/", MyGraphqlWsConsumer.as_asgi()),
])
})
ASGI_APPLICATION = 'graphql_ws.urls.application'
client:
from graphql_client import GraphQLClient
ws = GraphQLClient('ws://localhost:8000/graphql/')
def callback(_id, data):
print("got new data..")
print(f"msg id: {_id}. data: {data}")
query = """
subscription {
countSeconds
}
"""
sub_id = ws.subscribe(query, callback=callback)
Actually i have a python script and soap server running using soap spyne and i need to call that soap api using c++ gsoap client so that the python script will run and get the output as a response to client
i am able to call the api using SOAP UI and python zeep client but when i try to call the client using gsoap it gave me error
DEBUG:spyne.protocol.soap.soap11:ValueError: Deserializing from unicode strings with encoding declaration is not supported by lxml
the generated wsdl file of both gsoap and soap spyne have different namespace also
```python
from spyne import Application, rpc, ServiceBase, Integer, Unicode,String
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication
from spyne.model.complex import ComplexModel
from spyne.model.complex import Array
from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.wsgi import WSGIResource
from twisted.python import log
import sys
sys.path.insert(1,'../cloud-client/slicing')
import speech as t
class caps__CSoapReqBuf(ComplexModel):
stringarray=String
size=Integer
class caps__CSoapResponse(ComplexModel):
__namespace__ = "spyne.examples.hello.soap"
nRetCode=Integer
strResponseData=String
class caps__CSoapRequest(ComplexModel):
__namespace__ = "spyne.examples.hello.soap"
nRequestType = Integer
wstrRequestParam= String
class caps_CCallbackData(ComplexModel):
__namespace__ = "spyne.examples.hello.soap"
nPort=Integer
strFunction = String
class TranscriptionService(ServiceBase):
#rpc(String, String, caps_CCallbackData, caps__CSoapResponse, _returns=Integer)
def caps__SoapRequestString(ctx, function_name, SoapRequest, CallbackData, SoapResponse):
parameters = SoapRequest
list = parameters.split('|')
d = dict(s.split(':') for s in list)
filename = d['path']
samplerate = int(d['sr'])
outputpath = d['outputpath']
# samplerate=parameters.samplerate
if(function_name=='gettranscription'):
print("gettranscription")
out=t.main(filename,samplerate)
SoapResponse.nRetCode=1
SoapResponse.wstrResponseData=out
return 0
elif(function_name=='getocr'):
return "Do OCR"
else:
return "error"
#rpc(caps__CSoapResponse,_returns=Unicode)
def caps_SoapResponseString(ctx,caps__CSoapResponse):
response = caps__CSoapResponse.wstrResponseData
return response
application = Application([TranscriptionService], 'spyne.examples.hello.soap',
in_protocol=Soap11(validator='lxml'),
out_protocol=Soap11())
wsgi_application = WsgiApplication(application)
if __name__ == '__main__':
import logging
from wsgiref.simple_server import make_server
ip = '192.168.0.103'
port = 8090
resource = WSGIResource(reactor, reactor, wsgi_application)
site = Site(resource)
reactor.listenTCP(port, site,interface=ip)
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG)
logging.info("listening to "+ ip +":"+str(port))
reactor.run()
```
Updated
After updating the code following errors follows in response.
``` Response
'<soap11env:Envelope
xmlns:soap11env="http://schemas.xmlsoap.org/soap/envelope/">\n
<soap11env:Body>\n <soap11env:Fault>\n
<faultcode>soap11env:Client.ResourceNotFound</faultcode>\n
<faultstring>Requested resource
\'{http://tempuri.org/caps.xsd/Service.wsdl}\' not
found</faultstring>\n <faultactor></faultactor>\n
</soap11env:Fault>\n </soap11env:Body>\n</soap11env:Envelope>\n'
DEBUG:spyne:gc.collect() took around 40ms.
```
On that front, lxml is a bit strict on the kind of stream it thinks it can process.
I'm persuaded that the right thing to do to solve this conflict is to strip the encoding declaration from the xml string.
You can add a handler for the 'wsgi_call' event to implement this. See the events example to see what the relevant API looks like.
My solution to this is to let lxml fail and parse the payload manually using WebOb and then place it in the user defined context. You can later access ctx.udc to access the data. So:
...
from webob import Request as WebObRequest
...
HOST = '0.0.0.0'
PORT = 8000
# parse payload manually
def _on_wsgi_call(ctx):
req = WebObRequest(ctx.transport.req)
decoded = req.body.decode('utf-8')
envelope = Etree.fromstring(decoded)
# depending on your data, you may need to update this step
element = next(envelope.iter())
ctx.udc = element.text
if __name__ == '__main__':
application = initialize([YourService])
wsgi_application = WsgiApplication(application)
wsgi_application.event_manager.add_listener('wsgi_call', _on_wsgi_call)
resource = WSGIResource(reactor, reactor, wsgi_application)
site = Site(resource)
reactor.listenTCP(PORT, site, interface=HOST)
logging.info('listening on: %s:%d' % (HOST, PORT))
logging.info('wsdl is at: http://%s:%d/?wsdl' % (HOST, PORT))
sys.exit(reactor.run())
I tried to make proxy server by tornado in Python. The simple http proxy server has worked well, but the https proxy has some problem.
Part of my programs which might have problem are below.
import tornado.ioloop
from tornado.web import RequestHandler, Application
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
from tornado.httpserver import HTTPServer
class HTTPSHandler(RequestHandler):
#tornado.web.asynchronous
def get(self):
print self.request.host, self.request.method
def handle_request(response):
if response.error and not isinstance(response.error, tornado.httpclient.HTTPError):
print "Error:", response.error
else:
self.write(response.body)
self.finish(" ")#in case of response.body == None
request = self.request
req = HTTPRequest(url=request.uri, method=request.method,
headers=request.headers, body=request.body,
allow_nonstandard_methods = True, follow_redirects = False,
validate_cert=True)
http_client = AsyncHTTPClient()
try:
http_client.fetch(req, handle_request)
except Exception as e:
print e
#tornado.web.asynchronous
def post(self):
return self.get()
#tornado.web.asynchronous
def head(self):
return self.get()
#tornado.web.asynchronous
def delete(self):
return self.get()
#tornado.web.asynchronous
def patch(self):
return self.get()
#tornado.web.asynchronous
def put(self):
return self.get()
#tornado.web.asynchronous
def options(self):
return self.get()
if __name__ == "__main__":
app2 = Application([(r"https:.*", HTTPSHandler),])
httpsServer = HTTPServer(app2, ssl_options = {
"certfile": "./server.crt",
"keyfile": "./server.key",
})
app2.listen(444)
tornado.ioloop.IOLoop.instance().start()
It outputs a WARNING like below (when I access to https://www.google.com and https://github.com)
WARNING:tornado.access:405 CONNECT www.google.co.jp:443 (127.0.0.1) 0.69ms
WARNING:tornado.access:405 CONNECT github.com:443 (127.0.0.1) 0.58ms
Finally, web pages which use https protocol could not be displayed with browser error.
ERR_TUNNEL_CONNECTION_FAILED
I guess, this is caused by the tornado’s requestHandler because it does not support CONNECT method.
My question is how can I use the CONNECT method?
I have noticed the way to fix this problem.
At first, SUPPORTED_METHOD should be written in HTTPSHandler class.
This can solve the 405 WARNING and browser error.
class HTTPSHandler(RequestHandler):
SUPPORTED_METHODS = ("CONNECT", "GET", "HEAD", "POST", "DELETE", "PATCH", "PUT", "OPTIONS")
#tornado.web.asynchronous
def get(self):
This is written in official document as follows.
If you want to support more methods than the standard GET/HEAD/POST, you
should override the class variable ``SUPPORTED_METHODS`` in your
`RequestHandler` subclass.
Moreover, to handle and process the CONNECT method request, additional method is needed in HTTPSHandler class.
#tornado.web.asynchronous
def connect(self):
print "some specific processings here"
Finally, I took stupid mistakes in regular expressions and ssl_option.
app2 = Application([(r"https:.*", HTTPSHandler),]) # not correct
app2 = Application([(r".*", HTTPSHandler),]) # correct
httpServer = HTTPServer(app2) # ssl_options is not needed
In theory you should be able to implement the CONNECT method in the same way that WebSocketHandler works, by hijacking the underlying connection's IOStream. But be warned that this is uncharted territory; the HTTP proxy protocol has some differences from plain HTTP and I don't know how well it will work to implement a proxy on top of a normal application-level HTTP service.
Below I provided a code example which simply respond to HTTP GET request with the data from Redis:
Request: http://example.com:8888/?auth=zefDWDd5mS7mcbfoDbDDf4eVAKb1nlDmzLwcmhDOeUc
Response: get: u'"True"'
The purpose of this code is to serve as a REST server (that's why I'm using lazyConnectionPool) responding to the requests, and using data from Redis (read/ write).
What I need to do:
Run multiple requests to Redis inside render_GET of the IndexHandler (like GET, HMGET, SET, etc)
Run multiple requests in a transaction inside render_GET of the IndexHandler
I've tried multiple ways to do that (including examples from the txredisapi library), but due to lack of experience failed to do that. Could you please advise on questions 1) and 2).
Thanks in advance.
import txredisapi as redis
from twisted.application import internet
from twisted.application import service
from twisted.web import server
from twisted.web.resource import Resource
class Root(Resource):
isLeaf = False
class BaseHandler(object):
isLeaf = True
def __init__(self, db):
self.db = db
Resource.__init__(self)
class IndexHandler(BaseHandler, Resource):
def _success(self, value, request, message):
request.write(message % repr(value))
request.finish()
def _failure(self, error, request, message):
request.write(message % str(error))
request.finish()
def render_GET(self, request):
try:
auth = request.args["auth"][0]
except:
request.setResponseCode(404, "not found")
return ""
d = self.db.hget(auth, 'user_add')
d.addCallback(self._success, request, "get: %s\n")
d.addErrback(self._failure, request, "get failed: %s\n")
return server.NOT_DONE_YET
# Redis connection parameters
REDIS_HOST = '10.10.0.110'
REDIS_PORT = 6379
REDIS_DB = 1
REDIS_POOL_SIZE = 1
REDIS_RECONNECT = True
# redis connection
_db = redis.lazyConnectionPool(REDIS_HOST, REDIS_PORT, REDIS_DB, REDIS_POOL_SIZE)
# http resources
root = Root()
root.putChild("", IndexHandler(_db))
application = service.Application("web")
srv = internet.TCPServer(8888, server.Site(root), interface="127.0.0.1")
srv.setServiceParent(application)
Regarding first question:
There is a few ways to generalize to making multiple database requests in a single HTTP request.
For example you can make multiple requests:
d1 = self.db.hget(auth, 'user_add')
d2 = self.db.get('foo')
Then you can get a callback to trigger when all of these simultaneous requests are finished (see twisted.internet.defer.DeferredList).
Or you can use inlineCallbacks if you need sequential requests. For example:
#inlineCallbacks
def do_redis(self):
foo = yield self.db.get('somekey')
bar = yield self.db.hget(foo, 'bar') # Get 'bar' field of hash foo
But you will need to read more about combining inlineCallbacks with twisted.web (there are SO questions on that topic you should look up).
Regarding question 2:
Transactions are really ugly to do without using inlineCallbacks. There is an example at txredisapi homepage that shows it using inlineCallbacks.
I'm developing a twisted.web server - it consists of some resources that apart from rendering stuff use adbapi to fetch some data and write some data to postgresql database. I'm trying to figoure out how to write a trial unittest that would test resource rendering without using net (in other words: that would initialize a resource, produce it a dummy request etc.).
Lets assume the View resource is a simple leaf that in render_GET returns NOT_DONE_YET and tinkers with adbapi to produce simple text as a result. Now, I've written this useless code and I can't come up how to make it actually initialize the resource and produce some sensible response:
from twisted.trial import unittest
from myserv.views import View
from twisted.web.test.test_web import DummyRequest
class ExistingView(unittest.TestCase):
def test_rendering(self):
slug = "hello_world"
view = View(slug)
request = DummyRequest([''])
output = view.render_GET(request)
self.assertEqual(request.responseCode, 200)
The output is... 1. I've also tried such approach: output = request.render(view) but same output = 1. Why? I'd be very gratefull for some example how to write such unittest!
Here's a function that will render a request and convert the result into a Deferred that fires when rendering is complete:
def _render(resource, request):
result = resource.render(request)
if isinstance(result, str):
request.write(result)
request.finish()
return succeed(None)
elif result is server.NOT_DONE_YET:
if request.finished:
return succeed(None)
else:
return request.notifyFinish()
else:
raise ValueError("Unexpected return value: %r" % (result,))
It's actually used in Twisted Web's test suite, but it's private because it has no unit tests itself. ;)
You can use it to write a test like this:
def test_rendering(self):
slug = "hello_world"
view = View(slug)
request = DummyRequest([''])
d = _render(view, request)
def rendered(ignored):
self.assertEquals(request.responseCode, 200)
self.assertEquals("".join(request.written), "...")
...
d.addCallback(rendered)
return d
Here is a DummierRequest class that fixes almost all my problems. Only thing left is it does not set any response code! Why?
from twisted.web.test.test_web import DummyRequest
from twisted.web import server
from twisted.internet.defer import succeed
from twisted.internet import interfaces, reactor, protocol, address
from twisted.web.http_headers import _DictHeaders, Headers
class DummierRequest(DummyRequest):
def __init__(self, postpath, session=None):
DummyRequest.__init__(self, postpath, session)
self.notifications = []
self.received_cookies = {}
self.requestHeaders = Headers()
self.responseHeaders = Headers()
self.cookies = [] # outgoing cookies
def setHost(self, host, port, ssl=0):
self._forceSSL = ssl
self.requestHeaders.setRawHeaders("host", [host])
self.host = address.IPv4Address("TCP", host, port)
def addCookie(self, k, v, expires=None, domain=None, path=None, max_age=None, comment=None, secure=None):
"""
Set an outgoing HTTP cookie.
In general, you should consider using sessions instead of cookies, see
L{twisted.web.server.Request.getSession} and the
L{twisted.web.server.Session} class for details.
"""
cookie = '%s=%s' % (k, v)
if expires is not None:
cookie = cookie +"; Expires=%s" % expires
if domain is not None:
cookie = cookie +"; Domain=%s" % domain
if path is not None:
cookie = cookie +"; Path=%s" % path
if max_age is not None:
cookie = cookie +"; Max-Age=%s" % max_age
if comment is not None:
cookie = cookie +"; Comment=%s" % comment
if secure:
cookie = cookie +"; Secure"
self.cookies.append(cookie)
def getCookie(self, key):
"""
Get a cookie that was sent from the network.
"""
return self.received_cookies.get(key)
def getClientIP(self):
"""
Return the IPv4 address of the client which made this request, if there
is one, otherwise C{None}.
"""
return "192.168.1.199"