¿Cómo subir archivos al servidor con Go (golang) y Ajax?

Subir archivos al servidor es una funcionalidad muy común que realizamos en muchas de las páginas web que construimos, puede ser para subir el avatar para el perfil de un usuario, para una galería de imágenes o la carátula de un artículo, etc. Pero ¿qué pasa si lo quieres hacer usando una tecnología como Ajax?, veamos una posible solución a este problema.

Primero mostraré la estructura de carpetas y archivos que usaremos en este ejercicio:

public\
    api.js
    app.js
    index.html
    style.css
controllers\
    file.go
files\

Abrimos el archivo index.html y escribimos el siguiente código HTML:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, 
    user-scalable=no, initial-scale=1.0, maximum-scale=1.0,
minimum-scale=1.0">
    <link rel="stylesheet" 
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
    <link rel="stylesheet" href="styles.css">
    <title>File upload using Ajax</title>
</head>
<body>
    <form action="/upload" method="post" enctype="multipart/form-data" class="uploadForm">
        <input class="uploadForm__input" type="file" name="file" id="inputFile" accept="image/*">
        <label class="uploadForm__label" for="inputFile">
            <i class="fa fa-upload uploadForm__icon"></i> Select a file
        </label>
    </form>
    <div class="notification" id="alert"></div>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="api.js"></script>
    <script src="app.js"></script>
</body>
</html>

Ahora vamos añadir algunos estilos básicos a nuestro formulario, en el archivo styles.css:

body {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100vh;    
    font-family: Verdana, Geneva, Tahoma, sans-serif;
}

body,
.uploadForm,
.uploadForm__label {
    display: flex;
    align-items: center;
    justify-content: center;
}

.uploadForm {
    width: 600px;
    max-width: 600px;
    height: 300px;
    flex-direction: column;    
    border: 2px dashed gray;
    font-family: inherit;
}

.uploadForm__input {
    display: none;
}

.uploadForm__label {
    border: 1px solid gray;
    padding: .5em 2em;
    color: deepskyblue;
    transition: transform .4s;
    flex-direction: column;
}

.uploadForm__label:hover {
    cursor: pointer;
    transform: scale(1.01);
    box-shadow: grey 2px 2px 10px;
}

.uploadForm__icon {
    font-size: 1.8em;
}

.notification {    
    display: none;    
}

.success,
.error {
    right: 30px;
    z-index: 10;
    width: 300px;
    bottom: 40px;
    padding: 1em;
    height: auto;
    text-align: center;
    display: block;
    position: fixed;
    font-family: inherit;
    animation: alert .8s forwards;
}

.notification.success {
    background: #D9EDF7;
    color: #31709C;
}

.notification.error {
    background: #F2DEDE;
    color: #B24842;
}

@keyframes alert {
    0% {
        opacity: 0;
        bottom: -40px;
    }

    100% {
        opacity: 1;
        bottom: 40px;
    }
}

Dentro del archivo app.js pondremos el código js necesario para mandar nuestro archivo;

(function (d, axios) {
    "use strict";
    var inputFile = d.querySelector("#inputFile");
    var divNotification = d.querySelector("#alert");

    inputFile.addEventListener("change", addFile);

    function addFile(e) {
        var file = e.target.files[0];
        if(!file){
            return;
        }
        upload(file);
    }

    function upload(file) {
        var formData = new FormData();
        formData.append("file", file);
        post("/upload", formData)
            .then(onResponse)
            .catch(onResponse);
    }

    function onResponse(response) {
        var className = (response.status !== 400) ? "success" : "error";
        divNotification.innerHTML = response.data;
        divNotification.classList.add(className);
        setTimeout(function() {
            divNotification.classList.remove(className);
        }, 3000);
    }
})(document, axios);

Finalmente dentro del archivo api.js haremos uso de la librería axios para mandar nuestra petición Ajax al servidor:

"use strict";

function post(url, data) {
    return axios.post(url, data)
        .then(function (response) {
            return response;
        }).catch(function (error) {
            return error.response;
        });
}

Bién, para manejar la petición del lado del servidor usaremos el archivo file.go que está dentro de la carpeta controllers escribiendo el siguiente código:

package controllers

import (
    "fmt"
    "io/ioutil"
    "mime/multipart"
    "net/http"
)

// UploadFile sube el archivo al servidor
func UploadFile(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Redirect(w, r, "/", http.StatusSeeOther)
        return
    }

    file, handle, err := r.FormFile("file")
    if err != nil {
        fmt.Fprintf(w, "%v", err)
        return
    }
    defer file.Close()

    mimeType := handle.Header.Get("Content-Type")
    switch mimeType {
    case "image/jpeg":
        saveFile(w, file, handle)
    case "image/png":
        saveFile(w, file, handle)
    default:
        jsonResponse(w, http.StatusBadRequest, "The format file is not valid.")
    }
}

func saveFile(w http.ResponseWriter, file multipart.File, handle *multipart.FileHeader) {
    data, err := ioutil.ReadAll(file)
    if err != nil {
        fmt.Fprintf(w, "%v", err)
        return
    }

    err = ioutil.WriteFile("./files/"+handle.Filename, data, 0666)
    if err != nil {
        fmt.Fprintf(w, "%v", err)
        return
    }
    jsonResponse(w, http.StatusCreated, "File uploaded successfully!.")
}

func jsonResponse(w http.ResponseWriter, code int, message string) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(code)
    fmt.Fprint(w, message)
}

Como último paso dentro del archivo main.go haremos uso de nuestro controlador de la siguiente forma:
 

package main

import (
    "log"
    "net/http"

     // Nota esta es mi GOPATH cambiala de acuerdo a la tuya.
    "bitbucket.org/gustavocd/upload-img/controllers"
)

func main() {
    http.Handle("/", http.FileServer(http.Dir("./public")))
    http.HandleFunc("/upload", controllers.UploadFile)
    log.Println("Running")
    http.ListenAndServe(":8080", nil)
}

Ahora solo tienes que ejecutar el siguiente comando en la terminal y después visitar http://localhost:8080 en tu navegador para empezar a subir tus archivos:

go run main.go

Eso es todo por este artículo, espero les sea de utilidad, gracias por leerlo y nos vemos en una próxima ocasión.

Sigue leyendo