skip to Main Content

I have a small JS/HTML front-end where i want to be able to send files to a kotlin backend server. The problem I am having at the moment seems like it is a wierd type like convertsion done by javascript.

note: this is code will not to be actually used but more of a learning experiance with server and file system management. For this reason i also dont want to use something like the FormData() object of javascript though i am fine with using Blob.

the problem lies with the JS part :

const parts = files.map((file) => {
    return file.fileData;
 });

When i use the code above everything would work fine, but I only send the actual binary of the file not the extra information like size or a separator. When I try to add something like a string or any other object as far as i have tested like so:

 const parts = files.map((file) => {
    return file.fileData + "---";
  });

The code instead of sending the binary, will just send this:

[object File]---[object File]---[object File]---

This is not any type of special syntax that I have used. That is the actual String it sends over (I’ve just copy and pasted).

Below is all the code i am using:

FRONT END

<!DOCTYPE html>
<html>
  <head>
    <script src="script.js"></script>
    <title>File and Folder Upload</title>
  </head>
  <body>
    <form id="uploadForm" enctype="multipart/form-data"></form>
      <h1>File and Folder Upload</h1>
      <input
        type="file"
        id="folder"
        name="folder[]"
        directory=""
        webkitdirectory=""
        mozdirectory=""
      />
      <button onclick="uploadFiles()">Upload</button>
    </form>
  </body>
</html>

JASCRIPT

function uploadFiles() {
  const folderInput = document.getElementById("folder");
  var files = [];

  for (let i = 0; i < folderInput.files.length; i++) {
    const file = folderInput.files[i];
    files.push({
      length: file.size,
      fileName: file.name,
      fileData: file,
    });
  }

  const parts = files.map((file) => {
    return file.fileData;
  });

  var blob = new Blob(parts);

  fetch("http://localhost:8000/upload", {
    method: "POST",
    headers: {
      "Content-Type": "multipart/form-data",
    },
    body: blob,
  })
    .then((response) => {
      if (response.ok) {
        console.log("Files uploaded successfully");
        response.text().then((data) => {
          console.log("Return body data: ", data);
        });
      } else {
        console.error("Error uploading files");
      }
    })
    .catch((error) => {
      console.error("Error:", error);
    });

  return false;
}

BACKEND Kotlin

import com.sun.net.httpserver.HttpServer
import com.sun.net.httpserver.HttpHandler
import com.sun.net.httpserver.HttpExchange
import java.io.IOException
import java.io.OutputStream
import java.net.InetSocketAddress
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.FileOutputStream
import java.io.File

class MyServer {
    private lateinit var server: HttpServer

    fun startServer() {
        try {
            server = HttpServer.create(InetSocketAddress(8000), 0)
            server.createContext("/upload", PutRemoteFiles())
            server.executor = null
            server.start()
            val host = server.address
            println("Server started at http://$host")
        } catch (e: IOException) {
            println("Error starting the server: ${e.message}")
        }
    }

    fun stopServer() {
        server.stop(0)
        println("Server stopped")
    }

    private class PutRemoteFiles : HttpHandler {
        override fun handle(exchange: HttpExchange) {
            // Set CORS headers
            val headers = exchange.responseHeaders
            headers.add("Access-Control-Allow-Origin", "*")
            headers.add("Access-Control-Allow-Methods", "POST")
            headers.add("Access-Control-Allow-Headers", "Content-Type")

            if (exchange.requestMethod == "POST") {
                try {
                    val requestBody = exchange.requestBody
                    val reader = BufferedReader(InputStreamReader(requestBody))
                    val formData = reader.readText()
                    writeBinFile(formData)
                    val response = "Received form data: $formData"
                    exchange.sendResponseHeaders(200, response.length.toLong())
                    val os: OutputStream = exchange.responseBody
                    os.write(response.toByteArray())
                    os.close()
                } catch (e: IOException) {
                    println("Error reading the request body: ${e.message}")
                    exchange.sendResponseHeaders(500, 0)
                    exchange.responseBody.close()
                }
            } else {
                val response = "Invalid request"
                exchange.sendResponseHeaders(400, response.length.toLong())
                val os: OutputStream = exchange.responseBody
                os.write(response.toByteArray())
                os.close()
            }
        }
    }
}

