168 lines
5.1 KiB
Markdown
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
|
|
}
|
|
```
|
|
|
|

|
|
|
|
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)
|