william-os4y / fapws3

Fast Asynchronous Python Web Server (based on libev)
GNU General Public License v2.0
341 stars 38 forks source link

Incompatible with static_file in bottlepy #37

Open yangzhe1990 opened 12 years ago

yangzhe1990 commented 12 years ago

fapws3 will print the below line when I'm using static_file() in bottlepy

wsgi output of is neither a list, neither a fileobject, neither an iterable object!!!!!

Demo code can be found here: http://bottlepy.org/docs/dev/tutorial.html#routing-static-files

btw, return a static_file("index.html", root = ".").output which is a file object also fails. Even return an open("index.html") fails. Only return open("index.html").readlines() works.

p.s. Thank you for putting my name on the contributor list

william-os4y commented 12 years ago

So it sounds that your file object does not match the PyFile_check :-(. https://github.com/william-os4y/fapws3/blob/master/fapws/mainloop.c#L552

Which release of Fapws are you using ?

yangzhe1990 commented 12 years ago

I'm using the newest fapws3. It's compiled with python2.7-dev and the python version I'm using is also 2.7

william-os4y commented 12 years ago

I'm not a bottle's expert, neither user, but what I see in the bottle code is that:

Thus Fapws's complaints is logic.

Concerning your initial comment ... return a file object must work. For example return open("index.html", "rb") is working on my several Fapws's projects.

yangzhe1990 commented 12 years ago

Thank you. I'll find some time to test why returning a file object didn't work. Maybe bottle's @ did something.

An additional question to ask: Does HTTPResponse object conform to WSGI?

defnull commented 10 years ago

Bottle unpacks HTTPResponse objects in Bottle._cast(). The result should be one of the following four options:

1) A file object created by open(..., 'rb'). 2) A generator returned by _file_iter_range() on range requests. 3) A string on errors. 4) An instance of environ['wsgi.file_wrapper'] if defined.

All four should be WSGI conform.

defnull commented 10 years ago

Actually, because fapws3 does not implement environ['wsgi.file_wrapper'], an instance of WSGIFileWrapper may be returned. That object looks like a file-object, but it is not a real one. It is still iterable and conforms to WSGI.

Perhaps that helps.

william-os4y commented 10 years ago

I've the feeling that both Fapws and bottle use the name "static file", but implement it differently :-(.

If I read the Bootle code, I see that static_file return an HTTPResponse object, where fapws expect a file object. At least recognized by PyFile_Check.

In fact, Fapws treats objects responding "true" to the following: PyList_Check, PyTuple_Check, PyFile_Check, PyIter_Check.

Could we not get an intermediary object building the bridge between both systems ? Does this is your idea, when you talk about file_wrapper ? Are you talking about a code like this ? if 'wsgi.file_wrapper' in environ: return environ['wsgi.file_wrapper'](filelike, block_size) else: return iter(lambda: filelike.read(block_size), '')

(copy/past from http://www.python.org/dev/peps/pep-0333/)

On Sat, Nov 9, 2013 at 10:50 PM, Marcel Hellkamp notifications@github.comwrote:

Actually, because fapws3 does not implement environ['wsgi.file_wrapper'], an instance of WSGIFileWrapper may be returned. That object looks like a file-object, but it is not a real one. It is still iterable and conforms to WSGI.

— Reply to this email directly or view it on GitHubhttps://github.com/william-os4y/fapws3/issues/37#issuecomment-28137833 .

eivindt commented 7 years ago

As mentioned by @defnull, bottle will cast the HTTPResponse's body in _cast(). Since fapws3 doesn't define a wsgi.file_wrapper, the cast ends up as a WSGIFileWrapper object. This is not a file object, according to PyFile_Check (which is to be expected), but it does not pass the iterator check either, which is more surprising (since that seems to be the point of the WSGIFileWrapper class).

As far as I can see, bottle's WSGIFileWrapper implements container iteration (iter() returns an iterator), whereas PyIter_Check checks for an actual iterator object.

I found 2 workarounds:

  1. Make FapwsServer set wsgi.file_wrapper to this class:
class MyWSGIFileWrapper(WSGIFileWrapper):
        def __iter__(self):
                return self

        def next(self):
                part = self.read(self.buffer_size)
                if not part:
                        raise StopIteration
                return part

(i.e. implement the necessary methods to be recognized as an iterator object)

or 2:

def WSGIFileWrapperWrapper(fp):
        return iter(WSGIFileWrapper(fp))

(i.e. use the iterator object returned by iter).

I'm not sure if any of these are that nice. Making fapws recognize and use a generator as well as an iterator would be nice, but I couldn't find out how.