Skip to content

Instantly share code, notes, and snippets.

@nimatrueway
Last active January 13, 2023 22:55
Show Gist options
  • Save nimatrueway/0068b9a182176fdfc285b1c66aa2292b to your computer and use it in GitHub Desktop.
Save nimatrueway/0068b9a182176fdfc285b1c66aa2292b to your computer and use it in GitHub Desktop.
Simulate server timeout for large/slow file uploads after 3 seconds
<html>
<script>
async function upload() {
let files = document.getElementsByName("file")[0].files;
console.assert(files.length > 0)
const content = files[0];
const res = fetch("/upload", {
method: "PUT",
body: content,
duplex: 'half'
})
const resJson = await (await res).json()
console.log(resJson)
}
</script>
<body>
Select a file to upload:
<br/>
<input type="file" name="file"/>
<br/>
<button onclick="upload()">Upload</button>
</body>
</html>
// Run instructions:
//
// - yarn install
// - brew install mkcert; sudo mkcert localhost
// - node main.mjs
import fs from 'fs'
import finalhandler from 'finalhandler'
import send from 'send'
import path from 'path'
import http2 from 'node:http2'
import Router from 'router'
const http_error_code = 408
const timeout = 3000
const port = 3000
const router = Router()
router.get('/', function (req, res) {
send(req, './index.html').pipe(res)
});
router.put('/upload', function (req, res, next) {
console.info("Request received.")
const file = fs.createWriteStream(path.join('./uploaded-time-' + Date.now().toString()))
req.pipe(file);
req.on('end', () => {
console.info("Upload completed!")
res.end('Upload completed!');
})
setTimeout(() => {
if (!res.writableEnded) {
console.error("Upload timeout!")
res.statusCode = http_error_code
res.end('Upload timeout!\n')
}
}, timeout)
});
const options = {
key: fs.readFileSync('localhost-key.pem'),
cert: fs.readFileSync('localhost.pem'),
allowHTTP1: true
}
const server = http2.createSecureServer(options, function (req, res) {
router(req, res, finalhandler(req, res))
})
server.listen(port)
{
"dependencies": {
"finalhandler": "^1.2.0",
"router": "^1.3.7",
"send": "^0.18.0"
}
}
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
@nimatrueway
Copy link
Author

nimatrueway commented Jan 3, 2023

  • Run server first
git clone https://gist.github.com/nimatrueway/0068b9a182176fdfc285b1c66aa2292b simulate-timeout
cd simulate-timeout
yarn install
node main.mjs
  • Acquire a sample image file
curl -o damavand.jpg "https://upload.wikimedia.org/wikipedia/commons/8/8d/981012-Damavand-South-IMG_9861-2.jpg"
  • Upload the sample image slowly (upload speed throttled to 1k/sec)
time curl -v "http://localhost:3000/upload" \
  --http1.1 \
  --limit-rate 1k \
  -X PUT \
  -H "Expect: " \
  -H "Content-Type: octet-stream" \
  --data-binary "@damavand.jpg"
  • cURL received server 408 http error, after ~3-4 seconds.
< HTTP/1.1 408 Request Timeout
< X-Powered-By: Express
< Date: Tue, 03 Jan 2023 22:49:20 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Content-Length: 15
* HTTP error before end of send, stop sending
<
* Closing connection 0
Upload timeout!        4.02 real         0.00 user         0.01 sys
  • Now try doing the same in the browser, with Network connection throttled to 1kB/s using Talend API Tester - Free Edition the connection won't close and is still in pending state even after 60 seconds.

image

Here's a screen recording of me running both tests:

screen-record.webm
  • I also captured packet exchanges between cURL<->Server vs. Browser<->Server on my machine that you can download using command below and open using wireshark.
curl -s https://gist.githubusercontent.com/nimatrueway/0068b9a182176fdfc285b1c66aa2292b/raw/wireshark-dump.zip.base64 | base64 -d > wireshark-dump.zip
unzip wireshark-dump.zip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment