Upload and Download Round Trip

Salvador Guerrero
4 min readApr 11, 2020

--

In this story I’ll be showing how to upload an image to a node.js server and from there uploading to a FTP server, then downloading the image from the FTP server and sending it back to the website as the response, and then the website will show it in an <img> tag.

Why is this useful? basically for learning, in real production environment I don’t think I’ll be using an FTP server, and if I do, it will have to be in a private network, a better choice for production apps is to upload files to Amazon S3 or Google’s Cloud FileStore.

Environment setup

  • FTP server running locally and setup for anonymous to upload/download files, I’m using docker for this.
  • Node.js & the following third-party modules: basic-ftp & base64-stream.

The server

Whenever the user goes to the root of the website example: http://localhost/ I load an html file and send it as response, to do that, I put the following code snippet inside the http.createServer lambda:

if (req.url == '/' && req.method.toLowerCase() == 'get') {
response.setHeader('Content-Type', 'text/html')
const stream = fs.createReadStream(`${__dirname}/index.html`)
stream.pipe(response)
}

I’ll show the content of the html later in this story.

To receive the image, websites need to send the POST request to the /fileUpload endpoint example: http://localhost/fileUpload and I use the following conditions for this to work:

if (req.url == '/fileUpload' && req.method.toLowerCase() == 'post') {

Now within /fileUpload, I create a FTP client instance, upload to the FTP server, download the image from the FTP server, pipe it through Base64Encode and pipe it from there to the response as you see in the code below:

const client = new ftp.Client()
client.ftp.verbose = true
client.access({
host: "localhost",
user: "anonymous",
password: "",
secure: false
}).then(ftpResponse => {
(async () => {
try {
await client.uploadFrom(req, `uploads/${filename}`)
var base64Encoder = new Base64Encode()
base64Encoder.pipe(response)
await client.downloadTo(base64Encoder, `uploads/${filename}`)
}
catch(err) {
console.log(err)
}
client.close()
})();
})

client.uploadFrom() uploads to the FTP server whatever we receive from the website directly from the request parameter from http.createServer.

I then create a Base64Encoder instance and pipe it to the response parameter from the http.createServer. I encode the bytes from the image to base64 so that the <img> tag can show the image without storing the file locally in the client, this is just for test, in the real production apps I think I would use some kind of file caching mechanism.

client.downloadTo() downloads the image from the FTP server and pipes the bytes to the base64encoder and then it gets piped to the response object.

I will show the complete source code below, so stay tuned.

The Website

The html code was the easy part, the javascript client code was interesting and I made me really think about this one. Below is the body, as you can see super simple:

<body onload="init()">
<input id="fileUpload" type="file" accept="image/*" /><br />
<img id="img" alt="Uploaded image"/>
</body>

The JavaScript contains two functions, one called when the site loads to add a listener to the input button, and the other one is called when the button is clicked.

<script>
const handleImageUpload = event => {
const file = event.target.files[0]
fetch('/fileUpload', {
method: 'POST',
body: file
}).then(response => {
let contentType = response.headers.get('Content-Type')
response.text().then(text => {
document.getElementById("img").src = `data:${contentType};base64,${text}`
})
.catch(error => {
console.error(error)
})
})
.catch(error => {
console.error(error)
})
}
function init() {
document.getElementById('fileUpload').addEventListener('change', event => {
handleImageUpload(event)
})
}
</script>

event.target.files[0] I get the file selected by the user, if the user doesn’t select any file this function will not get called.

fetch() I create a fetch request to /fileUpload and send the file as the body parameter, it internally does the parsing and streaming up to the server.

(At this point the server upload to FTP and downloads from it)

.then(response => {}) will be called once the server sends the image as a response.

response.text() reads the complete content of the response and sends the text result to it’s promise .then(text => {}).

Remember that we encoded the image bytes to base64?, this is where it’s useful; once we receive the complete response as text, I change the src attribute of <img> to be the content of the base64 encoded image:

.src = `data:${contentType};base64,${text}`

${contentType} is image/gif or whatever the original content type is.

${text} is the base64 encoded image.

Below is the complete working code

That’s it for this story, please feel free to add questions.

See y’all later ✌️

--

--

Salvador Guerrero

Computer Science Engineer, Cross-Platform App Developer, Open Source contributor. 🇲🇽🇺🇸