Search code examples
pythonajaxuploadcherrypy

Uploading a file in ajax to CherryPy


I am trying to upload many files at once to my CherryPy server.

I am following this tutorial that shows PHP code on the server side.

The JavaScript part is simple. Here is a summary of what it does:

function FileSelectHandler(e) {
  var files = e.target.files || e.dataTransfer.files;
  for (var i = 0, f; f = files[i]; i++) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "upload", true);
    xhr.setRequestHeader("X_FILENAME", file.name);
    xhr.send(file);
}

I translated the upload.php described in the tutorial into something like this:

def upload(self):
    [...]

When the server receives the request I can see that cherrypy.request.headers['Content-Length'] == 5676 which is the length of the file I'm trying to upload, so I assume the whole file has been sent to the server.

How do I get the content of the file?


Solution

  • At its minimum it looks like the following. Tested in Firefox and Chromium. If you need to support legacy browsers I'd look at some JavaScript library for polyfills and fallback.

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    
    import os
    import shutil
    
    import cherrypy
    
    
    config = {
      'global' : {
        'server.socket_host' : '127.0.0.1',
        'server.socket_port' : 8080,
        'server.thread_pool' : 8,
      }
    }
    
    
    class App:
    
      @cherrypy.expose
      def index(self):
        return '''<!DOCTYPE html>
          <html>
          <head>
            <title>CherryPy Async Upload</title>
          </head>
          <body>
            <form id='upload' action=''>
              <label for='fileselect'>Files to upload:</label>
              <input type='file' id='fileselect' multiple='multiple' />
            </form>
            <script type='text/javascript'>
              function upload(file)
              {
                var xhr = new XMLHttpRequest();
    
                xhr.upload.addEventListener('progress', function(event) 
                {
                  console.log('progess', file.name, event.loaded, event.total);
                });
                xhr.addEventListener('readystatechange', function(event) 
                {
                  console.log(
                    'ready state', 
                    file.name, 
                    xhr.readyState, 
                    xhr.readyState == 4 && xhr.status
                  );
                });
    
                xhr.open('POST', '/upload', true);
                xhr.setRequestHeader('X-Filename', file.name);
    
                console.log('sending', file.name, file);
                xhr.send(file);
              }
    
              var select = document.getElementById('fileselect');
              var form   = document.getElementById('upload')
              select.addEventListener('change', function(event)
              {
                for(var i = 0; i < event.target.files.length; i += 1)
                {
                  upload(event.target.files[i]); 
                }
                form.reset();
              });
            </script>
          </body>
          </html>
       '''
    
      @cherrypy.expose
      def upload(self):
        '''Handle non-multipart upload'''
    
        filename    = os.path.basename(cherrypy.request.headers['x-filename'])
        destination = os.path.join('/home/user', filename)
        with open(destination, 'wb') as f:
          shutil.copyfileobj(cherrypy.request.body, f)
    
    
    if __name__ == '__main__':
      cherrypy.quickstart(App(), '/', config)