Merge pull request #5 from astaxie/master

merged
This commit is contained in:
vz
2016-12-27 18:02:50 +08:00
committed by GitHub
16 changed files with 1477 additions and 17 deletions

View File

@@ -15,8 +15,9 @@ There are many ways to configure the Go development environment on your computer
In case you want to install more than one version of Go on a computer, you should take a look at a tool called [GVM](https://github.com/moovweb/gvm). It is the best tool I've seen so far for accomplishing this task, otherwise you'd have to deal with it yourself.
## Install from source code
Go 1.5 completely remove the C codeRuntime、Compiler、Linker powered by Go,Achieve bootstrapping,You only need the previous version to compile go.
Because some parts of Go are written in Plan 9 C and AT&T assembler, you have to install a C compiler before taking the next step.
But before Go 1.5 some parts of Go are written in Plan 9 C and AT&T assembler, you have to install a C compiler before taking the next step.
On a Mac, if you have installed Xcode, you already have the compiler.
@@ -75,15 +76,15 @@ A 64-bit operating system will show the following:
### Mac
Go to the [download page](https://golang.org/dl/), choose `go1.4.2.darwin-386.pkg` for 32-bit systems and `go1.4.2.darwin-amd64.pkg` for 64-bit systems. Going all the way to the end by clicking "next", `~/go/bin` will be added to your system's $PATH after you finish the installation. Now open the terminal and type `go`. You should see the same output shown in figure 1.1.
Go to the [download page](https://golang.org/dl/), choose `go1.4.2.darwin-386.pkg` for 32-bit systems and `go1.7.4.darwin-amd64.pkg` for 64-bit systems. Going all the way to the end by clicking "next", `~/go/bin` will be added to your system's $PATH after you finish the installation. Now open the terminal and type `go`. You should see the same output shown in figure 1.1.
### Linux
Go to the [download page](https://golang.org/dl/), choose `go1.4.2.linux-386.tar.gz` for 32-bit systems and `go1.4.2.linux-amd64.tar.gz` for 64-bit systems. Suppose you want to install Go in the `$GO_INSTALL_DIR` path. Uncompress the `tar.gz` to your chosen path using the command `tar zxvf go1.4.2.linux-amd64.tar.gz -C $GO_INSTALL_DIR`. Then set your $PATH with the following: `export PATH=$PATH:$GO_INSTALL_DIR/go/bin`. Now just open the terminal and type `go`. You should now see the same output displayed in figure 1.1.
Go to the [download page](https://golang.org/dl/), choose `go1.7.4.linux-386.tar.gz` for 32-bit systems and `go1.7.4.linux-amd64.tar.gz` for 64-bit systems. Suppose you want to install Go in the `$GO_INSTALL_DIR` path. Uncompress the `tar.gz` to your chosen path using the command `tar zxvf go1.7.4.linux-amd64.tar.gz -C $GO_INSTALL_DIR`. Then set your $PATH with the following: `export PATH=$PATH:$GO_INSTALL_DIR/go/bin`. Now just open the terminal and type `go`. You should now see the same output displayed in figure 1.1.
### Windows
Go to the [download page](https://golang.org/dl/), choose `go1.4.2.windows-386.msi` for 32-bit systems and `go1.4.2.windows-amd64.msi` for 64-bit systems. Going all the way to the end by clicking "next", `c:/go/bin` will be added to `path`. Now just open a command line window and type `go`. You should now see the same output displayed in figure 1.1.
Go to the [download page](https://golang.org/dl/), choose `go1.7.4.windows-386.msi` for 32-bit systems and `go1.7.4.windows-amd64.msi` for 64-bit systems. Going all the way to the end by clicking "next", `c:/go/bin` will be added to `path`. Now just open a command line window and type `go`. You should now see the same output displayed in figure 1.1.
## Use third-party tools
@@ -95,8 +96,8 @@ GVM is a Go multi-version control tool developed by a third-party, like rvm for
Then we install Go using the following commands:
gvm install go1.4.2
gvm use go1.4.2
gvm install go1.7.4
gvm use go1.7.4
After the process has finished, you're all set.
@@ -108,10 +109,28 @@ Ubuntu is the most popular desktop release version of Linux. It uses `apt-get` t
sudo apt-get update
sudo apt-get install golang-stable
###wget
```sh
wget https://storage.googleapis.com/golang/go1.7.4.linux-amd64.tar.gz
sudo tar -xzf go1.7.4.linux-amd64.tar.gz -C /usr/local
export PATH=PATH:/usr/local/go/binexportGOROOT=HOME/go
export PATH=PATH:GOROOT/bin
export GOPATH=HOME/gowork
```
### Homebrew
Homebrew is a software management tool commonly used in Mac to manage packages. Just type the following commands to install Go.
1.Install Homebrew
```sh
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```
2.Install Go
brew update && brew upgrade
brew install go
## Links

View File

@@ -47,7 +47,9 @@ We see that it's very easy to use concurrency in Go by using the keyword `go`. I
runtime.Gosched() means let the CPU execute other goroutines, and come back at some point.
The scheduler only uses one thread to run all goroutines, which means it only implements concurrency. If you want to use more CPU cores in order to take advantage of parallel processing, you have to call runtime.GOMAXPROCS(n) to set the number of cores you want to use. If `n<1`, it changes nothing. This function may be removed in the future, see more details about parallel processing and concurrency in this [article](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide).
In Go 1.5,the runtime now sets the default number of threads to run simultaneously, defined by GOMAXPROCS, to the number of cores available on the CPU.
Before Go 1.5,The scheduler only uses one thread to run all goroutines, which means it only implements concurrency. If you want to use more CPU cores in order to take advantage of parallel processing, you have to call runtime.GOMAXPROCS(n) to set the number of cores you want to use. If `n<1`, it changes nothing.
## channels

View File

@@ -176,21 +176,21 @@ En escenarios mas complejos también podemos utilizar este método, la ventaja e
Vamos a echar un vistazo a la lista de flujo de ejecución en conjunto.
- Se llama http.HandleFunc
- Se llama `http.HandleFunc`
1. Se Llama HandleFunc de DefaultServeMux
2. Se Llama Handle de DefaultServeMux
3. Se agregan las reglas del enrutamiento a map[string]muxEntry de DefaultServeMux
- Se llama http.ListenAndServe (":9090" , nil )
- Se llama `http.ListenAndServe(":9090" , nil)`
1. Se instancia el servidor
2. Llama ListenAndServe del Servidor
3. Llama net.Listen ( " tcp" , addr ) para escuchar en el puerto .
2. Se Llama ListenAndServe del Servidor
3. Se Llama net.Listen ( "tcp" , addr ) para escuchar en el puerto.
4. Iniciar un bucle, y aceptar las solicitudes en el cuerpo del bucle.
5. Instanciada una Conn se empieza una goroutine para cada solicitud : ir c.serve ().
6. Lee petición de datos : w , err : = c.readRequest ().
7. Comprueba si el controlador está vacío, si está vacíoutiliza DefaultServeMux .
8. Llama al controlador de ServeHTTP
9. Ejecutar código en DefaultServeMux en este caso.
10. Elije el controlador URL y ejecutar código del controlador en esta seccion: mux.handler.ServeHTTP ( w , r)
5. Instanciar una Conn y se empieza una goroutine para cada solicitud : `go c.serve ()`
6. Se Lee petición de datos : `w , err : = c.readRequest ()`
7. Se Comprueba si el controlador está vacío, si está vacíoutiliza DefaultServeMux .
8. Se Llama al controlador de ServeHTTP
9. Se Ejecuta el código en DefaultServeMux en este caso.
10. Elije el controlador URL y ejecutar código del controlador en esta seccion: `mux.handler.ServeHTTP (w , r)`
11. Cómo elegir handler:
A. Normas de router de verificación para esta URL.
B. Llamar ServeHTTP en ese controlador, si es que existe.

141
es/04.2.md Normal file
View File

@@ -0,0 +1,141 @@
# 4.2 Verificando las entradas
Uno de los principios mas importantes en el desarrollo web es que no puedes confiar en nada de lo que viene en los formularios de usuario. Necesitas validar todos los datos de entrada antes de usarlos. Muchos sitios web son afectados por este problema, lo que es simplemente crucial.
Existen dos maneras de verificar datos de formularios, que son usadas comunmente. La primera es la validación del lado del cliente en. el front end, y la segunda es la validación del lado del servidor, en el back end. En esta sección vamos a hablar sobre la validación del lado del servidor.
## Campos requeridos
Algunas veces requerimos que los usuarios ingresen algunos campos, pero ellos fallan completándolos. Por ejemplo, en la sección anterior, nosotros requerimos un nombre de usuario. Puedes usar la función `len` para obtener el tamaño de un campo para asegurarte que se ha ingresado algo.
```
if len(r.Form["username"][0])==0{
// code for empty field
}
```
`r.Form` trata diferente a las entradas que son de distinto tipo cuando están vacíos. Para cuadros de textos, áreas de texto y campos de archivos, retorna una cadena vacía, para botones circulares y cajas de chequeo, a veces ni siquiera se crean los tipos. En cambio, vas a tener problemas accediendo a los elementos correspondientes. Por esto, es mas seguro usar `r.Form.Get()` para obtener los valores de los campos de una manera que siempre van a retornar vacío si el valor no existe. Por otra parte, `r.Form.Get()` solo puede obtener un valor al tiempo, así que necesitas usar `r.Form` para obtener un mapa de los valores.
## Números
Algunas veces necesitamos números mas allá de texto en un campo. Por ejemplo, digamos que necesitas la edad de un usuario como un entero solamente, es decir: 50 o 10, en vez de "lo suficientemente viejo" u "hombre jóven". Si requerimos un número positivo, podemos convertir el valor al tipo `int` y luego procesarlo:
```
getint,err:=strconv.Atoi(r.Form.Get("age"))
if err!=nil{
// error occurs when convert to number, it may not a number
}
// check range of number
if getint >100 {
// too big
}
```
Otra manera de realizar esto es usando expresiones regulares:
```
if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {
return false
}
```
Para propósitos de eficiencia, las expresiones regulares no son eficientes, sin embarglo las expresiones regulares simples son lo suficientemente rápidas. Si estas familiarizado con las expresiones regulares, es una manera muy conveniente de verificar datos. Nota que Go usa la sintaxis [RE2](http://code.google.com/p/re2/wiki/Syntax), entonces todos los caracteres de UTF-8 están soportados.
## Chino
Algunas veces, necesitamos que los usuarios ingresen su nombre en chino, y necesitamos verificar que todos estén en chino, en vez de caracteres al azar. Para la verificación del chino, las expresiones regulares son la única manera de conseguirlo:
```
if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", r.Form.Get("realname")); !m {
return false
}
```
## Letras del Inglés
Algunas veces necesitamos que los usuarios solo ingresen letras del Inglés, por ejemplo algún nombre en inglés como astaxie en vez de asta谢. Podemos verificar esto usando la siguiente expresión regular.
```
if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
return false
}
```
## Correo electrónico
Si queremos si el usuario ha ingresado una dirección de correo electrónico válida, puedes usar la siguiente expresión regular:
```
if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
fmt.Println("no")
}else{
fmt.Println("yes")
}
```
## Lista desplegable
Digamos que vamos a requerir un elemento de una lista desplegable, pero en vez de esto, tenemos un valor fabricado por hackers. ¿Cómo evitamos que esto pase?
Suponga que tenemos el siguiente `<select>`
```
<select name="fruit">
<option value="apple">apple</option>
<option value="pear">pear</option>
<option value="banana">banana</option>
</select>
```
Podemos usar la siguiente estrategia para limpiar la entrada:
```
slice:=[]string{"apple","pear","banana"}
for _, v := range slice {
if v == r.Form.Get("fruit") {
return true
}
}
return false
```
Todas las funciones que he mostrado arriba están en mi proyecto de código abierto para operar con segmentos y mapas: [https://github.com/astaxie/beeku](https://github.com/astaxie/beeku)
## Botones circulares
Si queremos saber cuando un usuario es femenino o masculino, podríamos usar un botón circular, retornando 1 para femenino y 2 para masculino. Sin embargo, algún niño que acaba de leer su primer libro sobre HTTP decide enviar un 3. ¿Nuestro programa levantará una excepción? Como puedes ver, necesitamos usar el mismo método que usamos para la lista desplegable para asegurarnos sobre los valores que son ingresados a nuestro botón circular.
```
<input type="radio" name="gender" value="1">Femenino
<input type="radio" name="gender" value="2">Masculino
```
Y usualmente utilizamos el siguiente código para validar las entradas:
```
slice:=[]int{1,2}
for _, v := range slice {
if v == r.Form.Get("gender") {
return true
}
}
return false
```
## Cajas de chequeo
Supon que existen algunas cajas de chequeo para los intereses de los usuarios, y tu no quieres valores extraños aquí tampoco, puedes validarlos de la siguiente manera:
```
<input type="checkbox" name="interest" value="football">Football
<input type="checkbox" name="interest" value="basketball">Basketball
<input type="checkbox" name="interest" value="tennis">Tennis
```
En este caso la estrategia de limpieza es un poco diferente a la validación de botones de chequeo y listas desplegables:
```
slice:=[]string{"football","basketball","tennis"}
a:=Slice_diff(r.Form["interest"],slice)
if a == nil{
return true
}
return false
```
## Fecha y Hora
Supón que quieres que los usuarios ingresen fechas y horas. Go tiene un paquete `time` para convertir año, mes y día a la hora correspondiente. Después de eso, es fácil verificarlo.
```
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
fmt.Printf("Go launched at %s\n", t.Local())
```
Después de eso, también puedes utilizar el paquete `time` para mas operaciones, dependiende de tus necesidades.
En esta sección hemos discutido algunos métodos comunes para validar los datos del lado del servidor. Espero que ahora entiendas un poco mas sobre la validación de datos en Go, especialmente como usar las evntajas de las expresiones regulares.
## Enlaces
- [Índice](preface.md)
- Sección anterior: [Procesando la entrada de los formularios](04.1.md)
- Siguiente sección: [Cross site scripting](04.3.md)

69
es/04.3.md Normal file
View File

@@ -0,0 +1,69 @@
# 4.3 Cross site scripting
Hoy los sitios web tienen mucho contenido dinámico en orden de mejorar la experiencia de usuario, lo que significa que debemos proveer información dinámica en función de cada comportamiento individual, desafortunadamente, los sitios web dinámicos son suceptibles a ataques maliciosos conocidos como "Cross site scripting" (abreviado "XSS"). Los sitios web estáticos no son vulnerables a este tipo de ataque.
Los atacantes usualmente inyectan código malicioso como Javascript, VBScript, ActiveX o Flash en sitios que tienen vulnerabilidades. Una vez que han conseguido enyectar sus scripts, la información del usuario puede ser robada y tu sitio web puede ser inundado de Spam. Los atacantes también pueden cambiar las configuraciones de los usuarios como sea que ellos quieran.
Si deseas prevenir este tipo de ataques, deberías combinar las siguientes dos aproximaciones
- Validar toda la información de los usuarios, que discutimos en la sección pasada.
- Manejar cuidadosamente la información enviada por los clientes en orden de prevenir cualquier inyección de scripts hecha desde los navegadores.
Entonces ¿Cómo podemos haces estas dos cosas en Go? Afortunadamente, el paquete `http/template` tiene algunas funciones que nos permiten escapar los datos como sigue:
- `func HTMLEscape(w io.Writer, b []byte)` escapa b to w.
- `func HTMLEscapeString(s string) string` retorna una cadena después de escaparla de s
- `func HTMLEscaper(args ...interface{}) string` retorna una cadena después de escaparla de algunos argumentos.
Vamos a cambiar el ejemplo de la sección 4.1:
```
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) // Imprime en el lado del servidor
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
template.HTMLEscape(w, []byte(r.Form.Get("username"))) // Respuesta a los clientes
```
Si alguien trata de ingresar el nombre de usuario como `<script>alert()</script>` podremos ver el siguiente contenido en el navegador.
![](images/4.3.escape.png?raw=true)
Figure 4.3 JavaScript después del escape
Las funciones en el paquete `html/template` nos ayudan a escapar todas las etiquetas HTML. Y ¿qué pasa si queremos imprimir `<script>alert()</script>` en los navegarores? Deberías usar el paquete `text/template`.
```
import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
```
Salida:
```
Hello, <script>alert('you have been pwned')</script>!
```
O puedes usar el tipo `template.HTML`:
El contenido variable no va a ser escapado si es del tipo `template.HTML`
Variable content will not be escaped if its type is `template.HTML`.
```
import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", template.HTML("<script>alert('you have been pwned')</script>"))
```
Salida:
```
Hello, <script>alert('you have been pwned')</script>!
```
Un ejemplo mas de escape:
```
import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
```
Salida:
```
Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!
```
## Enlaces
- [Índice](preface.md)
- Sección anterior: [Verificando las entradas](04.2.md)
- Siguiente sección: [Envíos duplicados](04.4.md)

57
es/04.4.md Normal file
View File

@@ -0,0 +1,57 @@
# 4.4 Envíos duplicados
No se si alguna vez has visto blogs que tienen uno o mas de un post que son exactamente iguales, pero puedo decirte que es porque un usuario envió dos veces el mismo formulario. Existen muchas cosas que pueden cambiar envíos duplicados; algunas veces los usuarios hacen doble click en el botón de enviar, o quieren modificar el contenido después de postear y usan el botón de atrás. En algunos casos es por una acción intencioanl de usuarios malicioso. Es facil ver como los envíos duplicados pueden generar muchos problemas. Afortunadamente tenemos herramientas para prevenirlos.
La solución es añadir un campo oculto con un único token al formulario, y siempre verificar si el token ha sido procesado con anterioridad. También si quieres usar ajax para enviar un formulario, puedes usar javascript para deshabilitar el botón una vez se ha presionado.
Vamos a mejorar el ejemplo de la sección 4.2:
```
<input type="checkbox" name="interest" value="football">Futbol
<input type="checkbox" name="interest" value="basketball">Basquetbol
<input type="checkbox" name="interest" value="tennis">Tenis
Nombre de usuario:<input type="text" name="username">
Contraseña:<input type="password" name="password">
<input type="hidden" name="token" value="{{.}}">
<input type="submit" value="Ingresar">
```
Nosotros usamos un hash MD5 con la hora actual para generar el token, y agregamos esto a un campo oculto del lado del cliente y a una cookie en el lado del servidor (Capítulo 6). Nosotros usamos este token para verificar si el formulario ha sido enviado.
```
func login(w http.ResponseWriter, r *http.Request) {
fmt.Println("method:", r.Method) // get request 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("login.gtpl")
t.Execute(w, token)
} else {
// log in request
r.ParseForm()
token := r.Form.Get("token")
if token != "" {
// check token validity
} else {
// give error if no token
}
fmt.Println("username length:", len(r.Form["username"][0]))
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) // print in server side
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
template.HTMLEscape(w, []byte(r.Form.Get("username"))) // respond to client
}
}
```
![](images/4.4.token.png?raw=true)
Figure 4.4 El contenido del navegador después de agregar el Token
Puedes refrescar la página y verás un token diferente cada vez. Esto asegura que cada formulario es único.
Por ahora, puedes prevenir ataques de envíos duplicados añadiendo tokens a tus formularios, pero no puedes prevenir todos los ataques de este tipo, aún hay mucho trabajo por hacer.
## Enlaces
- [Índice](preface.md)
- Sección anterior: [Cross site scripting](04.3.md)
- Siguiente sección: [Subida de archivos](04.5.md)

168
es/04.5.md Normal file
View File

@@ -0,0 +1,168 @@
# 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 propuedad `enctype` al formulario que vas a usar para subir fotos. Aquí están los tres posibles valores para esta propiedad
You have to add property `enctype` to the form that you want to use for uploading photos. There are three possible values for this property:
```
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)

11
es/04.6.md Normal file
View File

@@ -0,0 +1,11 @@
# 4.6 Resumen
En este capítulo mayormente hemos aprendido como procesar datos de formularios en Go a través de varios ejemplos, como autenticar un usuario y subir archivos. También hemos enfatizado que validar la información es extremadamente importante para la seguridad del sitio web, y también hemos usado una sección para filtrar datos a través de expresiones regulares.
Espero que ahora sepas mas sobre el proceso de comunicación entre cliente y servidor.
## Enlaces
- [Índice](preface.md)
- Sección anterior: [Subida de archivos](04.5.md)
- Siguiente sección: [Base de datos](05.0.md)

13
es/05.0.md Normal file
View File

@@ -0,0 +1,13 @@
# 5 Bases de Datos
Para los desarrolladores web, la base de datos está en el núclo del desarrollo web. Puedes guardar casi todo en una base de datos y consultar o actualizar dentro de ella, como información, productos noticias o artículos.
Go no provee ningún manejador para base de datos, pero si provee una interfaz definida en el paquete `database/sql`. Las personas pueden desarrollar manejadores de bases de datos basado en esa interfaz. En la sección 5.1, vamos a hablar sobre el diseño de un manejador de base de datos en Go. En las secciones 5.2 a 5.4 voy a introducir algunos manejadores de bases de datos. En la sección 5.5 voy a presentar un ORM que he diseñado que está basado en el estandar `database/sql`. Es compatible con la mayoría de manejadores que implementan la interfaz `database/sql` y hace fácil acceder a las bases de datos ideomáticamente en Go.
NoSQL ha sido un tópico importante en los últimos años. Muchos sitios web están decidiendo usar bases de datos NoSQL como su base de datos principal, o solo con el propósito de caché. Introduciré dos bases de datos: MongoDB y Redis, en la sección 5.6.
## Enlaces
- [Índice](preface.md)
- Capítulo anterior: [Capítulo 4 Resumen](04.6.md)
- Siguiente sección: [Interfaz database/sql](05.1.md)

203
es/05.1.md Normal file
View File

@@ -0,0 +1,203 @@
# 5.1 Interfaz database/sql
Go no provee ningún manejador de base de datos oficial, a diferencia de otros lenguajes como PHP que si lo hacen. Sin embargo, si provee una estándar de interfaz para que los desarrolladores desarrollen manejadores basados en ella. La ventaja es que si tu código es desarrollado de acuerdo a esa interfaz estándar, no vas a tener que cambiar ningún código si tu base de datos cambia. Veamos cuales son los estándares de esta interface.
## sql.Register
Esta función está en el paquete `database/sql` para registrar una base de datos cuando usas un manejador de base de datos de terceros. Todos deberían llamar a la función `Register(name String, driver driver.Driver)` en la función `init()` en orden de registrarse a si mismas.
Vamos a mirar los corespondientes llamados en el código de mymysql y sqlite3:
```
//manejador https://github.com/mattn/go-sqlite3
func init() {
sql.Register("sqlite3", &SQLiteDriver{})
}
//manejador https://github.com/mikespook/mymysql
// Driver automatically registered in database/sql
var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"}
func init() {
Register("SET NAMES utf8")
sql.Register("mymysql", &d)
}
```
Vemos que los manejadores de terceros implementan esta función para registrarse a si mismos y Go utiliza un mapa para guardar los manejadores dentro de `database/sql`.
```
var drivers = make(map[string]driver.Driver)
drivers[name] = driver
```
Así, la función de registro puede registrar tantos manejadores como requieras, cada uno con un nombre diferente.
Siempre veremos el siguiente código cuando usemos manejadores de terceros:
```
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
```
Aquí, el guión bajo, (también llamado 'blank') `_` puede ser un poco confuso para muchos principiantes, pero es una gran característica de Go. Sabemos que el identificador de guión bajo es usado para no guardar valores que una función retorna, y también que debes usar todos los paquetes que importes en tu código Go. Entonces, cuando el guión bajo es usado con un import, significa que necesitas ejecutar la función init() de ese paquete sin usarlo directamente. lo cual encaja perfectamente en el caso de registrar el manejador de base de datos.
## driver.Driver
`Driver` es una interface que contiene un método `Open(name string)` que retorna una interfaz `Conn`.
```
type Driver interface {
Open(name string) (Conn, error)
}
```
Esta es una conexión de una vez, que significa que solamente puede ser usada una vez por rutina. El siguiente código causará errores:
```
...
go goroutineA (Conn) // query
go goroutineB (Conn) // insert
...
```
Porque no no tiene idea de que rutina va a realizar cada operación, la operación de consulta podría obtener el resultado de la operación de incersión y viceversa.
Todos los manejadores de terceros debería tener esta función para analizar el nombre de una Conn y retornar el resultado correcto.
## driver.Conn
Esta es la interfaz de conexión con algunos métodos, como dije aarriba, la misma conexión solo puede usarse una vez por rutina.
```
type Conn interface {
Prepare(query string) (Stmt, error)
Close() error
Begin() (Tx, error)
}
```
- `Prepare` retorna el estado del comando SQL ejecutado para consultas, eliminaciones, etc.
- `Close` cierra la conexión actual y limpia los recursos. La mayoría de paquetes de terceros implementan algún tipo de piscina de conexiones, así que no necesitas almacenar las conexiones que puedan causar errores.
- `Begin` retorna un Tx que representa una transacción para manejar. Puedes usarlo para consultar, almacenar, o volver hacia atrás algunas transacciones.
## driver.Stmt
Este es un estado de listo que corresponde con una Conn, y solamente puede ser usada una vez por rutina (como en el caso de Conn).
```
type Stmt interface {
Close() error
NumInput() int
Exec(args []Value) (Result, error)
Query(args []Value) (Rows, error)
}
```
- `Close` cierra la conexión actual, pero sigue retornando la información de la ejecución de una operación de consulta.
- `NumInput` retorna el número de argumentos obligados. Los manejadores de bases de datos debería revisar la cantidad de argumentos cuando los resultados son mayor que 0, y retorna -1 cuando el manejador de la base de datos no conoce ningún argumento obligado.
- `Exec` ejecuta un comando `update/insert` preparados en `Prepare`, retorna un `Result`.
- `Query` ejecuta un comando `select` preparado en `Prepare`, retorna información.
## driver.Tx
Generalmente, las transacciones únicamente tienen métodos de envío y de vuelta atrás, y el manejador de las bases de datos solo necesita implementar estos dos métodos.
```
type Tx interface {
Commit() error
Rollback() error
}
```
## driver.Execer
Esta es una interfaz opcional.
```
type Execer interface {
Exec(query string, args []Value) (Result, error)
}
```
El el manejador no implementa esta interfaz, cuando llame `DB.Exec`, automáticamente llamará `Prepare`, retornará un `Stmt`. Después ejecutará el método `Exec` de `Smtt`, y cerrará el `Smtm`.
## driver.Result
Esta es la interface para los operaciones de `update/insert`.
```
type Result interface {
LastInsertId() (int64, error)
RowsAffected() (int64, error)
}
```
- `LastInsertId` retorna el identificador de autoincremento después de una operación de inserción.
- `RowsAffected` retorna la cantidad de filas afectadas por la operación.
## driver.Rows
Esta interfaz es el resultado de una operación de consulta.
```
type Rows interface {
Columns() []string
Close() error
Next(dest []Value) error
}
```
- `Columns` retorna la información de los campos de la base de datos. El segmento tiene correspondencia con los campos SQL únicamente. y no retorna todos los campos de la tabla de la base de datos.
- `Close` cierra el iterador sobre las columnas.
- `Next` retorna la siguiente información y la asigna a dest, convirtiendo todas las cadenas en arreglos de bytes, y retorna un error io.EOF si no existe mas data disponible.
## driver.RowsAffected
Este es un alias para int64, pero implementado por la interfaz Result.
```
type RowsAffected int64
func (RowsAffected) LastInsertId() (int64, error)
func (v RowsAffected) RowsAffected() (int64, error)
```
## driver.Value
Esta es una interfaz vacía que contiene cualquier tipo de información.
```
type Value interface{}
```
El `Value` debe ser algo con lo que los manejadores puedan operar, o nil, así que debería ser alguno de los siguientes tipos:
```
int64
float64
bool
[]byte
string [*] Exceptuando los Rows.Next, que no puede retornar una cadena.
time.Time
```
## driver.ValueConverter
Define una interfaz para convertir valores nativos en valores de driver.Value.
```
type ValueConverter interface {
ConvertValue(v interface{}) (Value, error)
}
```
Esta interfaz es comunmente usada en manejadores de bases de datos y tiene muchas características útiles:
- Convierte un driver.Value al tipo de campo correspondiente, por ejemplo convierte un int64 a un uint16.
- Conviernte una consulta de base de datos a un driver.Value.
- Conviernte un driver.Value a un usuario definido en la función `scan`.
## driver.Valuer
Define una interfaz para devolver un driver.Value.
```
type Valuer interface {
Value() (Value, error)
}
```
Muchos tipos implementan esta interface para la conversión entre driver.Value y ellos mismos.
En este punto, deberías saber un poco mas sobre el desarrollo de manejadores de bases de datos en Go. una vez que hayas implementado interfaces para operaciones como agregar, eliminar, actualizar, etc, existirán algunos problemas relacionados con comunicarse específicamente con algún tipo de bases de datos.
## database/sql
`database/sql` define en un alto nivel métodos existentes en `database/sql/driver` para tener las operaciones en un nivel mas conveniente, y sugiera que implementes una piscina de conexiones.
```
type DB struct {
driver driver.Driver
dsn string
mu sync.Mutex // protects freeConn and closed
freeConn []driver.Conn
closed bool
}
```
Como puedes ver, la función `Open` retorna una base de datos que tiene una `freeConn`, y esta es una piscina de conexiones simple. Su implementación es simple y fea. Usa `defer db.putConn(ci, err)` en la función `Db.Prepare` para colocar una conexión en la piscina de conexiones. Siempre que se vaya a llamar la función `Conn`, verifica la longitud de `freeConn`. Si es mayor a 0, significa que hay una conexión reusable y la retorna directamente a ti. De otra manera, crea una nueva conexión y la retorna.
## Enlaces
- [Índice](preface.md)
- Sección anterior: [Bases de Datos](05.0.md)
- Sección siguiente: [MySQL](05.2.md)

125
es/05.2.md Normal file
View File

@@ -0,0 +1,125 @@
# 5.2 MySQL
La pila LAMP ha sido muy popular en internet los últimos años. La M en LAMP significa MySQL. MySQL es famoso porque es de código abierto y fácil de usar. También porque viene como base de datos por defecto en el backend de muchos sitios web.
## Manejadores MySQL
Existen varios manejadores que soportan MySQL en Go. Algunos de ellos implementan la interfaz `database/sql`, y otros usan sus propias interfaces estándares.
- [https://github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) soporta `database/sql`, está escrita en Go puro.
- [https://github.com/ziutek/mymysql](https://github.com/ziutek/mymysql) soporta `database/sql` e interfaces definidas por el usuario, está escrita en Go puro.
Usaré el primer manejador en los siguientes ejemplos (uso este en mis proyectos personales también) y también lo recomiendo por las siguientes razones:
- Es un manejador de bases de datos nuevo y suporta muchas características
- Soporta completamente la interfaz `database/sql`
- Soporta el keep-alive, conexiones largas con seguridad entre hilos
## Ejemplos
En las siguientes secciones usaré las misma estructura de tablas para diferentes bases de datos, luego crear la base de datos de la siguiente manera:
```
CREATE TABLE `userinfo` (
`uid` INT(10) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(64) NULL DEFAULT NULL,
`departname` VARCHAR(64) NULL DEFAULT NULL,
`created` DATE NULL DEFAULT NULL,
PRIMARY KEY (`uid`)
);
```
El siguiente ejemplo muestra como operar en bases de datos con los estándares de `database/sql`.
```
package main
import (
_ "github.com/go-sql-driver/mysql"
"database/sql"
"fmt"
)
func main() {
db, err := sql.Open("mysql", "astaxie:astaxie@/test?charset=utf8")
checkErr(err)
// insertar
stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
checkErr(err)
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
checkErr(err)
id, err := res.LastInsertId()
checkErr(err)
fmt.Println(id)
// actualizar
stmt, err = db.Prepare("update userinfo set username=? where uid=?")
checkErr(err)
res, err = stmt.Exec("astaxieupdate", id)
checkErr(err)
affect, err := res.RowsAffected()
checkErr(err)
fmt.Println(affect)
// consultar
rows, err := db.Query("SELECT * FROM userinfo")
checkErr(err)
for rows.Next() {
var uid int
var username string
var department string
var created string
err = rows.Scan(&uid, &username, &department, &created)
checkErr(err)
fmt.Println(uid)
fmt.Println(username)
fmt.Println(department)
fmt.Println(created)
}
// eliminar
stmt, err = db.Prepare("delete from userinfo where uid=?")
checkErr(err)
res, err = stmt.Exec(id)
checkErr(err)
affect, err = res.RowsAffected()
checkErr(err)
fmt.Println(affect)
db.Close()
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
```
Permítanme explicar algunas de las funciones importantes aquí:
- `sql.Open()` abre un manejador de bases de datos registrado. Go-MySQL-Driver registra el manejador de MySQL aquí. El segundo argumento es el DSN (Data Source Name) que define la información para realizar la conexión con la base de datos. Soporta los siguientes formatos:
```
user@unix(/path/to/socket)/dbname?charset=utf8
user:password@tcp(localhost:5555)/dbname?charset=utf8
user:password@/dbname
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname
```
- `db.Prepare()` retorna la operación que va a ser ejecutada. También reetorna el estado de ejecución después de ejecutar el SQL.
- `db.Query()` ejecuta el SQL y retorna el resultado en Rows
- `stmt.Exec()` ejecuta el SQL que ha sido preparado y almacenado en un Stmt
Note que usamos el formato `=?` para pasar argumentos. Esto es necesario para prevenir ataques de inyección SQL.
## Enlaces
- [Índice](preface.md)
- Sección previa: [Interfaz database/sql](05.1.md)
- Siguiente sección: [SQLite](05.3.md)

149
es/05.3.md Normal file
View File

@@ -0,0 +1,149 @@
# 5.3 SQLite
SQLite es una base de datos embebida de código abierto. Es una base de datos contenida en si misma, tiene 0 configuracieon y soporta transacciones. Es altamente portable, fácil de usar, compacta, eficiente y confiable. En la mayoría de los casos solamente se necesita crear una archivo de base de datos para crear conectarse y operar en una base de datos. Si estás buscando una solución embebida, SQLite es una opción para considerar. Se puede decir que SQLite es una versión de código abierto de Access.
## Manejadores SQLite
Existen muchos manejadores de bases de datos para SQLite en Go, pero muchos de ellos no soportan los estándares de la interfaz `database/sql`.
- [https://github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) soporta `database/sql`, basado en cgo
- [https://github.com/feyeleanor/gosqlite3](https://github.com/feyeleanor/gosqlite3) no soporta `database/sql`, basado en cgo.
- [https://github.com/phf/go-sqlite3](https://github.com/phf/go-sqlite3) no soporta `database/sql`, basado en cgo.
El primer manejador es el único que soporta los estándares de la interfaz `database/sql`, por esto lo uso en mis proyectos, ya que será mas fácil migrar el código en el futuro si lo necesito.
## Ejemplos
Crearemos el siguiente SQL:
```
CREATE TABLE `userinfo` (
`uid` INTEGER PRIMARY KEY AUTOINCREMENT,
`username` VARCHAR(64) NULL,
`departname` VARCHAR(64) NULL,
`created` DATE NULL
);
```
Un ejemplo:
```
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./foo.db")
checkErr(err)
// insertar
stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)")
checkErr(err)
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
checkErr(err)
id, err := res.LastInsertId()
checkErr(err)
fmt.Println(id)
// actualizar
stmt, err = db.Prepare("update userinfo set username=? where uid=?")
checkErr(err)
res, err = stmt.Exec("astaxieupdate", id)
checkErr(err)
affect, err := res.RowsAffected()
checkErr(err)
fmt.Println(affect)
// consultar
rows, err := db.Query("SELECT * FROM userinfo")
checkErr(err)
var uid int
var username string
var department string
var created time.Time
for rows.Next() {
err = rows.Scan(&uid, &username, &department, &created)
checkErr(err)
fmt.Println(uid)
fmt.Println(username)
fmt.Println(department)
fmt.Println(created)
}
rows.Close() // Buen hábito cerrar
// eliminar
stmt, err = db.Prepare("delete from userinfo where uid=?")
checkErr(err)
res, err = stmt.Exec(id)
checkErr(err)
affect, err = res.RowsAffected()
checkErr(err)
fmt.Println(affect)
db.Close()
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
```
Puedes notar que el código es casi el mismo que en la sección pasada, y es porque solamente cambiamos el nombre del manejador de bases de datos registrado llamando a `sql.Open` para conectarnos a SQLite de una manera diferente.
Nota que algunas veces no puedes usar la sentencia `for` porque no tienes mas de una fila, entonces puedes usar la sentencia `if`:
```
if rows.Next() {
err = rows.Scan(&uid, &username, &department, &created)
checkErr(err)
fmt.Println(uid)
fmt.Println(username)
fmt.Println(department)
fmt.Println(created)
}
```
También tienes que ahcer un `rows.Next()` sin usar eso que no puedes obtener en la función `Scan`.
Transacciones
=============
El ejemplo de arriba muestra como puedes obtener datos de una base de datos, pero cuando quieres escribir aplicaciones web, no solo vas a consultar información de la base de datos sino que también quieres escribir en ella. Para este propósito tu puedes usar transacciones, porque puedes tener múltiples rutinas que accesan a la base de datos, y la base de datos se podría bloquear. Esto no es deseable en una aplicación web y el uso de transacciones es efectivo asegurando que las operaciones sean exitosas en total o fallen completamente, dependiendo de las circunstancias. Es claro que el uso de transacciones puede prevenir un montón de problemas que pueden ocurrir en una aplicación web.
```
trashSQL, err := database.Prepare("update task set is_deleted='Y',last_modified_at=datetime() where id=?")
if err != nil {
fmt.Println(err)
}
tx, err := database.Begin()
if err != nil {
fmt.Println(err)
}
_, err = tx.Stmt(trashSQL).Exec(id)
if err != nil {
fmt.Println("doing rollback")
tx.Rollback()
} else {
tx.Commit()
}
```
Como es claro en el código de arribam primero debes prepara un Statement, después debes ejecutarlo, dependiendo de la salida de esa ejecución, puedes volver atrás o mantenerlo.
Como última nota de esta sección, aquí está una herramienta de mantenimiento para SQLite: [http://sqlitebrowser.org](http://sqlitebrowser.org)
## Enlaces
- [Índice](preface.md)
- Sección anterior: [MySQL](05.2.md)
- Siguiente sección: [PostgreSQL](05.4.md)

123
es/05.4.md Normal file
View File

@@ -0,0 +1,123 @@
# 5.4 PostgreSQL
PostgreSQL es una base de datos relacional y de objetos disponible en muchas plataformas incluyendo Linux, FreeBSD, Solaris, Microsoft Windows y Mac OS X. Fue liberada bajo la licencia MIT y por eso es gratuita y de código abierto. Es más grande que MySQL porque está diseñada para uso empresarial y es una alternativa a Oracle. PostgreSQL es una buena elección para proyectos empresariales.
## Manejadores PostgreSQL
Existen muchos manejadores de bases de datos para PostgreSQL. Aquí hay algunos ejemplos de ellos:
- [https://github.com/bmizerany/pq](https://github.com/lib/pq) soporta `database/sql`, escrita en Go puro.
- [https://github.com/jbarham/gopgsqldriver](https://github.com/jbarham/gopgsqldriver) soporta `database/sql`, escrita en Go puro
- [https://github.com/lxn/go-pgsql](https://github.com/lxn/go-pgsql) soporta `database/sql`, escrita en Go puro
Usaré el primer ejemplo para explicar lo que sigue.
## Ejemplos
Crearemos el siguiente SQL:
```
CREATE TABLE userinfo
(
uid serial NOT NULL,
username character varying(100) NOT NULL,
departname character varying(500) NOT NULL,
Created date,
CONSTRAINT userinfo_pkey PRIMARY KEY (uid)
)
WITH (OIDS=FALSE);
```
Un ejemplo:
```
package main
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
"time"
)
const (
DB_USER = "postgres"
DB_PASSWORD = "postgres"
DB_NAME = "test"
)
func main() {
dbinfo := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable",
DB_USER, DB_PASSWORD, DB_NAME)
db, err := sql.Open("postgres", dbinfo)
checkErr(err)
defer db.Close()
fmt.Println("# Inserting values")
var lastInsertId int
err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId)
checkErr(err)
fmt.Println("last inserted id =", lastInsertId)
fmt.Println("# Updating")
stmt, err := db.Prepare("update userinfo set username=$1 where uid=$2")
checkErr(err)
res, err := stmt.Exec("astaxieupdate", lastInsertId)
checkErr(err)
affect, err := res.RowsAffected()
checkErr(err)
fmt.Println(affect, "rows changed")
fmt.Println("# Querying")
rows, err := db.Query("SELECT * FROM userinfo")
checkErr(err)
for rows.Next() {
var uid int
var username string
var department string
var created time.Time
err = rows.Scan(&uid, &username, &department, &created)
checkErr(err)
fmt.Println("uid | username | department | created ")
fmt.Printf("%3v | %8v | %6v | %6v\n", uid, username, department, created)
}
fmt.Println("# Deleting")
stmt, err = db.Prepare("delete from userinfo where uid=$1")
checkErr(err)
res, err = stmt.Exec(lastInsertId)
checkErr(err)
affect, err = res.RowsAffected()
checkErr(err)
fmt.Println(affect, "rows changed")
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
```
Nota que PostgreSQL usa el formato `$1, $2` en vez del `?` de MySQL, y tiene un formato distinto de DSN en el `sql.Open`
Otra cosa es que el manejador de PostgreSQL no soporta el `sql.Result.LastInsertId()`.
Entonces en lugar de
```
stmt, err := db.Prepare("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3);")
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
fmt.Println(res.LastInsertId())
```
usa `db.QueryRow()` y `.Scan()` para obtener el último valor insertado.
```
err = db.QueryRow("INSERT INTO TABLE_NAME values($1) returning uid;", VALUE1").Scan(&lastInsertId)
fmt.Println(lastInsertId)
```
## Enlaces
- [Índice](preface.md)
- Sección anterior : [SQLite](05.3.md)
- Siguiente sección: [Desarrollo de un ORM basado en beedb](05.5.md)

256
es/05.5.md Normal file
View File

@@ -0,0 +1,256 @@
# 5.5 Desarrollo de un ORM basado en beedb
( ***El proyecto beedb no está mantenido actualmente, pero el código sigue ahí*** )
( ***Project beedb is no longer maintained, but the code s still there*** )
beedb es un ORM ( object relational mapper ) desarrollado en Go, por mi.
Usa el lenguaje ideomático de Go para operar con las bases de datos, implementando un mapeo de estructura a base de datos que actua como un Framework ORM liviano de Go. El propósito de desarrollar este ORM no es solamente ayudar a la gente a aprender como implementar un ORM, sino también encontrar un buen balance entre funcionalidad y desempeño en lo que respecta a persistencia de datos.
beedb es un proyecto de código abierto que soporta la funcionalidad básica de un ORM, pero no soporta las consultas asociativas.
Como beedb soporta los estándares de la interfaz `database/sql` cualquier manejador que implemente esta interfaz podrá ser usado con beedb. Yo lo he probado con los siguientes manejadores:
Mysql: [github.com/ziutek/mymysql/godrv](github.com/ziutek/mymysql/godrv)
Mysql: [code.google.com/p/go-mysql-driver](code.google.com/p/go-mysql-driver)
PostgreSQL: [github.com/bmizerany/pq](github.com/bmizerany/pq)
SQLite: [github.com/mattn/go-sqlite3](github.com/mattn/go-sqlite3)
MS ADODB: [github.com/mattn/go-adodb](github.com/mattn/go-adodb)
ODBC: [bitbucket.org/miquella/mgodbc](bitbucket.org/miquella/mgodbc)
## Instalación
Puedes usar el comando `go get` para instalar beedb localmente.
```
go get github.com/astaxie/beedb
```
## Inicialización
Primero debes importar los paquetes necesarios:
```
import (
"database/sql"
"github.com/astaxie/beedb"
_ "github.com/ziutek/mymysql/godrv"
)
```
Luego necesitas abrir la conexión a la base de datos y crear un objeto beedb (MySQL en este ejemplo):
```
db, err := sql.Open("mymysql", "test/xiemengjun/123456")
if err != nil {
panic(err)
}
orm := beedb.New(db)
```
`beedb.New()` actualmente toma dos argumentos. El primero es el objeto de la base de datos y el segundo es para indicar que motor de base de datos estás usando. Si estás usando MySQL o SQLite puedes omitir el segundo argumento.
De otra manera, este argumento debe ser incluido, por ejemplo en el caso de SQLServer:
```
orm = beedb.New(db, "mssql")
```
PostgreSQL:
```
orm = beedb.New(db, "pg")
```
beedb soporta depuración. Usa el siguiente código para habilitarla:
```
beedb.OnDebug=true
```
Después debemos tener una estructura para la tabla `Userinfo` que hemos usado en las secciones anteriores.
```
type Userinfo struct {
Uid int `PK` // Si la llave primaria no es id, necesitas añadir la etiqueta `PK` para una llave primaria personalizada.
Username string
Departname string
Created time.Time
}
```
Se conciente que beedb autoconvierte la notación de camello a notación de guiones bajos. Por ejemplo, si tenemos `UserInfo` como el nombre de la estructura, beedb lo convertirá a `user_info` en la base de datos. La misma regla se aplica para los nombres de campos de la estructura.
## Insertar datos
El siguiente ejemplo muestra como usar beedb a guardar una estructura en vez de usar comandos planos de SQL. Usamos el método `Save` de beedb para aplicar el cambio.
```
var saveone Userinfo
saveone.Username = "Test Add User"
saveone.Departname = "Test Add Departname"
saveone.Created = time.Now()
orm.Save(&saveone)
```
Puedes verificar el campo `saveone.Uid` después de que este registro es insertado; su valor es un identificador autoincrementado por el que el método `Save` se ocupa por ti.
beedb provee otra manera de insertar datos, usando el estilo de mapas de Go.
```
add := make(map[string]interface{})
add["username"] = "astaxie"
add["departname"] = "cloud develop"
add["created"] = "2012-12-02"
orm.SetTable("userinfo").Insert(add)
```
Insertando varios datos:
```
addslice := make([]map[string]interface{}, 10)
add:=make(map[string]interface{})
add2:=make(map[string]interface{})
add["username"] = "astaxie"
add["departname"] = "cloud develop"
add["created"] = "2012-12-02"
add2["username"] = "astaxie2"
add2["departname"] = "cloud develop2"
add2["created"] = "2012-12-02"
addslice = append(addslice, add, add2)
orm.SetTable("userinfo").InsertBatch(addslice)
```
El método mostrado arriba es similar a una consulta encadenada, la cual te podría ser familiar si has usado jQuery. Retorna el objeto del ORM después de la llamada, entonces continua haciendo otros trabajos.
El método `SetTable` le dice al ORM que queremos insertar nuestra data en la tabla `userinfo`.
## Actualizar datos
Continuemos trabajando con el ejemplo de arriba, para ver como actualizar los datos. Ahora que tenemos la llave primeria de `saveone(Uid)`, beedb ejecuta una operación de actualizacieon en lugar de insertar un nuevo registro.
```
saveone.Username = "Update Username"
saveone.Departname = "Update Departname"
saveone.Created = time.Now()
orm.Save(&saveone) // update
```
Como antes, también puedes usar un mapa para actualizar los datos:
```
t := make(map[string]interface{})
t["username"] = "astaxie"
orm.SetTable("userinfo").SetPK("uid").Where(2).Update(t)
```
Permíteme explicar algunos de los métodos usados arriba:
- `.SetPK()` le dice al ORM que `uid` es la llave primaria del registro en la tabla `userinfo`
- `.Where()` define condiciones y suporta múltiples argumentos. Si el primer arguento es un entero, es una manera de acortar `Where("<llave primaria>=?", <valor>)`.
- `.Update()` acepta un mapa y actualiza la base de datos.
## Consultado datos
Las consultas con la interface beedb son muy fleibles. Veamos algunos ejemplos:
Ejemplo 1: Consultando por llaves primarias:
```
var user Userinfo
// Where acepta dos argumentos, el segundo entero
orm.Where("uid=?", 27).Find(&user)
```
Ejemplo 2:
```
var user2 Userinfo
orm.Where(3).Find(&user2) // Forma corta que omite el nombre de la llave primaria
```
Ejemplo 3, otras condiciones de consulta
```
var user3 Userinfo
// Where acepta dos argumentos, el segundo cadena
orm.Where("name = ?", "john").Find(&user3)
```
Ejemplo 4, condiciones mas complicadas:
```
var user4 Userinfo
// Where con tres argumentos aceptados
orm.Where("name = ? and age < ?", "john", 88).Find(&user4)
```
Ejemplos para obtener múltiples registros
Ejemplo 1, Obtiene 10 registros donde `id>3` Que comienzan en la posición 20
```
var allusers []Userinfo
err := orm.Where("id > ?", "3").Limit(10,20).FindAll(&allusers)
```
Ejemplo 2, omite el segundo argumento de límite, entonces comienza en 0 y obtiene 10 registros:
```
var tenusers []Userinfo
err := orm.Where("id > ?", "3").Limit(10).FindAll(&tenusers)
```
Ejemplo 3, obtener todos los recursos:
```
var everyone []Userinfo
err := orm.OrderBy("uid desc,username asc").FindAll(&everyone)
```
Como puedes ver, el método límite es para limitar el número de resultados.
- `.Limit()` suporta dos argumentos: el número de resultados comenzando en la posición. 0 es el valor por defecto de posición inicial.
- `.OrderBy()` es para ordenar resultados. El argumento es la condición de orden.
Todos los ejemplos aquí simplemente mapean registros a estructuras. También puedes colocar data en un mapa como sigue:
```
a, _ := orm.SetTable("userinfo").SetPK("uid").Where(2).Select("uid,username").FindMap()
```
- `.Select()` le dice a beedb cuantos campos quieres obtener desde la tabla de la base de datos. Si no es especifica, todos los registros son retornados por defecto.
- `.FindMap()` retorna el tipo `[]map[String][]byte`, entonces puedes convertir a otros tipos tu mismo.
## Eliminar datos
beedb provee métodos amplios para eliminar datos.
Ejemplo 1, eliminar un único registro
```
// saveone es el mismo del ejemplo anterior.
orm.Delete(&saveone)
```
Ejemplo 2, elimintar todos los usuarios
```
// alluser es un segmento que tiene múltiples usuarios
orm.DeleteAll(&alluser)
```
Ejemplo 3, Eliminar registros con SQL
```
orm.SetTable("userinfo").Where("uid>?", 3).DeleteRow()
```
## Consultas de asociación
beedb no soportar uniones entre asociaciones.
Sin embargo, desde que algunas aplicaciones necesitan esta caractarísticas, aquí está una implementación:
```
a, _ := orm.SetTable("userinfo").Join("LEFT", "userdetail", "userinfo.uid=userdetail.uid")
.Where("userinfo.uid=?", 1).Select("userinfo.uid,userinfo.username,userdetail.profile").FindMap()
```
Nosotros vemos un método llamado `.Join()` que toma tres argumentos
- El primer argumento: Tipo del Join; INNER, LEFT, OUTER, CROSS, etc.
- El segundo argumento: la tabla con la que quieres hacer la unión
- El tercer argumento: la condición de Join
## Group By y Having
beedb también tiene una implementación de `group by` y `having`.
```
a, _ := orm.SetTable("userinfo").GroupBy("username").Having("username='astaxie'").FindMap()
```
- `.GroupBy()` indica el campo con el que se va a agrupar.
- `.Having()` indica la condición del `having`.
## Futuro
He recibido un montón de retroalimentación de beedb de muchas personas al rededor del mundo y estoy pensando en reconfigurar los siguientes aspectos:
- Implementar una interfaz similar a `database/sql/driver` en orden de facilitar las operaciones CRUD
- Implementar asociaciones de bases de datos relacionales como uno a uno, muchos a uno y muchos a muchos.
Aquí hay un ejemplo:
```
type Profile struct {
Nickname string
Mobile string
}
type Userinfo struct {
Uid int
PK_Username string
Departname string
Created time.Time
Profile HasOne
}
```
- Auto crear tablas e indices.
- Implementar una piscina de conexiones usando rutinas.
## Enlaces
- [Índice](preface.md)
- Sección previa: [PostgreSQL](05.4.md)
- Siguiente sección section: [Bases de Datos NoSQL](05.6.md)

113
es/05.6.md Normal file
View File

@@ -0,0 +1,113 @@
# 5.6 Bases de Datos NoSQL
Una base de datos NoSQL provee un mecanismo para el almacenamiento y el retorno de información que usa modelos de consistencia menores que bases de datos relacionales en orden de alcanzar escalabilidad horizontal y mayor disponibilidad. Alguns autores se refieren a ellas como "No solamente SQL" para enfatizar que algunos sistemas NoSQL permiten consultas similares a SQL.
Al ser el lenguaje C del siglo XXI, Go tiene soporte para bases de datos NoSQL, incluyendo populares como redis, mongoDB, Cassandra y Membase.
## redis
redis es un sistema de almacenamiento llave valor como Memcached, que soporta los tipos de cadenas, listas conjuntos y conjuntos ordenados.
Aquí están algunos de los manejadores de bases de datos para redis:
- [https://github.com/alphazero/Go-Redis](https://github.com/alphazero/Go-Redis)
- [http://code.google.com/p/tideland-rdc/](http://code.google.com/p/tideland-rdc/)
- [https://github.com/simonz05/godis](https://github.com/simonz05/godis)
- [https://github.com/hoisie/redis.go](https://github.com/hoisie/redis.go)
Realicé una bifurcación de el último de estos paquetes, arreglé algunos servicios y lo usé en mi sistema de acortado de urls (2 millones de peticiones por día).
- [https://github.com/astaxie/goredis](https://github.com/astaxie/goredis)
Veamos como usar el manejador que bifurqué para operar con la base de datos:
```
package main
import (
"github.com/astaxie/goredis"
"fmt"
)
func main() {
var client goredis.Client
// Definir el puerto por defecto de redis
client.Addr = "127.0.0.1:6379"
// Manipulación de cadenas
client.Set("a", []byte("hello"))
val, _ := client.Get("a")
fmt.Println(string(val))
client.Del("a")
// Operaciones con listas
vals := []string{"a", "b", "c", "d", "e"}
for _, v := range vals {
client.Rpush("l", []byte(v))
}
dbvals,_ := client.Lrange("l", 0, 4)
for i, v := range dbvals {
println(i,":",string(v))
}
client.Del("l")
}
```
Como podemos ver es muy sencillo operar redis en Go, y como tiene un alto rendimiento. Los comandos de este cliente son casi lo mismo que los comandos que vienen con redis.
## mongoDB
mongoDB (de "humongous" (enorme)) es una bases de datos orientada a documentos de código abierto desarrollado y mantenida por 10gen. Hace parte de la familia de bases de datos NoSQL. En lugar de almacenar la información en tablas como es hecho en bases de datos relacionales 'clasicas', MongoDB guarda la información en estructurada en documentos al estilo JSON con esquemas dinámicos (MongoDB llama el formato BSON) haciendo que la integración de los datos en cierto tipo de aplicaciones sea mas fácil y rápido.
![](images/5.6.mongodb.png?raw=true)
Figura 5.1 MongoDB comparada con Mysql
El mejor manejador para mongoDB es llamado `mgo`, y es posible que se incluya en la librería estándar en el futuro.
Aquí está un ejemplo
```
package main
import (
"fmt"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
)
type Person struct {
Name string
Phone string
}
func main() {
session, err := mgo.Dial("server1.example.com,server2.example.com")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
c := session.DB("test").C("people")
err = c.Insert(&Person{"Ale", "+55 53 8116 9639"},
&Person{"Cla", "+55 53 8402 8510"})
if err != nil {
panic(err)
}
result := Person{}
err = c.Find(bson.M{"name": "Ale"}).One(&result)
if err != nil {
panic(err)
}
fmt.Println("Phone:", result.Phone)
}
```
Como podemos ver no hay muchas diferencias en lo que respecta a operar con mgo o bases de datos beedb; ambas son basadas en estructuras. Esta es la manera en que Go hace las cosas.
## Enlaces
- [Índice](preface.md)
- Sección previa: [Desarrollo de un ORM basado en beedb](05.5.md)
- Siguiente sección: [Resumen](05.7.md)

11
es/05.7.md Normal file
View File

@@ -0,0 +1,11 @@
# 5.7 Resumen
En este capítulo, primero aprendiste sobre el diseño de la interfaz `database/sql` y varios manejadores de bases de datos para varios tipos de bases de datos. Luego introducimos beedb, un ORM para bases de datos relacionales. También mostramos algunos ejemplos de operaciones con bases de datos. Al final, hablamos sobre algunas bases de datos NoSQL. Vimos como Go provee soporte para estas bases de datos NoSQL.
Después de leer este capítulo, espero que hayas entendido un poco mas como funcionan las bases de datos en Go. Esta es la parte mas importante del desarrollo web, por eso es importante que entiendas bien los conceptos de la interfaz `database/sql`.
## Enlaces
- [Índice](preface.md)
- Sección anterior: [Bases de Datos NoSQL ](05.6.md)
- Siguiente capítulo: [Almacenamiento de datos y sesiones](06.0.md)