Upload a file to a HTTP endpoint in POST

It seems I can’t get to understand this topic, since it’s a variation of this other old post of mine.
Unfortunately that solution cannot be applied here.

What I am trying to accomplish is to upload a file from a remote web-page to my App, submitting it as a form action POST.

So I have written for testing this very simple upload form:

<!DOCTYPE HTML>
<html>
<head>
  <title>My Upload</title>
</head>
<body>
<form action="https://my-app-name.anvil.app/_/api/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="name_of_file"/>
<input type="submit" value="Upload"/>
</form>
</body>
</html>

I use it to upload a 1-line-txt file to my api endpoint, that is this:

@anvil.server.http_endpoint(path="/upload")
def upload(**querystring):
  request = anvil.server.request
  result = 'QUERYSTRING:{qs}\nPATH:{path}\nQUERY:{query}\nFORM:{form}\nHEADERS:{headers}\nBODY:\n-content type:{content_type}\n-length:{length}\n-name:{name}\n-getbytes:{bytestr}\nBODY_JSON:{body_json}'.format(
    qs = querystring,
    path = request.path,
    query = request.query_params,
    form = request.form_params,
    headers = request.headers,
    content_type = request.body.content_type,
    length = request.body.length,
    name = request.body.name,
    bytestr = request.body.get_bytes(),
    body_json = request.body_json
  )
  return(result)

When I fire the “upload” button in the html form, the http endpoint is hit and the response I get in the browser is:

QUERYSTRING:{}
PATH:/upload
QUERY:{}
FORM:{}
HEADERS:{'origin': 'https://my-app-name.anvil.app', 'sec-fetch-site': 'same-origin', 'host': 'platform-server:3000', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36', 'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryp6DsIiMuewtzMNii', 'cookie': 'anvil-test-cookie=true; anvil-test-cookie=true; _ga=GA1.2.xxxx.yyyy; _gid=GA1.2.xxxx.yyyy; ring-session=xxxxxyyyy', 'content-length': '206', 'upgrade-insecure-requests': '1', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'accept-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7', 'ssl-client-verify': 'NONE', 'sec-fetch-dest': 'empty', 'x-forwarded-for': 'x.x.x.x', 'accept-encoding': 'gzip, deflate, br', 'sec-fetch-mode': 'same-origin', 'x-real-ip': 'x.x.x.x', 'cache-control': 'max-age=0', 'anvil-host': 'my-app-name.anvil.app'}
BODY:
-content type:multipart/form-data; boundary=----WebKitFormBoundaryp6DsIiMuewtzMNii
-length:206
-name:None
-getbytes:b'------WebKitFormBoundaryp6DsIiMuewtzMNii\r\nContent-Disposition: form-data; name="name_of_file"; filename="file.txt"\r\nContent-Type: text/plain\r\n\r\nMy file contents\r\n------WebKitFormBoundaryp6DsIiMuewtzMNii--\r\n'
BODY_JSON:None

My questions are again:

  1. why are form parameters not available in request.form_params?
  2. why is request.body_json empty?
  3. in general why request.body is the only attribute with meaningful content? I don’t think the standard way to do this is to parse the MIME multipart structure to get the file.

Thanks for your help.

Finally I found a solution, don’t know if it’s THE solution but couldn’t find another.
It looks like the only way is actually parsing the Multipart MIME content contained in request.body but, after a looooong googling session, that worked out to be simpler than expected.

In server code I need to import

from requests_toolbelt.multipart import decoder

and then it’s as simple as:

    multipart_bytes = request.body.get_bytes()
    content_type = request.body.content_type
    if len(decoder.MultipartDecoder(multipart_bytes, content_type).parts) > 0:
        uploaded_file = decoder.MultipartDecoder(multipart_bytes, content_type).parts[0]

uploaded_file is a standard bytes array object you can use to build a BlobMedia and then it’s Anvil’s world.

BR

4 Likes