Seiichi Yonezawa

Uploading large files

· nzwsch

Introduction to File Browser and background

There is a web server written in Go called File Browser. For a while, I was using MinIO to upload files, but it takes a little extra work to use files uploaded to MinIO directly from another application.

File Browser can display a list of files, as its name suggests, but it also seems to be able to be used as a simple uploader. It is a popular project, with over 26K stars on GitHub, but API documentation is not currently available.

Uploading using Node.js and implementing it in Ruby

You can make some assumptions using the network tab in your browser, but fortunately, the method for Node.JS was left in an issue.

Using this code as a reference, I wrote a simple file uploader using HTTParty:

class Filebrowser
  include HTTParty

  def self.auth(username, password)
    headers = { "Content-Type" => "application/json" }
    body = { username:, password: }.to_json

    post("/api/login", headers:, body:).then do |res|
      new(res.body) if res.success?
    end
  end

  # Store the logged in token
  def initialize(token)
    @token = token
  end

  # If you want to save to `/Media`, specify `/api/resources/Media`
  def upload(file_path, mime_type = "text/plain")
    url = "/api/resources/Media/#{File.basename(file_path)}?override=true"
    headers = { "Content-Type" => mime_type, "X-Auth" => @token }

    self.class.post(url, headers:, body: File.read(file_path))
  end
end

The problem with large files

This uploader works fine for relatively small files. But what if the file you want to upload is over 1GB?

2.8 GB video file

First, I prepared a file called video.mp4 with a size of 2.8 GB and uploaded it.

Resource monitor

As you might expect, this program uses 2.8 GB of memory.

If you have plenty of memory like in a desktop environment, there won’t be much of a problem running this program, but if you run this program on a server with limited resources, it will be affected by the size of the file you upload.

A solution using stream processing in Ruby

Node.JS has fs.createReadStream(), but what should we do in Ruby?

class Filebrowser
  def upload(file_path)
    url = "/api/resources/Media/#{File.basename(file_path)}?override=true"
    headers = { "Content-Type" => "application/octet-stream",
                "Transfer-Encoding" => "chunked",
                "X-Auth" => @token }

    self.class.post(url, headers:, body_stream: File.open(file_path, "rb"))
  end
end

I specified Content-Type: application-octet-stream and Transfer-Encoding: chunked in the headers, and specified body_stream instead of body.

Improved memory usage

I uploaded the exact same file, but I was able to successfully reduce the overall memory usage to 23.3 MB.

I think this script can be run without any problems regardless of the size of the file.