bigskysoftware / htmx

</> htmx - high power tools for HTML
https://htmx.org
Other
38.36k stars 1.31k forks source link

HTMX Form multipart fails to upload file in production - Empty request - Both [2.0.3] and [1.9.3] !! #3008

Open LordPraslea opened 5 days ago

LordPraslea commented 5 days ago

When trying to upload a file via HTMX with multipart, it fails to do anything and just posts an empty request without any object/request information. (Empty request)

I've been trying to debug this and cannot seem to find any reason why HTMX fails to upload or send any object data for this particular field. The file I'm trying to upload is between 40 and 60 MB.

On a local testing environment everything works flawlessly, same code/binary gets deployed to the webserver and then upload fails

Same issue occurs in both versions of HTMX "1.9.3" and upgraded to "2.0.3" seems to be the same thing.

I've been using firefox 115 and librewolf 131.

I should mention that I added hx-post and hx-encoding while debugging, previously, only the action field existed, and a hx-boost exists somewhere at top level.

I added the progress and on localhost it seems to function properly, on production it does something but then quickly jumps to full and a 400 HTTP error gets returned by the server.

htmx-failure-upload

Example code

    <form id="upload-form" action={ templ.SafeURL(fmt.Sprintf(`/uploads/new`)) }  hx-post={ fmt.Sprintf(`/uploads/new`) }  method="POST" class="form signup"
            enctype="multipart/form-data"  hx-encoding="multipart/form-data" hx-target="#uploads-modal-inner">
        for _, err := range form.NonFieldErrors {
      <div class="notification is-danger is-light">{err}</div>
        }
            <!-- Include the CSRF token -->
            <input type="hidden" name="csrf_token" value={data.CSRFToken}> 

            if !form.New  {
            <input type="hidden" name="id" value={ c.Tostr(form.ID) }>
        <input type="hidden" name="uuid" value={ form.UUID.String() }>
            }

..... description  input field & misc 
... file_type select field & misc..

    <div class="field">
      <div class="file is-info has-name">
        <label class="file-label">

          <input placeholder="File" type="file" name="file" 
            class={ "file-input", templ.KV("is-danger", form.FieldErrors["File"]) }   />

          <span class="file-cta">
            <span class="file-icon">
              <i class="fas fa-upload"></i>
            </span>
            <span class="file-label"> Select File to Upload </span>
          </span>
          // if form.File != "" {
            <span class="file-name">   { form.File } </span>
          // }
        </label>
      </div>

    </div>  

    <progress id="progress" value="0" max="100"></progress>
    <div class="field is-grouped">
      <div class="control">
            <input type="submit" class="button is-primary" if form.New {         value="Upload File"          } else {          value="Edit uploaded file"     }  >
      </div>
    </div>
    </form>
    <script>
        htmx.on('#upload-form', 'htmx:xhr:progress', function(evt) {
          htmx.find('#progress').setAttribute('value', evt.detail.loaded/evt.detail.total * 100)
        });
    </script>
Telroshan commented 4 days ago

Hey, sounds weird, especially as you say it works fine locally, and as the Content-Length header seems to be correct (~40MB). Some ideas out loud:

It sounds like a backend issue to me, as htmx has no reason to work differently between your 2 environments here. Hope this helps you investigate!

LordPraslea commented 3 days ago

I reviewed tens of existing and past HTMX bug reports, added, removed various configs So I went in a debugging investigation frenzy and ended up even adding hx-headers for CORS, so I could go further on the backend and investigate further (although CORS WAS already in the input but nothing was sent via HTMX). Logs on the backend where as if they did NOT reach the current function. This is what I found weird and made me think of an HTMX problem.

If a upload is lower than 5MB, it gets sent through.. If it's larger (say 20~50 mb) it fails. There is an empty request sent. On multiple browsers i had the same thing.

I then set a timeout in HTMX with hx-request for 1000 seconds.. and HTMX eventually gave an error which cancelled the upload giving a server context error. (

HTMX error

SyntaxError: JSON.parse: expected double-quoted property name at line 1 column 20 of the JSON data
    parseJSON /static/js/htmx.js:804
    getValuesForElement static/js/htmx.js:3792
    issueAjaxRequest https://mastery.linsublim.com/static/js/htmx.js:4302
  .... 30more lines
htmx.js:2963:15

Upon closer examination in the proxy config nothing seemed to time out, this lead me to investigate the backend and it does seem that there was a timeout setting somewhere. It was not clear to me if it was a readheader timeout or a simple read timeout so when I increased it to a certain point it seemed to work for certain uploads.

It might be useful for HTMX to give an error if it can't upload, gets no response or gets a non 200 response especially when uploading. Because i'm thinking of the many cases when intermediary proxies CAN block. Luckily for me, caddy has sane defaults and no timeout exists and it was easier to debug.

UPDATE: IT seems that even with updated timeouts, it still fails to upload and still sends the empty request so I still need to review this.