Nested web service requests with tornado (async?)

I am using SOAP web service using tornado (and third party tornado module). One of the operations in my service should call the other, so I have a chain:

  • External request in (via SOAPUI) for activity A
  • Internal request (via request module) to activity B
  • Internal response from operation B
  • External response from operation A

Since everything is running in the same service, it gets blocked somewhere. I am not familiar with asynchronous tornado functionality.

There is only one way to process a request (post) because everything happens at one URL and then a specific operation (the method that does the processing) is performed based on the value of the SOAPAction request header. I decorated my post method with @ tornado.web.asynchronous and called self.finish () at the end but not cubes.

Can a tornado handle this scenario, and if so, how to implement it?

EDIT (code added):

class SoapHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def post(self):
        """ Method post() to process of requests and responses SOAP messages """
        try:
            self._request = self._parseSoap(self.request.body)
            soapaction = self.request.headers['SOAPAction'].replace('"','')
            self.set_header('Content-Type','text/xml')
            for operations in dir(self):
                operation = getattr(self,operations)
                method = ''
                if callable(operation) and hasattr(operation,'_is_operation'):
                    num_methods = self._countOperations()
                    if hasattr(operation,'_operation') and soapaction.endswith(getattr(operation,'_operation')) and num_methods > 1:
                        method = getattr(operation,'_operation')
                        self._response = self._executeOperation(operation,method=method)
                        break
                    elif num_methods == 1:
                        self._response = self._executeOperation(operation,method='')
                        break
            soapmsg = self._response.getSoap().toprettyxml()
            self.write(soapmsg)
            self.finish()
        except Exception as detail:
            #traceback.print_exc(file=sys.stdout)
            wsdl_nameservice = self.request.uri.replace('/','').replace('?wsdl','').replace('?WSDL','')
            fault = soapfault('Error in web service : {fault}'.format(fault=detail), wsdl_nameservice)
            self.write(fault.getSoap().toxml())
            self.finish()

      

This is the post method from the request handler. This is from the webservices module I'm using (not my code), but I've added an asynchronous decorator and self.finish (). All it basically does is invoke the correct operation (as dictated by the SOAPAction of the request).

class CountryService(soaphandler.SoapHandler):
    @webservice(_params=GetCurrencyRequest, _returns=GetCurrencyResponse)
    def get_currency(self, input):
        result = db_query(input.country, 'currency')
        get_currency_response = GetCurrencyResponse()
        get_currency_response.currency = result
        headers = None
        return headers, get_currency_response

    @webservice(_params=GetTempRequest, _returns=GetTempResponse)
    def get_temp(self, input):
        get_temp_response = GetTempResponse()
        curr = self.make_curr_request(input.country)
        get_temp_response.temp = curr
        headers = None
        return headers, get_temp_response

    def make_curr_request(self, country):

        soap_request = """<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:coun='CountryService'>
   <soapenv:Header/>
   <soapenv:Body>
      <coun:GetCurrencyRequestget_currency>
         <country>{0}</country>
      </coun:GetCurrencyRequestget_currency>
   </soapenv:Body>
</soapenv:Envelope>""".format(country)

        headers = {'Content-Type': 'text/xml;charset=UTF-8', 'SOAPAction': '"http://localhost:8080/CountryService/get_currency"'}
        r = requests.post('http://localhost:8080/CountryService', data=soap_request, headers=headers)
        try:
            tree = etree.fromstring(r.content)
            currency = tree.xpath('//currency')
            message = currency[0].text
        except:
            message = "Failure"
        return message

      

These are two web service operations (get_currency and get_temp). So the SOAPUI gets caught in get_temp, which makes the SOAP get_currency request (via make_curr_request and the request module). The results should then simply be routed back and sent back to SOAPUI.

The actual operation of the service doesn't make sense (return currency on temperature request), but I'm just trying to get the functionality to work and these are the operations I have.

+3


source to share


2 answers


I don't think your soap module or requests are asynchronous.

I believe adding an @asyncronous decorator is only half the battle. Right now, you are not making any asynchronous requests inside your function (every request is blocked, which binds the server until your method completes)

You can toggle this using tornados AsynHttpClient . This can be used as an exact replacement for queries. From the example docoumentation :

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        http = tornado.httpclient.AsyncHTTPClient()
        http.fetch("http://friendfeed-api.com/v2/feed/bret",
                   callback=self.on_response)

    def on_response(self, response):
        if response.error: raise tornado.web.HTTPError(500)
        json = tornado.escape.json_decode(response.body)
        self.write("Fetched " + str(len(json["entries"])) + " entries "
                   "from the FriendFeed API")
        self.finish()

      



Their method is decorated with async AND they make asyn http requests. This is where the flow gets a little weird. When you use AsyncHttpClient it doesn't block the event loop (PLease, I just started using tornado this week, take it easy if all my terms are not correct yet). This allows the server to handle incoming requests freely. When your asynchttp request is complete, a callback method will be executed, in this case on_response

.

Here you can easily replace requests with tornado asynchttp client. Things can get trickier for your soap service, however. You can make a local website around your soap client and make asynchronous requests to it using the tornado asyn http client.

This will create some complex callback logic that can be fixed with gen

decorator

+6


source


This issue has been fixed since yesterday.

Weighting request: https://github.com/rancavil/tornado-webservices/pull/23

Example: Here's a simple web service that takes no arguments and returns a version. Please note that you must:

  • Method declaration: decorate a method with @gen.coroutine

  • Returned results: use raise gen.Return(data)



code:

from tornado import gen
from tornadows.soaphandler import SoapHandler
...

class Example(SoapHandler):
    @gen.coroutine
    @webservice(_params=None, _returns=Version)
    def Version(self):
        _version = Version()
        # async stuff here, let suppose you ask other rest service or resource for the version details.
        # ...
        # returns the result.
        raise gen.Return(_version)

      

Hooray!

0


source







All Articles