Skip to content

Latest commit

 

History

History
167 lines (132 loc) · 5.1 KB

File metadata and controls

167 lines (132 loc) · 5.1 KB

4.5 Subida de archivos

Supón que tienes un sitio web como Instagram y quieres que tus usuarios suban sus fotos. ¿Cómo implementarías esa funcionalidad?

Tienes que agregar la propiedad enctype al formulario que vas a usar para subir fotos. Aquí están los tres posibles valores para esta propiedad:

application/x-www-form-urlencoded. Transforma todos los caracteres antes de subirlos (Por defecto).
multipart/form-data Sin transformación. Debes usar esta valor cuando vas a subir fotos.
text/plain Convierte los espacios en "+" sin transformar los caracteres.

Entonces el contenido HTML para subir un archivo debería verse como esto:

<html>
<head>
   	<title>Upload file</title>
</head>
<body>
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
	<input type="file" name="uploadfile" />
	<input type="hidden" name="token" value="{{.}}"/>
	<input type="submit" value="Subit" />
</form>
</body>
</html>

Necesitamos añadir la funcionalidad al servidor para manejar este formulario.

http.HandleFunc("/upload", upload)

// lógica de subida
func upload(w http.ResponseWriter, r *http.Request) {
   	fmt.Println("method:", r.Method)
   	if r.Method == "GET" {
       	crutime := time.Now().Unix()
       	h := md5.New()
       	io.WriteString(h, strconv.FormatInt(crutime, 10))
       	token := fmt.Sprintf("%x", h.Sum(nil))

       	t, _ := template.ParseFiles("upload.gtpl")
       	t.Execute(w, token)
   	} else {
       	r.ParseMultipartForm(32 << 20)
       	file, handler, err := r.FormFile("uploadfile")
       	if err != nil {
           	fmt.Println(err)
           	return
       	}
       	defer file.Close()
       	fmt.Fprintf(w, "%v", handler.Header)
       	f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
       	if err != nil {
           	fmt.Println(err)
           	return
       	}
       	defer f.Close()
       	io.Copy(f, file)
   	}
}

Como puedes ver, llamamo a r.ParseMultipartForm para subir archivos. La función ParseMultipartForm toma el argumento maxMemory. Después de llamar ParseMultipartForm, el archivo puede ser guardado en el servidor con tamaño maxMemory. Si el tamaño del archivo es mayor que maxMemory, el resto del archivo será guardado en un archivo temporal del sistema. Puedes usar r.FormFile para obtener el archivo y usar io.Copy para guardarlo en tu sistema.

Puede que no necesites llamar a r.ParseForm cuando acceses a otros campos, porque Go llamará a este método si es necesario. También llamar a ParseMultipartForm una vez es suficiente, múltiples llamadas no hacen diferencia.

Usamos tres pasos para subir archivos:

  1. Agregar enctype="multipart/form-data" a tu formulario
  2. Llamar r.ParseMultipartForm en el lado del servidor para guardar el archivo en memoria o en un archivo temporal.
  3. Llamar r.FormFile para obtener el archivo y guardarlo en el sistema de ficheros.

El manejador del archivo es multipart.FileHeader. Este usa la siguiente estructura:

type FileHeader struct {
   	Filename string
   	Header   textproto.MIMEHeader
   	// contains filtered or unexported fields
}

Figure 4.5 Imprimir la información del servidor después de recibir el archivo.

Cliente para subir archivos

Les mostré en el ejemplo anterior como usar un formulario para subir un archivo. Nosotros podemos usar un cliente en Go para emular la subida de archivos.

package main

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

func postFile(filename string, targetUrl string) error {
    bodyBuf := &bytes.Buffer{}
    bodyWriter := multipart.NewWriter(bodyBuf)

    // este paso es muy importante
    fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
    if err != nil {
        fmt.Println("error writing to buffer")
        return err
    }

    // Abrir el archivo para manejarlo
    fh, err := os.Open(filename)
    if err != nil {
        fmt.Println("error opening file")
        return err
    }

    //iocopy
    _, err = io.Copy(fileWriter, fh)
    if err != nil {
        return err
    }

    contentType := bodyWriter.FormDataContentType()
    bodyWriter.Close()

    resp, err := http.Post(targetUrl, contentType, bodyBuf)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    resp_body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return err
    }
    fmt.Println(resp.Status)
    fmt.Println(string(resp_body))
    return nil
}

// Ejemplo de uso
func main() {
    target_url := "http://localhost:9090/upload"
    filename := "./astaxie.pdf"
    postFile(filename, target_url)
}

El código de arriba muestra como usar un cliente para subir archivos. El usa multipart.Write para escribir archivos en un cache y enviarlos al servidor a través de un método POST.

Si tienes otros campos que necesitas escribir en el cuerpo del mensaje, como un nombre d eusuario, llama a multipart.WriteField cada que lo necesites.

Enlaces