Nested Bottle app with Waitress #76

Closed
PlaidPhantom opened this Issue Nov 17, 2014 · 5 comments

Projects

None yet

4 participants

@PlaidPhantom

I am building a web application using nested Bottle apps and I'm having problems returning a static file from the nested app when running the site through the Waitress web server. It works fine when run with WSGIRefServer, which suggests a problem with Waitress.

Sample app that replicates the issue below (browse to /test/). If I run the app using WSGIRefServer (commented run command) then the javascript file downloads fine. If i use server="waitress" then /test/js returns a blank response (no errors).

from bottle import Bottle, run, static_file

baseapp = Bottle()
app = Bottle()
baseapp.mount("/test/", app)

@app.get("/")
def index():
    return """<!DOCTYPE html>
<html><body>
<p>Result: <span id="asdf">Failed :(</span></p>
<script src="js"></script>
</body></html>
"""

@app.get("/js")
def js():
    return static_file("test.js", "./")

run(app = baseapp, server="waitress", url="0.0.0.0", port=8080)
#run(app = baseapp, url="0.0.0.0", port=8080)

Contents of test.js:

document.getElementById('asdf').innerHTML = 'Worked! :)';

I'm running Python 3.4.2 with Bottle 0.12.7 and Waitress 0.8.9. (downloaded via pip).

@frankdarcy

I have the same issue,
with Python 2.7.5, Bottle v0.13-dev and Waitress 0.8.10dev (cloned about a month ago)
In my case I see a warning on the server side.
With your sample code and I see the same problem.

WARNING:waitress:application returned too few bytes (0) for specified Content-Length (58) via app_iter

Seems to be to do with how waitress implements 'wsgi.file_wrapper'

In the bottle app, if I remove 'wsgi.file_wrapper' from request.environ then it all works fine.
Like so ..

@app.get("/js")
def js():
    if 'wsgi.file_wrapper' in request.environ:
        del request.environ['wsgi.file_wrapper']
    return static_file("test.js", "./")

This causes an iterable file wrapper to be created, which avoids the problem,
but its only a workaround, a fix would be great.

@frankdarcy

Have looked into this a little deeper and I'm not convinced this is a waitress bug,
could be a bottle bug, would love to hear opinions from others with more WSGI knowledge and experience. Here's what I've found ...

waitress implements 'wsgi.file_wrapper' using the class ReadOnlyFileBasedBuffer
which inherits from FileBasedBuffer which implements:

def __nonzero__(self):
    return self.remain > 0

So this means that until self.remain is set to something greater than zero,
the iterator returned by 'wsgi.file_wrapper' evaluates to False
self.remain does not get set until prepare() is called.

This becomes a problem if used in a nested bottle app
because bottle wraps such apps in its mount method with the mountpoint_wrapper() function
which checks the response from the nested bottle app as follows:

body = app(request.environ, start_response)
if body and rs.body: body = itertools.chain(rs.body, body)
rs.body = body or rs.body
return rs

In our case, the above wrapper is called before ReadOnlyFileBasedBuffer.prepare() is called,
so body (which contains the ReadOnlyFileBasedBuffer object) evaluates to False and so it is not returned in rs.body

I have found that adding self.remain = 1 to ReadOnlyFileBasedBuffer.__init__ avoids the bug in this case, but ...

Why should bottle check whether or not body is True or False? Should it simple check it is an iterable object?

@frankdarcy frankdarcy referenced this issue in bottlepy/bottle Dec 23, 2014
Closed

Nested Bottle app with Waitress #678

@frankdarcy

From defnull in Bottle Issue 678

A non-empty iterator should not evaluate to False just because it does not know if it is empty or not.

If waitress' maintainers agree then can we fix this?

@djamelfel

I have got the same issue when I try to mount a plugin on my web application:
WARNING:waitress:application returned too few bytes (0) for specified Content-Length (1142) via app_iter

Do you know how I can work around this issue until this is fixed upstream?

@bertjwregeer bertjwregeer added a commit to bertjwregeer/waitress that referenced this issue Feb 7, 2015
@bertjwregeer bertjwregeer Remove __nonzero__ and bool from FileBasedBuffer
FileBasedBuffer is the parent class for a variety of sub-classes,
including ReadOnlyFileBasedBuffer. Since FileBasedBuffer can be used as
an app_iter return (and by extension it's sub-classes can as well) it
should probably behave more like an iterator, and the file returned from
open().

Even if an iterator contains no more objects, calling bool() will still
return True, in the case of FileBasedBuffer this is not true until this
fix.

----

Without this fix, if an application calls a second WSGI application that
returns an wsgi.file_wrapper (aka ReadOnlyFileBasedBuffer) they can't
simply test to see if the application returned a valid iterator or not
by testing it's truthiness.

This is documented in bug report:
Pylons#76

While one could argue that testing the truthiness of a return from an
WSGI application is the wrong thing to do, I would argue that an
iterator like object should not return False when tested with bool.
bool(iter([])) == True after all.
eaeab64
@frankdarcy

Workaround ...
In the bottle app, if I remove 'wsgi.file_wrapper' from request.environ then it all works fine.
Like so ..

@app.get("/js")
def js():
    if 'wsgi.file_wrapper' in request.environ:
        del request.environ['wsgi.file_wrapper']
    return static_file("test.js", "./")
@davisagli davisagli added the bug label Apr 13, 2015
@bertjwregeer bertjwregeer added a commit to bertjwregeer/waitress that referenced this issue Dec 21, 2015
@bertjwregeer bertjwregeer Remove __nonzero__ and bool from FileBasedBuffer
FileBasedBuffer is the parent class for a variety of sub-classes,
including ReadOnlyFileBasedBuffer. Since FileBasedBuffer can be used as
an app_iter return (and by extension it's sub-classes can as well) it
should probably behave more like an iterator, and the file returned from
open().

Even if an iterator contains no more objects, calling bool() will still
return True, in the case of FileBasedBuffer this is not true until this
fix.

----

Without this fix, if an application calls a second WSGI application that
returns an wsgi.file_wrapper (aka ReadOnlyFileBasedBuffer) they can't
simply test to see if the application returned a valid iterator or not
by testing it's truthiness.

This is documented in bug report:
Pylons#76

While one could argue that testing the truthiness of a return from an
WSGI application is the wrong thing to do, I would argue that an
iterator like object should not return False when tested with bool.
bool(iter([])) == True after all.
7be6c83
@bertjwregeer bertjwregeer closed this in #82 Mar 16, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment