0

I have a Zope/Plone server which has a browser (let's call it thebrowser) which sends signed requests to a ReSTful web service, receives XML, transforms it to HTML and delivers it to the client via AJAX (thus, from the client's point of view, everything takes place on my server). Most of the time this works, but for certain requests it won't, and I can't find out why. The failing requests take some seconds, and the data length is about 100 kB, but we're not talking about minutes or Megabytes here.

Strangely, the ZServerHTTPResponse object looks quite ok, but it arrives at the web client's side (Firefox, Firebug) as an empty string!

The toolchain looks like this:

In a skin, I have a Script (Python):

## Script (Python) "theservice"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=
##title=
##
thebrowser = context.getBrowser('thebrowser')
return thebrowser.get(thedict={'path':'/'.join(traverse_subpath)})

This makes requests to /theservice/some/sub/path work; some/sub/path is put in the path key.

The get method of thebrowser takes the path argument, does the general processing (sending a signed request to https://remote.service.somewhere/some/sub/path), feeds the XML response to the right transform method according to the path, and returns the response, which is used e.g. by a jquery accordion.

The browser thebrowser looks like this (error handling etc. stripped for clarity):

def transformer(func):
    """
    decorator function for generic processing
    """
    def f2(self, txt, req=None, auth_tup=None):
        global HTML_HEAD, HTML_TAIL
        ham = func(self, txt, auth_tup=auth_tup)
        res = list(HTML_HEAD)
        res.extend(ham)
        res.extend(HTML_TAIL)
        return u''.join(res)

    f2.__name__ = func.__name__
    return f2

class Browser(BrowserView):

    def get(self, propagate=True, thedict=None):
        """
        send a request to the remote ReSTful service
        and transform the received XML text to HTML

        pretty URL:
        /theservice/path/as/sub/path
        """
        context = self.context
        request = context.request
        response = request.response
        if thedict is None:
            thedict = request.form
        path = thedict['path']
        # ... set the 'pseudonym', 'theurl' variables ...
        auth_tuples = self._auth_tuples(pseudonym)
        code = None
        text = None
        fo = urlopen(theurl)
        code = fo.code
        headers = fo.info().headers
        # set the headers explicitly; might be unnecessary:
        for line in headers:
            name, text = line.split(':', 1) # found: ': '
            handle_header(response, name, text)
        raw = fo.read().strip()
        text = unicode(raw, 'utf-8')   # -> unicode
        fo.close()
        text = self.transform(path, text, response, theurl, auth_tuples)
        response.setBody(text)
        return response
        # -------------------------------------------------------- get

    def transform(self, path, txt, response, theurl=None, auth_tup=None):
        """
        Choose a transformer function, apply it to the given text,
        and return the HTML text generated
        """
        transformer = self.getTransformer(path) # not included here
        html = transformer(self,                # e.g. tf_xy; see below
                           txt.decode('utf-8'),
                           theurl or path,
                           auth_tup=auth_tup)
        if html is not None:
            response.setHeader('Content-Type', 'text/html; charset=utf-8')
            return html
        return txt.encode('utf-8')

    @transformer
    def tf_xy(self, txt, auth_tup=None):
        """
        transform function for path xy
        """
        root = ElementTree.fromstring(txt)
        res = []
        for child in root.getchildren():
            if child.tag == 'foo':
                res.append(''.join(('<p>', child.text, '</p>'))) # example
            # ...
        return res

When I apply another decorator to the get method to log the results, I get the following information about the ZServerHTTPResponse object:

headers = {'connection': 'close',
           'content-length': '110626',
           'content-type': 'text/html; charset=utf-8',
           'date': 'Wed, 17 Apr 2013 09:04:13 GMT',
           'status': '200 OK',
           'transfer-encoding': 'chunked'}
accumulated_headers = 'Set-Cookie: JSESSIONID=471108159E6EF40EB9F2F7305E77EB69; Path=/; Secure\r\n'
body = '<!DOCTYPE html><html><head><meta h(... 110575 bytes total ...)iv></body></html>'
errmsg = 'OK'
status = 200

... which looks quite reasonable to me. Anyway, for some requests (possibly those with big responses or longest processing times) the client browser sees an empty string.

Versions: Zope 2.10.13, Python 2.4.6 (64bit), Plone 3.3 (yes, we'll switch to Plone 4, but it will take some time...)

Update: I thought my problem was solved when I fixed the ElementTree imports to make use of the compiled version:

try:
    from celementtree import fromstring        # modern Python versions
except ImportError:
    try:
        from cElementTree import fromstring    # Python 2.4
    except ImportError:
        from elementtree import fromstring     # emergency fallback

Unfortunately, this worked for me, but not for the customer.

The Zope site is served in an Apache VirtualHost where the usual RewriteRule is active:

RewriteRule ^/(.*) http://localhost:8082/VirtualHostBase/http/%{HTTP_HOST}:80/theportalroot/VirtualHostRoot/$1 [L,P]

According to Firebug, there is no content-length header present (can this have been stripped by Apache?!). Here are the complete response headers:

Connection          Keep-Alive
Content-Encoding    gzip
Content-Type        text/html; charset=utf-8
Date                Thu, 18 Apr 2013 12:31:41 GMT
Keep-Alive          timeout=15, max=99
Server              Zope/(Zope 2.10.7-final, python 2.4.6, linux2) ZServer/1.1 Plone/3.2.2 ShellEx Server/4.0.0
Set-Cookie          JSESSIONID=308B09957635CA02829AF5C362FB60E3; Path=/; Secure
Transfer-Encoding   chunked
Vary                Accept-Encoding

I try to get the response directly by Zope, but to do this I must apparently access the Zope root, and I have a permission problem there.

Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251
Tobias
  • 2,481
  • 3
  • 26
  • 38
  • 1
    Can you look at a wireshark dump? What does Firebug report for the Content-length header? Do you have intermediate proxies in-between zope/zserver and your browser, or do you also see this behavior on bare connections? Can you duplicate in development environment? – sdupton Apr 17 '13 at 18:50
  • I updated my question to add some more info. The Wireshark part will take me a while ... – Tobias Apr 18 '13 at 12:45
  • There will not be a Content-length header when chunked transfer encoding is used to deliver the response (either by zserver or Apache in your case). Is this a GET request or a POST? I think looking at your Z2.log to verify that the request is a 200 response might confirm that this problem is with Apache (likely, maybe mod_gzip returning a zero-length response or the client choking on chunked gzip content?). Wireshark might help, since I think this problem is either on the client or the reverse-proxy setup you have. – sdupton Apr 18 '13 at 16:28
  • All requests are GET requests so far, and all requests in question yield status 200. Most of the successful requests have the Content-Length header, but some have chunked transfer encoding as well ... – Tobias Apr 19 '13 at 08:35
  • I tried to replace the GET by POST requests. Didn't help, sadly. – Tobias Apr 19 '13 at 12:26
  • You might need to experiment with Apache settings, maybe disabled chunked transfer encoding of the response? I would be fairly certain that Apache or mod_gzip / mod_deflate are getting in your way based on your description. – sdupton Apr 19 '13 at 16:06

0 Answers0