🌈📄⬆️💩
austingil.com | @heyAustinGil
🌈📄⬆️💩
austingil.com | @heyAustinGil
I work at Akamai (akamai.com)
We offer a lot of relevant services
Really hard not to mention (kind of my job)
Impossible to be 100% unbiased
This is not a sales pitch in disguise
I’m here to teach practical, general concepts
I may mention Akamai because it’s familiar
But this info applies anywhere
Protocol for hypermedia communication between distributed computers.
Required: Method, Path, HTTP version
Optional: Headers, Body
GET / HTTP/1.1
Host: austingil.com
Accept: */*
Accept-Encoding: gzip
Connection: keep-alive
Say hi to your dog for me<form>
<button>Upload</button>
</form><form method="post">
<button>Upload</button>
</form><form method="post">
File:
<input type="file" />
<button>Upload</button>
</form><form method="post">
<label for="file">File:</label>
<input type="file" id="file" />
<button>Upload</button>
</form><form method="post" enctype="multipart/form-data">
<label for="file">File:</label>
<input type="file" id="file" name="file" />
<button>Upload</button>
</form><form method="post" enctype="multipart/form-data">
<label for="file">File:</label>
<input type="file" id="file" name="file" />
<button>Upload</button>
</form>POST / HTTP/1.1
Content-Type: multipart/form-data;boundary=
---------------------------735323031399963166993862150
Content-Length: 17
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file"; filename="nugget.txt"
Content-Type: text/plain
Who's a good boy?
-----------------------------735323031399963166993862150--fetch or XMLHttpRequest)POST method<input type="file" onchange="handleFileChange">function handleFileChange(event) {
fetch('/some-url', {
method: 'post',
body: event.target.files[0],
});
}<form
method="post"
enctype="multipart/form-data"
onsumit="handleSubmit"
>
<!-- content -->
</form>async function handleSubmit(event) {
const request = submitFormWithJs(event.currentTarget)
event.preventDefault()
const response = await request
}function submitFormWithJs(form) {
const url = new URL(form.action);
const formData = new FormData(form);
const fetchOptions = {
method: form.method,
};
if (form.method.toLowerCase() === 'post') {
if (form.enctype === 'multipart/form-data') {
fetchOptions.body = formData; // multipart/form-data
} else {
fetchOptions.body = new URLSearchParams(formData); // application/x-www-form-urlencoded
}
} else {
url.search = new URLSearchParams(formData);
}
return fetch(url, fetchOptions);
}During HTTP requests, each chunk triggers the request.on() method.
function processNodeRequest(request) {
request.on("data", (data) => {
console.log(data);
}
}
function processNodeRequest(request) {
return new Promise((resolve, reject) => {
const chunks = [];
request.on('data', (data) => {
chunks.push(data);
});
request.on('end', () => {
const payload = Buffer.concat(chunks).toString()
resolve(payload);
});
request.on('error', reject);
});
}console.log(await processNodeRequest(request))POST / HTTP/1.1
Content-Type: multipart/form-data;boundary=
---------------------------WebKitFormBoundary4Ay52hDeKB5x2vXP
Content-Length: 19
------WebKitFormBoundary4Ay52hDeKB5x2vXP--
Content-Disposition: form-data; name="file"; filename="dear-nugget.txt"
Content-Type: text/plain
I love you, Nugget!
------WebKitFormBoundary4Ay52hDeKB5x2vXP--function processNodeRequest(request) {
return new Promise((resolve, reject) => {
const form = formidable({ multiples: true })
form.parse(request, (error, fields, files) => {
if (error) {
reject(error);
return;
}
resolve({ ...fields, ...files });
});
});
}{
file: PersistentFile {
lastModifiedDate: 2023-03-21T22:57:42.332Z,
filepath: '/tmp/d53a9fd346fcc1122e6746600',
newFilename: 'd53a9fd346fcc1122e6746600',
originalFilename: 'dear-nugget.txt',
mimetype: 'text/plain',
hashAlgorithm: false,
size: 19,
hash: null,
}
}A single, central place to store and access all of your uploads.
Highly available, easily scalable, and often 10x cheaper than server space.
Ideally should be S3-compatible.
S3 ("Simple Storage Service") is AWS’s object storage product.
Has become a standard communication protocol for Object Storage solutions.
Libraries: @aws-sdk/client-s3 @aws-sdk/lib-storage
const form = formidable({
multiples: true,
fileWriteStreamHandler: fileWriteStreamHandler,
});
function fileWriteStreamHandler(file) {
// TODO
}const s3Uploads = [];
function fileWriteStreamHandler(file) {
const fileStream = new PassThrough(); // from 'stream' module
const upload = new Upload({
client: s3Client,
params: {
Bucket: 'austins-bucket',
Key: `files/${file.newFilename}`,
ContentType: file.mimetype,
ACL: 'public-read',
Body: fileStream,
},
});
const uploadRequest = upload.done().then((response) => {
file.location = response.Location;
});
s3Uploads.push(uploadRequest);
return fileStream;
}file: {
lastModifiedDate: null,
filepath: '/tmp/93374f13c6cab7a01f7cb5100',
newFilename: '93374f13c6cab7a01f7cb5100',
originalFilename: 'nugget.jpg',
mimetype: 'image/jpeg',
hashAlgorithm: false,
createFileWriteStream: [Function: fileWriteStreamHandler],
size: 82298,
hash: null,
location: 'https://austins-bucket.us-southeast-1.linodeobjects.com/files/nugget.jpg',
}In to your server and out to Object Storage.
Globally distributed network of servers that caches content close to users.
Initial requests pass through CDN node, grab content from origin, and get cached. Subsequent requests use cached content.
Faster deliver of static assets (HTML, CSS, JavaScript, images, fonts, etc.)


Prevent excessively long file names or invalid characters for OS.
const form = formidable({
// other config options
filename(file) {
// return some random string
},
});(formidable does this by default)
File upload & download size impacts storage, bandwidth, and performance.
const form = formidable({
// other config options
maxFileSize: 1024 * 1024 * 10, // 10mb
});(formidable defaults to 200mb max file size)
Quarantine files away from running application and with limited permissions.
const form = formidable({
// other config options
uploadDir: './uploads',
});(formidable defaults to OS’s temp folder)
Only allow files with allowed extensions into your system.
const form = formidable({
// other config options
filter(file) {
const allowed = /\.(jpe?g|png|gif|avif|webp|svg)$/i;
return allowed.test(file.originalFilename)
}
});Only allow files with allowed MIME-types into your system.
const form = formidable({
// other config options
filter(file) {
const mimetype = file.mimetype ?? '';
return Boolean(mimetype.includes('image'));
}
});(formidable bases file.mimetype from file extension)
Scanning files for malware is one of the more important security steps you can take when accepting file uploads.
(WARNING: Uniquely Akamai Product Content)
Akamai customers have access to App & API Protector which provides:


austingil.com/uploading-files-with-html
Covers HTML, JavaScript, Node.js (Nuxt), Object Storage, CDNs, Malware