Search code examples
javascriptpythonfile-uploadxmlhttprequestbottle

How to make my HTTP request behave the same as a form


I'd need some help with my HTTP request. Here's the setup:

  1. A webpage load an image to a form and send it to a python server running bottle (with the form or a custom http request)
  2. Bottle receive the file, give it as an input for a python script, receive the result and return it to the webpage

On bottle's website there's an example with a form: https://bottlepy.org/docs/dev/tutorial.html#file-uploads I've tried it and it works. Here's the code I used:

<html>
  <head>
  </head>   
  <body>
    <form action="http://localhost:8080/solve" method="POST" enctype="multipart/form-data" norm="form" id='myForm'>
      Select a file: <input type="file" name="upload"/>
      <input type="submit" value="Start upload" />
    </form>
  </body>     
</html>

In bottle I have:

@route('/solve', method='POST')
def solve():
    file     = request.files.get('upload')
    name, ext = os.path.splitext(file.filename)
    if ext not in ('.png','.jpg','.jpeg'):
        return 'File extension not allowed.'
    print(file.name)
    resolved = sudoku.solve(file.file)
    return str(resolved)

This "works", but the form redirects me to localhost:8080 and it's not what I want. I tried putting the target to a hidden iFrame, which prevent the redirection, but I don't manage to access the result in the body of the iFrame...

What I want: Make an HTTP request similar to the one made by the form. So I tried:

<html>

<head> </head>

<body>
  <form enctype="multipart/form-data" norm="form" id="myForm">
    Select a file:
    <input id="fileInput" type="file" name="upload" accept="image/png, image/jpeg, image/jpg" />
    <input type="submit" value="Start upload" />
    <label class="button-upload" onclick="send()">Upload</label>
  </form>

</body>
<script>
  var _file = null;

  function send() {
    var file = document.getElementById("fileInput").files[0]
    console.log(file)
    var url = "http://localhost:8080/solve";

    var xhr = new XMLHttpRequest();
    xhr.open("POST", url, true);
    xhr.setRequestHeader(
      "Content-Type",
      "multipart/form-data; boundary=---------------------------169461201884497922237853436"
    );
    var formData = new FormData();

    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4 && xhr.status == 200) {
        alert(xhr.responseText);
      }
    };
    formData.append("upload", file);
    xhr.send(formData);
  }
</script>

</html>

I've checked with the developper tool in network and the request seems to be the same as the one sent by the form, though bottle can't find the file.

The file = request.files.get('upload') returns None and file = request.files returns <bottle.FormsDict object at 0x7ff437abf400> so there's something but I don't understand how to access it!

Any help would be greatly appreciated!


Solution

  • Your JavaScript code seems fine, except for where you set request headers with xhr.setRequestHeader. FormData handles multipart encoding for you, you don't need to set request headers manually. I just tried it, and it seems to be working fine with bottlepy.

    Overall, change your send() function as follows:

    function send() {
      var file = document.getElementById("fileInput").files[0]
      console.log(file)
      var url = "http://localhost:8080/solve";
    
      var xhr = new XMLHttpRequest();
      xhr.open("POST", url, true);
      var formData = new FormData();
    
      xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
          alert(xhr.responseText);
        }
      };
      formData.append("upload", file);
      xhr.send(formData);
    }