Search code examples
pythontornado

Downloading a file served by a tornado web server


This is how I have my tornado web server currently defined:

application = tornado.web.Application([
    tornado.web.url(r"/server", MainHandler),
    tornado.web.url(r"/(.*)", tornado.web.StaticFileHandler, { "path": scriptpath,  "default_filename": "index.html" }),
])

index.html is the start page of a web based gui. It will communicate with the backend server through http:///server and the requests by the gui to the server are handled by the MainHandler function.

The directory structure looks like:

root_directory/
    server.py
    fileiwanttodownload.tar.gz
    index.html

I would like to be able to type into the browser:

http:///data/fileiwanttodownload.tar.gz

and have the file delivered to me as a regular file download.

What I have tried to do is:

application = tornado.web.Application([
    tornado.web.url(r"/server", MainHandler),
    tornado.web.url(r"/data", tornado.web.StaticFileHandler, { "path": scriptpath } ),
    tornado.web.url(r"/(.*)", tornado.web.StaticFileHandler, { "path": scriptpath,  "default_filename": "index.html" }),
])

But this is not working for reasons that are probably obvious to those who know the answer.

The only clue I have is the following error message:

Uncaught exception GET /data (192.168.4.168)
HTTPServerRequest(protocol='http', host='192.168.4.195:8888', method='GET', uri='/data', version='HTTP/1.1', remote_ip='192.168.4.168', headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9', 'Host': '192.168.4.195:8888', 'Accept-Encoding': 'gzip, deflate', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Connection': 'keep-alive', 'Accept-Language': 'en-us'})
Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/tornado/web.py", line 1445, in _execute
    result = yield result
  File "/usr/local/lib/python3.4/dist-packages/tornado/gen.py", line 1008, in run
    value = future.result()
  File "/usr/local/lib/python3.4/dist-packages/tornado/concurrent.py", line 232, in result
    raise_exc_info(self._exc_info)
  File "<string>", line 3, in raise_exc_info
  File "/usr/local/lib/python3.4/dist-packages/tornado/gen.py", line 267, in wrapper
    result = func(*args, **kwargs)
TypeError: get() missing 1 required positional argument: 'path'

Solution

  • scriptpath you haven't shown, is probably wrong. In path you should provide root dir to the files, in URI matcher capture only the file or so. Simple example:

    import tornado.ioloop
    import tornado.web
    import os
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("Hello, world")
    
    def make_app():
        script_path = os.path.dirname(__file__)
        return tornado.web.Application([
            (r"/", MainHandler),
            (r"/data/(.*)", tornado.web.StaticFileHandler, {"path": script_path}),
            #         ^ we capture only this part
        ])
    
    if __name__ == "__main__":
        app = make_app()
        app.listen(8888)
        tornado.ioloop.IOLoop.current().start()
    

    As you can run it works, but it is recommended to store static/data files in separate directory, because it is possible to download everything from the app root dir, including python one.

    So put your downloadable file e.g. in data subdirectory and then

    script_path = os.path.join(os.path.dirname(__file__), 'data')
    

    More info about StaticFileHandler.

    edit

    The error you are getting is because in your code /data route has StaticFileHandler, but nothing is captured () from requested path.