Files
build-web-application-with-…/es/04.5.md
2018-04-14 23:00:43 +02:00

168 lines
5.1 KiB
Markdown

# 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
}
```
![](images/4.5.upload2.png?raw=true)
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
- [Índice](preface.md)
- Sección anterior: [Envíos duplicados](04.4.md)
- Siguiente sección: [Resumen](04.6.md)