fun main() {
    val server = MyServer()
    server.startServer()
    println("Server started. Type 'stop' to stop the server.")
    var command = readLine()
    while (command != "stop") {
        command = readLine()
    }
    server.stopServer()
}

fun writeBinFile(file: String) {
    val fileName = "DataBase/output.bin"
    val content = file.toByteArray()
    try {
        val fileOutputStream = FileOutputStream(fileName)
        fileOutputStream.write(content)
        fileOutputStream.close()
        println("File '$fileName' is written successfully.")
    } catch (e: IOException) {
        println("An error occurred while writing the file: ${e.message}")
    }
}

2

Answers


  1. Chosen as BEST ANSWER

    I ended up fixing this by repasenting everything as binary data so that javascript never needs to use type conversions that could never return a [object Type].

    This was done using the File API and a binary array like so:

    function uploadFiles() {
      const folderInput = document.getElementById("folder");
      const files = [];
    
      for (let i = 0; i < folderInput.files.length; i++) {
        const file = folderInput.files[i];
        files.push({
          length: file.size,
          fileName: file.name,
          fileData: file,
        });
      }
    
      function readAsBinary(file) {
        return new Promise((resolve, reject) => {
          const reader = new FileReader();
          reader.onloadend = () => {
            resolve(reader.result);
          };
          reader.onerror = reject;
          reader.readAsArrayBuffer(file);
        });
      }
    
      Promise.all(files.map((file) => readAsBinary(file.fileData)))
        .then((binaryDataArray) => {
          // Combine the binary data arrays with the delimiter
          let combinedBinary = new Uint8Array();
          binaryDataArray.forEach((binaryData, index) => {
            const fileInfo = `---------${files[index].fileName}---------${files[index].length}---------
    `;
            const fileInfoArray = new TextEncoder().encode(fileInfo);
            combinedBinary = new Uint8Array([...combinedBinary, ...fileInfoArray]);
            combinedBinary = new Uint8Array([
              ...combinedBinary,
              ...new Uint8Array(binaryData),
            ]);
          });
    
          const blob = new Blob([combinedBinary]);
    
          fetch("http://localhost:8000/upload", {
            method: "POST",
            body: blob,
          })
            .then((response) => {
              if (response.ok) {
                console.log("Files uploaded successfully");
                response.text().then((data) => {
                  console.log("Return body data: ", data);
                });
              } else {
                console.error("Error uploading files");
              }
            })
            .catch((error) => {
              console.error("Error:", error);
            });
        })
        .catch((error) => {
          console.error("Error reading files:", error);
        });
    }
    

    the TextEncoder was also a used

    const fileInfoArray = new TextEncoder().encode(fileInfo);
    

    converts to a Uint8Array so it stays the same type and not a [object String]


  2. if i where you i would have created a general purpose solution that works for more than just your own upload usecase where html defines the logic and the javascript just enhance the experiences.

    i would for instance just listen to the onsubmit event rather than on a click handler as that would allow you to use the constraint validation as well.

    and then i would also had used the FormData

    addEventListener('submit', async evt => {
      const form = /** @type {HTMLFormElement} */ (evt.target)
    
      if (!form.matches('[data-ajaxify]')) return
      
      // Prevent the form from submitting the normal way
      evt.preventDefault()
    
      const response = await fetch(form.target, {
        method: form.method,
        body: new FormData(form)
      })
    
      const text = await response.text()
    
      if (response.ok) {
        console.log("Return body data: ", text)
      } else {
        console.error("Error submitting form!")
      }
    })
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search