require 'sinatra/base'
require_relative 'upload_store'
class UploadApp < Sinatra::Base
MAX_BYTES = 50 * 1024 * 1024
configure do
set :store, UploadStore.new(File.expand_path('uploads', __dir__))
end
post '/uploads' do
part = params[:file]
halt 400, 'missing file field' unless part.is_a?(Hash) && part[:tempfile]
tempfile = part[:tempfile]
if tempfile.size > MAX_BYTES
halt 413, 'file too large'
end
begin
stored = settings.store.store(tempfile, part[:filename], limit: MAX_BYTES)
rescue UploadStore::TooLarge
halt 413, 'file too large'
rescue UploadStore::InvalidName
halt 422, 'invalid filename'
end
status 201
content_type :json
{ name: stored.name, bytes: stored.bytes }.to_json
end
end
require 'fileutils'
class UploadStore
CHUNK = 64 * 1024
TooLarge = Class.new(StandardError)
InvalidName = Class.new(StandardError)
Stored = Struct.new(:name, :bytes, :path)
def initialize(dir)
@dir = dir
FileUtils.mkdir_p(@dir)
end
def store(io, original_name, limit:)
name = safe_name(original_name)
final = File.join(@dir, name)
partial = "#{final}.part"
written = 0
io.rewind if io.respond_to?(:rewind)
File.open(partial, 'wb') do |dest|
while (chunk = io.read(CHUNK))
written += chunk.bytesize
raise TooLarge if written > limit
dest.write(chunk)
end
end
File.rename(partial, final)
Stored.new(name, written, final)
ensure
File.delete(partial) if partial && File.exist?(partial) && !File.exist?(final)
end
private
def safe_name(raw)
base = File.basename(raw.to_s).gsub(/[^a-zA-Z0-9._-]/, '_')
raise InvalidName if base.empty? || base == '.' || base == '..'
base
end
end
require 'rack/test'
require 'rspec'
require_relative '../app'
RSpec.describe UploadApp do
include Rack::Test::Methods
def app
UploadApp
end
it 'stores an uploaded file in chunks' do
file = Rack::Test::UploadedFile.new(StringIO.new('hello world'), 'text/plain', original_filename: 'greeting.txt')
post '/uploads', file: file
expect(last_response.status).to eq(201)
body = JSON.parse(last_response.body)
expect(body['name']).to eq('greeting.txt')
expect(body['bytes']).to eq('hello world'.bytesize)
end
it 'rejects files larger than the limit' do
stub_const('UploadApp::MAX_BYTES', 4)
file = Rack::Test::UploadedFile.new(StringIO.new('too many bytes'), 'text/plain', original_filename: 'big.txt')
post '/uploads', file: file
expect(last_response.status).to eq(413)
end
end
This snippet shows how to accept multipart file uploads in Sinatra and write them to disk in fixed-size chunks instead of loading the whole body into memory. Buffering large uploads is a common source of RSS spikes and OOM kills under concurrency, so the goal is to keep a bounded amount of data resident at any moment regardless of upload size.
The app.rb tab defines the Sinatra application. When a multipart/form-data request arrives, Rack has already parsed the field into a hash where :tempfile is a Tempfile that Rack spooled the part into. Rather than calling params[:file][:tempfile].read (which materializes the entire file as a String), the route hands the tempfile to UploadStore#store, which copies it in bounded chunks. The route validates the presence of the part, guards against path traversal, and rejects files whose declared size exceeds MAX_BYTES before any writing begins.
The UploadStore tab is the heart of the pattern. Its store method opens the destination with File.open(..., 'wb') and loops with IO.copy_stream-style semantics implemented manually via read(CHUNK) so the enforced byte ceiling can be applied mid-stream. Writing goes to a temporary .part file first and is atomically renamed with File.rename only after the full copy succeeds; a partial upload therefore never appears under its final name, and a failed transfer is cleaned up in an ensure block. safe_name collapses the client-supplied filename to its basename and strips anything but a small allowlist of characters, which is what actually stops ../../etc/passwd style attacks — the controller never trusts the original name.
The spec/upload_spec.rb tab uses Rack::Test to drive the route with Rack::Test::UploadedFile, asserting both the success path and that an oversized upload is rejected with 413. Testing through Rack rather than calling the store directly proves the multipart wiring and the size guard behave together.
The main trade-off is that chunked copying is slightly more code than a one-liner read, but it makes memory usage independent of file size and gives a natural place to enforce limits, checksums, or virus scanning. This approach is worth reaching for whenever uploads can be large or the server runs many workers on constrained memory.
Related snips
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
package files
import (
"context"
"time"
Presigned S3 upload URLs (AWS SDK v2)
# Installation
# rails active_storage:install
# rails db:migrate
# config/storage.yml
local:
ActiveStorage for file uploads and attachments
Share this code
Here's the card — post it anywhere.