Merge pull request #769 from contraslash/master
Translated chapters 6 and 7 to spanish
This commit is contained in:
11
es/06.0.md
Normal file
11
es/06.0.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 6 Almacenamiento de datos y Sesiones
|
||||
|
||||
Un tema importante en el desarrollo web es proveer una buena experiencia de usuario, pero el hecho de que HTTP sea un procolo sin estado parece ir en contra de ese espíritu. ¿Cómo podemos controlar el proceso completo de ver sitios a un usuario? La solución clásica es usar sesiones y cookies, donde las cookies sirven como mecanismo del lado del cliente y las sesiones son guardadas en el servidor con un identificador único para cada usuario. Nota que las sesiones puede ser pasadas en la URL, en las cookies o incluso en la base de datos, lo cual es mas seguro, pero puede alterar un poco el rendimiento de la aplicación.
|
||||
|
||||
En la sección 6.1 vamos a ablar sobre las diferencias entre cookies y sesiones. En la sección 6.2 aprenderás como usar sesiones en Go con una implementaición de un manejador de sesión. En la sección 6.3 hablaremos como el hijacking de sesiones y como prevernirlo cuando sabes que la sesión puede ser almacenada en calquier lugar. el manejador de sesiones que implementaremos en la sesión 6.3 guardará las sesiones en memoria, pero si queremos expandir la aplicación para permitir sesiones compartidas, es siempre mejor guardar las sesiones en nuestra base de datos. Hablaremos de como hacer esto en la sección 6.4.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Capítulo previo: [Capítulo 5 Resumen](05.7.md)
|
||||
- Siguiente sección: [Sesión y cookies](06.1.md)
|
||||
110
es/06.1.md
Normal file
110
es/06.1.md
Normal file
@@ -0,0 +1,110 @@
|
||||
## 6.1 Sesiones y cookies
|
||||
|
||||
Las sesiones y cookies son dos conceptos muy comunes en la web, y también son muy malentendidos. Sin embargo, son extremadamente importantes para la autorización de páginas, así como para tener información para estadísticas de las páginas. Echémos un vistazo a los dos siguientes casos de uso.
|
||||
|
||||
Supón que quieres acceder a una página que tiene acceso restringido al público, como el perfil de twitter de un usuario. Por supuesto que puedes abrir tu navegador y escribir tu usuario y contraseña para acceder a esa información, pero el concepto de "exploración web" significa usar un programa para automatizzar este proceso sin intervención humana. Por consiguiente, tenemos que encontrar que está ocurriendo realmente detrás de bambailnas cuando vamos a usar un navegador para acceder.
|
||||
|
||||
Cuando por primera vez recibimos una página de acceso y escribimos un usuario y contraseña, después de presionar el botor de "entrar", el navegador envía una petición POST a un servidor remoto. El servidor redirecciona al usuario a su página de inicio después de verificar la información de acceso y retorna una respuesta HTTP. La pregunta aquí es ¿cómo el servidor sabe quien tiene privilegios de acceso a cierta página? Como HTTP es sin estado el servidor no tiene cómo saber si se ha pasado la verificación en el paso anterior. La solución mas fácil en ingenua es pegar el nombre de usuario y la contraseña en la URL. esto funciona, pero coloca mucha presión en el servidor, porque siempre se debe validar un usuario y una contraseña en la base de datos, y puede hacer que la experiencia de usuario se deteriore. Una alternativa para lograr esta meta es guardar la identidad del usuario en el cliente y el servidor usando cookies y sesiones.
|
||||
|
||||
Las cookies, de una manera corta, almacenan información (incluyendo la información de acceso) en el computador del cliente. El navegador envía estas cookies cada vez que el usuario visita el mismo sitio, automáticamente completando el paso de acceso para el usuario.
|
||||
|
||||

|
||||
|
||||
Figure 6.1 principio de las cookies.
|
||||
|
||||
Las sesiones, por otro lado, guardan información histórica en el lado del servidor. El servidor utiliza un identificador de sesión para diferenciar las esiones, y también este identificador es generado por el servidor y debería ser aleatorio y único. Puedes utilizar cookies o argumentos en la URL para obtener la identidad del cliente.
|
||||
|
||||

|
||||
|
||||
Figura 6.2 principio de las sesiones.
|
||||
|
||||
## Cookies
|
||||
|
||||
Las cookies son mantenidas por los navegadores. Ellas pueden ser modificadas durante la comunicación entre servidores web y navegadores. Las aplicaciones web pueden acceder a la información de las cookies cuando el usuario visita los sitios correspondientes. La mayoría de navegadores web tienen una configuración para determinar la privacidad de las cookies. Puedes ver algo similar cuando lo abres.
|
||||
|
||||

|
||||
|
||||
Figura 6.3 cookies en navegadores.
|
||||
|
||||
Las cookies tiene un tiempo de expiración, y se dividen en dos tipos por su ciclo de vida: las cookies de sesión y las cookies persistentes.
|
||||
|
||||
Si tu aplicación no le da a una cookie un tiempo de expiración, el navegador no la guardará en el sistema de almacenamiento después de que el navegador sea cerrado. Estas cookies son llamadas cookies de sesión y son guardadas usualmente en memoria en vez del sistema de almacenamiento permanente.
|
||||
|
||||
Si tu aplicación define un tiempo de expiración (por ejemplo, setMaxAge(60*60*24)), el navegador *guardará* esta cookie en el sistema de ficheros, y no la elimninará hasta que se alcance el tiempo de expiración. Las cookies guardadas en el sistema de ficheros pueden ser accedidas por diferentes instancias del navegador, por ejemplo, por dos ventanas de Internet Explorer; diferentes navegadores usan diferentes procesos para manejar las cookies que son guardadas en memoria.
|
||||
|
||||
## Definir cookies en Go
|
||||
|
||||
Go usa la función `SetCookie` del paquete `net/http` para definir cookies:
|
||||
```
|
||||
http.SetCookie(w ResponseWriter, cookie *Cookie)
|
||||
```
|
||||
`w` es la respuesta de la petición y es cookie una estructura. Vamos a ver como se ve:
|
||||
```
|
||||
type Cookie struct {
|
||||
Name string
|
||||
Value string
|
||||
Path string
|
||||
Domain string
|
||||
Expires time.Time
|
||||
RawExpires string
|
||||
|
||||
// MaxAge=0 significa que no esta especificado el atributo 'Max-Age'.
|
||||
// MaxAge<0 significa elimiar la cookie ahora mismo, equivalente a 'Max-Age: 0'
|
||||
// MaxAge>0 significa a cantidad de segundos que debe permanecer en memoria la cookie
|
||||
MaxAge int
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
Raw string
|
||||
Unparsed []string // Texto plano con las parejas de atributos sin evaluar
|
||||
}
|
||||
```
|
||||
Aquí está un ejemplo de cookie:
|
||||
```
|
||||
expiration := time.Now().Add(365 * 24 * time.Hour)
|
||||
cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration}
|
||||
http.SetCookie(w, &cookie)
|
||||
```
|
||||
|
||||
## Obtener cookies en Go.
|
||||
|
||||
El ejemplo de arriba muestra como definir una cookie. Ahora vamos a obtener una cookie que ha sido definida:
|
||||
```
|
||||
cookie, _ := r.Cookie("username")
|
||||
fmt.Fprint(w, cookie)
|
||||
```
|
||||
Aquí hay otra manera de obtener una cookie:
|
||||
```
|
||||
for _, cookie := range r.Cookies() {
|
||||
fmt.Fprint(w, cookie.Name)
|
||||
}
|
||||
```
|
||||
Como puedes ver, es muy sencillo obtener cookies de las peticiones.
|
||||
|
||||
## Sesiones
|
||||
|
||||
Una sesión es una serie de acciones o mensajes. Por ejemplo, piensa en las acciones que existen entre contestart el teléfono y colgarlo, esto es un tipo de sesión. Cuando se refiere a protocolo de red, las sesiones tienen que ver con las conexiones entre navegador y servidor.
|
||||
|
||||
Las sesiones ayudan a almacenar el estado de la conexión entre servidor y cliente, y esto puede ser en forma de estructuras de datos.
|
||||
|
||||
Las sesiones son mecanismos del lado del servidor y usualmente empleaan tablas has (o algo similar) para guardar la información que llega.
|
||||
|
||||
Cuando una aplicación necesita asignar una nueva sesión a un cliente, el servidor debe verificar si existen sesiones anteriores del actual cliente con un único identificador de sesión. Si la sesión ya existe, el servidor continúa con la misma sesión al cliente. Por otro lado, si una sesión no existe para el cliente, el servidor crea una nueva sesión (estu usualmente ocurre cuando el servidor ha eliminado el identificador de sesión correspondiente, pero el usuario ha usado la sesión anterior manualmente).
|
||||
|
||||
La sesión por si misma no es complicada, pero su implementación y despliegue lo es, así que no puedes usar "una regla que le sirva a todas".
|
||||
|
||||
## Resumen
|
||||
|
||||
En conclusión, el propósito de las sesiones y cookies es el mismo. Ambas son para superar las conexiones sin estado de HTTP, pero ellas usa diferentes métdos. Las sesiones usan cookies para guardar los identificadores de sesión en el lado del cliente, y guardan toda la demás información del lado del servidor. Las cookies guardan toda la información del lado del cliente. Puedes haber notado que las cookies tienen algunos problemas de seguridad. Por ejemplo, usuarios y contraseñas pueden ser descubiertas y recogidas por sitios web maliciosos de terceros.
|
||||
|
||||
Aquí están dos ataques comunes:
|
||||
|
||||
1. La aplicación A define una cookie no esperada para la aplicación B.
|
||||
2. Ataques XSS: la aplicaión utiliza el Javascript `document.cookie` para acceder a las cookies de la aplicación B.
|
||||
|
||||
Después de finalizar esta sección, deberías saber algunos conceptos básicos de sesiones y cookies. También entender las diferencias entre ellas, así que no te mates la cabeza cuando los errores emergan. Discutiremos las sesiones en mas detalle en las secciones siguientes.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior: [Almacenamiento de datos y sesiones](06.0.md)
|
||||
- Siguiente sección: [Como usar sesiones en Go](06.2.md)
|
||||
222
es/06.2.md
Normal file
222
es/06.2.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# 6.2 ¿Cómo usar sesiones en Go?
|
||||
|
||||
En la sección 6.1, aprendimos que las sesiones son soluciones para la verificación de usuarios y que por ahora, la librería estándar de Go no tiene soporte para las sesiones o el manejo de ellas. Entonces, vamos a implementar nuestro propio manejador de sesiones en Go.
|
||||
|
||||
## Creando sesiones
|
||||
|
||||
El principio básico detrás de las sesiones es que el servidor mantiene la información de cada cliente , y los clientes tienen una única sesión para acceder a su información. Cuando los usuarios visitan la aplicación web, el servidor crea una sesión siguiendo los siguientes pasos en tanto sean necesarios:
|
||||
|
||||
- Crear un identificador de sesión único.
|
||||
- Abrir un espacio de almacenamiento: normalmente tenemos las sesiones en memoria, pero las perderás si el sistema accidentalmente se interrumpe. Esto puede ser un serio problema si la aplicación web maneja datos sensibles, como de comercio electrónico por ejemplo. Para solucionar este problema, puedes guardar los datos de sesión en la base de datos o en el sistema de ficheros. Eso hace que los datos sean mas fiables y fácil de compartir con otras aplicaciones, sin embargo la negociaciónde información es mas del lado del servidor para leer y escribir sesiones.
|
||||
- Enviar el identificador de sesión único al cliente.
|
||||
|
||||
El paso clave aquí es enviar un identificador único de sesión al cliente. En el contexto de una respuesta HTTP estándar, puedes usar el encabezado o el cuerpo que lo acompaña, por consiguiente, tenemos dos maneras de enviar identificadores de sesión a los clientes: por cookies o por URLs.
|
||||
|
||||
- Cookies: el servidor puede fácilmente usar `Set-cookie` dentro del encabezado de la respuesta para enviar el identificador de sesión del cliente, y el cliente puede usar esta cookie para las futuras peticiones; usualmente definimos el tiempo de expiración de cookies que contienen información de sesión a 0, lo que significa que la cookie será guardada en memoria y será eliminada cuando el usuario cierre el navegador.
|
||||
- URLs: concatenar el identificador de sesión como argumento en todas las páginas. Esta opción puede sonar desordenada, pero es la mejor opción si el navegador tiene deshabilitadas las cookies.
|
||||
|
||||
## Usando Go para manejar sesiones
|
||||
|
||||
Hemos hablado sobre la construcción de sesiones, y deberías tener una idea de como funciona, pero ¿cómo podemos usar sesiones en páginas dinámicas? Miremos de cerca al ciclo de vida de una sesión y luego podremos continuar la implementación de nuestro manejador de sesiones en Go.
|
||||
|
||||
### Diseño del manejo de sesión
|
||||
|
||||
Aquí está una lista de algunas de las consideraciones claves para el diseño del manejo de sesión
|
||||
|
||||
- Manejador de sesiones Global.
|
||||
- Mantener el identificador de sesión único.
|
||||
- Tener una sesión por cada usuario.
|
||||
- Almacenamiento de la sesión en memoria, archivos o base de datos.
|
||||
- Manejar las sesiones expiradas.
|
||||
|
||||
A continuación vamos a examinar un ejemplo completo de un manejador de sesiones en Go y el porqué de las decisiones de diseño que se tomaron.
|
||||
|
||||
### Manejador de sesión
|
||||
|
||||
Definir un manejador de sesiones global:
|
||||
```
|
||||
type Manager struct {
|
||||
cookieName string //private cookiename
|
||||
lock sync.Mutex // protects session
|
||||
provider Provider
|
||||
maxlifetime int64
|
||||
}
|
||||
|
||||
func NewManager(provideName, cookieName string, maxlifetime int64) (*Manager, error) {
|
||||
provider, ok := provides[provideName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
|
||||
}
|
||||
return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil
|
||||
}
|
||||
```
|
||||
Crear un manejador de sesiones global en la función `main()`:
|
||||
```
|
||||
var globalSessions *session.Manager
|
||||
// Then, initialize the session manager
|
||||
func init() {
|
||||
globalSessions = NewManager("memory","gosessionid",3600)
|
||||
}
|
||||
```
|
||||
Como sabemos que podemos guardar las sesiones de muchas maneras, como en memoria, sistema de ficheros o directamente en la base de datos, necesitamos definir un interfaz `Provider` en orden de representar la estructura bajo nuestro manejador de sesiones:
|
||||
```
|
||||
type Provider interface {
|
||||
SessionInit(sid string) (Session, error)
|
||||
SessionRead(sid string) (Session, error)
|
||||
SessionDestroy(sid string) error
|
||||
SessionGC(maxLifeTime int64)
|
||||
}
|
||||
```
|
||||
- `SessionInit` implementa la inicialización de una sesión y retorna una nueva sesión si es exitoso.
|
||||
- `SessionRead` retorna una sesión representada con el identificador de sesión sid. Crea una nueva sesión y la retorna si no existe.
|
||||
- `SessionDestroy` dado un sid, elimina la sesión correspondiente.
|
||||
- `SessionGC` elimina las variables de sesión basado en el criterio `maxLifeTime`.
|
||||
|
||||
Entonces, ¿qué métodos debería tener nuestra interfaz de sesión? Si tienes alguna experiencia en desarrollo web deberías saber que solo hay cuatro operaciones para las sesiones: definir el valor, obtener el valor, eliminar el valor y obtener el identificador actual. Entonces en nuestra interfaz de sesión vamos a tener estos cuatro métodos para realizar estas operaciones.
|
||||
```
|
||||
type Session interface {
|
||||
Set(key, value interface{}) error //set session value
|
||||
Get(key interface{}) interface{} //get session value
|
||||
Delete(key interface{}) error //delete session value
|
||||
SessionID() string //back current sessionID
|
||||
}
|
||||
```
|
||||
Este diseño está basado en `database/sql/driver`, que define la interface primero, luego registra la estructura específica que queremos usar. El siguiente código es la implementación interna de nuestra función de registro.
|
||||
```
|
||||
var provides = make(map[string]Provider)
|
||||
|
||||
// Register makes a session provider available by the provided name.
|
||||
// If a Register is called twice with the same name or if the driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, provider Provider) {
|
||||
if provider == nil {
|
||||
panic("session: Register provider is nil")
|
||||
}
|
||||
if _, dup := provides[name]; dup {
|
||||
panic("session: Register called twice for provider " + name)
|
||||
}
|
||||
provides[name] = provider
|
||||
}
|
||||
```
|
||||
### Identificacores de sesión únicos.
|
||||
|
||||
Los identificadores de sesión únics sirven para identificar usuarios en las aplicaciones web, por lo tanto deben ser únicos. El siguiente código muestra como lograr esto:
|
||||
```
|
||||
func (manager *Manager) sessionId() string {
|
||||
b := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
return ""
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(b)
|
||||
}
|
||||
```
|
||||
### Crear una sesión
|
||||
|
||||
Necesitamos localizar u obtener una sesión existente en orden de validar las operaciones de usuario. La función `SessionStart` es para verificar la existencia de cualquier sesión relacionada al usuario actual, y crearla si no ninguna sesión es encontrada.
|
||||
```
|
||||
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
cookie, err := r.Cookie(manager.cookieName)
|
||||
if err != nil || cookie.Value == "" {
|
||||
sid := manager.sessionId()
|
||||
session, _ = manager.provider.SessionInit(sid)
|
||||
cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxlifetime)}
|
||||
http.SetCookie(w, &cookie)
|
||||
} else {
|
||||
sid, _ := url.QueryUnescape(cookie.Value)
|
||||
session, _ = manager.provider.SessionRead(sid)
|
||||
}
|
||||
return
|
||||
}
|
||||
```
|
||||
Aquí hay un ejemplo que usa la sesión de usuario para el ingreso.
|
||||
```
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
sess := globalSessions.SessionStart(w, r)
|
||||
r.ParseForm()
|
||||
if r.Method == "GET" {
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
t.Execute(w, sess.Get("username"))
|
||||
} else {
|
||||
sess.Set("username", r.Form["username"])
|
||||
http.Redirect(w, r, "/", 302)
|
||||
}
|
||||
}
|
||||
```
|
||||
### Operaciones con valores: definir, obtener y eliminar.
|
||||
|
||||
La función `SessionStart` retorna una variable que implementa la interfaz de sesión. ¿Cómo la usamos?
|
||||
|
||||
Viste un `session.Get("uid")` en el ejemplo anterior para una operación básica. Ahora examinémolo en mejor detalle.
|
||||
```
|
||||
func count(w http.ResponseWriter, r *http.Request) {
|
||||
sess := globalSessions.SessionStart(w, r)
|
||||
createtime := sess.Get("createtime")
|
||||
if createtime == nil {
|
||||
sess.Set("createtime", time.Now().Unix())
|
||||
} else if (createtime.(int64) + 360) < (time.Now().Unix()) {
|
||||
globalSessions.SessionDestroy(w, r)
|
||||
sess = globalSessions.SessionStart(w, r)
|
||||
}
|
||||
ct := sess.Get("countnum")
|
||||
if ct == nil {
|
||||
sess.Set("countnum", 1)
|
||||
} else {
|
||||
sess.Set("countnum", (ct.(int) + 1))
|
||||
}
|
||||
t, _ := template.ParseFiles("count.gtpl")
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
t.Execute(w, sess.Get("countnum"))
|
||||
}
|
||||
```
|
||||
Como puedes ver, operar con sesiones simplemente es usar un patrón llave/valor en las operaciones de definición, obtención y eliminación.
|
||||
|
||||
Porque las sesiones tiene el concepto de tiempo de expiración definimos un Recolector de Basura para actualizar el último tiempo de modificación. De esta manera, el recolector de basura no eliminará sesiones que todavía están siendo usados.
|
||||
|
||||
### Definir sesiones.
|
||||
|
||||
Sabemos que las aplicaciones web tienen una operación de cerrar sesión. Cuando los usuarios cierran sesión, necesitamos eliminar la sesión correspondiente. Ya hemos usado la operación de reset en el ejemplo anterior, ahora vamos a mirar el cuerpo de la función.
|
||||
```
|
||||
//Destroy sessionid
|
||||
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){
|
||||
cookie, err := r.Cookie(manager.cookieName)
|
||||
if err != nil || cookie.Value == "" {
|
||||
return
|
||||
} else {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
manager.provider.SessionDestroy(cookie.Value)
|
||||
expiration := time.Now()
|
||||
cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1}
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
}
|
||||
```
|
||||
### Eliminar sesiones
|
||||
|
||||
Vamos a ver como dejar al manejador de sesiones eliminar una sesión. Necesitamos iniciar el recolector de baseura en la función `main()`:
|
||||
```
|
||||
func init() {
|
||||
go globalSessions.GC()
|
||||
}
|
||||
|
||||
func (manager *Manager) GC() {
|
||||
manager.lock.Lock()
|
||||
defer manager.lock.Unlock()
|
||||
manager.provider.SessionGC(manager.maxlifetime)
|
||||
time.AfterFunc(time.Duration(manager.maxlifetime), func() { manager.GC() })
|
||||
}
|
||||
```
|
||||
Como podemos ver el recolector de basura hace un uso completo del paquete `time`. Automáticamente llama al recolector de basura cuando la sesión se termina, asegurando que todas las sesiones se puedan usar durante `maxLifeTime`. Una solución similar puede usarse para contar los usuarios activos.
|
||||
|
||||
## Resumen
|
||||
|
||||
Hemos implementado un manejador de sesión global para una aplicación web y definido la interfaz `Provider` como implementación de una `Session`. En la siguiente sección vamos a hablar sobre como implementar un `Provider` para una estructura de almacenamiento de sesiones, que podrás referencia en el futuro.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior: [Sesiones y cookies](06.1.md)
|
||||
- Siguiente sección: [Almacenamiento de sesiones](06.3.md)
|
||||
139
es/06.3.md
Normal file
139
es/06.3.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# 6.3 Almacenamiento de sesiones
|
||||
|
||||
Introducimos los principios de un manejador de sesiones simple en la sección anterior, y entre otras cosas, definimos una interfaz de almacenamiento de sesión. En esta sección, voy a mostrarte un ejemplo de un motor de sesiones basado en memoria que implementa esa interfaz. Puedes mejorar este a otros mecanísmos de almacenamiento también.
|
||||
```
|
||||
package memory
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"github.com/astaxie/session"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var pder = &Provider{list: list.New()}
|
||||
|
||||
type SessionStore struct {
|
||||
sid string // identificador único de sesión
|
||||
timeAccessed time.Time // último tiempo de acceso
|
||||
value map[interface{}]interface{} // valor de la sesión accedida
|
||||
}
|
||||
|
||||
func (st *SessionStore) Set(key, value interface{}) error {
|
||||
st.value[key] = value
|
||||
pder.SessionUpdate(st.sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *SessionStore) Get(key interface{}) interface{} {
|
||||
pder.SessionUpdate(st.sid)
|
||||
if v, ok := st.value[key]; ok {
|
||||
return v
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *SessionStore) Delete(key interface{}) error {
|
||||
delete(st.value, key)
|
||||
pder.SessionUpdate(st.sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *SessionStore) SessionID() string {
|
||||
return st.sid
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
lock sync.Mutex // bloqueo
|
||||
sessions map[string]*list.Element // guardar en memoria
|
||||
list *list.List // recikector de basura
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionInit(sid string) (session.Session, error) {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
v := make(map[interface{}]interface{}, 0)
|
||||
newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v}
|
||||
element := pder.list.PushBack(newsess)
|
||||
pder.sessions[sid] = element
|
||||
return newsess, nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionRead(sid string) (session.Session, error) {
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
return element.Value.(*SessionStore), nil
|
||||
} else {
|
||||
sess, err := pder.SessionInit(sid)
|
||||
return sess, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionDestroy(sid string) error {
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
delete(pder.sessions, sid)
|
||||
pder.list.Remove(element)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionGC(maxlifetime int64) {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
|
||||
for {
|
||||
element := pder.list.Back()
|
||||
if element == nil {
|
||||
break
|
||||
}
|
||||
if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() {
|
||||
pder.list.Remove(element)
|
||||
delete(pder.sessions, element.Value.(*SessionStore).sid)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pder *Provider) SessionUpdate(sid string) error {
|
||||
pder.lock.Lock()
|
||||
defer pder.lock.Unlock()
|
||||
if element, ok := pder.sessions[sid]; ok {
|
||||
element.Value.(*SessionStore).timeAccessed = time.Now()
|
||||
pder.list.MoveToFront(element)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
pder.sessions = make(map[string]*list.Element, 0)
|
||||
session.Register("memory", pder)
|
||||
}
|
||||
```
|
||||
|
||||
El ejmplo superior implementa un mecanismo de almacenamiento de sesiones en memoria. Usa la función `init()` para registrar este motor de almacenamiento al manejador de sesiones. ¿Cómo registramos este motor en nuestro programa principal?
|
||||
```
|
||||
import (
|
||||
"github.com/astaxie/session"
|
||||
_ "github.com/astaxie/session/providers/memory"
|
||||
)
|
||||
```
|
||||
Usamos el mecanismo de importación del guión bajo (que invoca a la funcuón `init` del paquete automáticamente) para registrar este motor al manejador de sesiones. Luego usamos el siguiente código para inicializar el manejador de sesión.
|
||||
```
|
||||
var globalSessions *session.Manager
|
||||
|
||||
// initialize in init() function
|
||||
func init() {
|
||||
globalSessions, _ = session.NewManager("memory", "gosessionid", 3600)
|
||||
go globalSessions.GC()
|
||||
}
|
||||
```
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección previa: [Cómo usar sesiones en Go](06.2.md)
|
||||
- Siguiente sección: [Prevenir el hijacking de sesión](06.4.md)
|
||||
92
es/06.4.md
Normal file
92
es/06.4.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# 6.4 Prevenir el hijacking de sesión
|
||||
|
||||
El hijacking de sesión es una amenaza conocida y seria de seguridad. Los clientes usan el identificador de sesión para validarse y otros propósitos cuando se comunican con el servidor. Desafortunadamente, paquetes deterceros pueden capturar estas comunicaciones y encontrar el identificador de sesión del cliente.
|
||||
|
||||
En esta sección vamos a mostrar como hijackear una sesión con propósitos educacionales.
|
||||
|
||||
## El proceso de hijacking de sesión
|
||||
|
||||
El siguiente código es un contador para la variable `count`:
|
||||
```
|
||||
func count(w http.ResponseWriter, r *http.Request) {
|
||||
sess := globalSessions.SessionStart(w, r)
|
||||
ct := sess.Get("countnum")
|
||||
if ct == nil {
|
||||
sess.Set("countnum", 1)
|
||||
} else {
|
||||
sess.Set("countnum", (ct.(int) + 1))
|
||||
}
|
||||
t, _ := template.ParseFiles("count.gtpl")
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
t.Execute(w, sess.Get("countnum"))
|
||||
}
|
||||
```
|
||||
El contenido de `count.gtpl` es el siguiente:
|
||||
```
|
||||
Hi. Now count:{{.}}
|
||||
```
|
||||
Podemos ver el siguiente contenido en el navegador:
|
||||
|
||||

|
||||
|
||||
Figur 6.4 conteo en el navegador.
|
||||
|
||||
Manté refrescando hasta que el número sea 6, entonces abre el manejador de cookies (Uso chrome aquí). Podrás ver la siguiente información:
|
||||
|
||||

|
||||
|
||||
Figura 6.5 cookies guardadas en el navegador.
|
||||
|
||||
Este es un paso muy importante: abre otro navegador (Uso firefox aquí), copia la URL en el nuevo navegador, abre el simulador de cookies para crear una nueva cookie y escribe exactamente el mismo valor que la cookie que vimos en el primer navegador.
|
||||
|
||||

|
||||
|
||||
Figura 6.6 Simular una cookie.
|
||||
|
||||
Refresca la página y verás lo siguiente:
|
||||
|
||||

|
||||
|
||||
Figura 6.7 hijacking de la sesión exitoso.
|
||||
|
||||
Aquí podemos que ver que podemos hijackear las sesiones entre diferentes navegadores y las acciones relizadas en uno pueden afectar el estado de la página del otro navegador. Porque HTTP es sin estado, no hay manera de saber si el identificador de sesión de firefox fue simulado, y chrome tampoco puede saber que su sesión fue hijackeada.
|
||||
## prevenir el hijacking de sesión
|
||||
|
||||
### cookie solamente y token
|
||||
|
||||
A través de este simple ejemplo de hijacking de sesión, puedes ver que es muy peligroso porque permise a los atacantes hacer lo que quieran. ¿Cómo podemos prevenir el hijacking de sesión?
|
||||
|
||||
El primer paso es únicamente definir el identificador en las cookies, en vez de las URLs. También asignr la propiedad httponly cookie a verdadero. Esto restringe a los scripts del lado del cliente ganen acceso al identificar de sesión. Usando estas técnicas las cookie sno pueden ser accesadas por XSS y no será tan fácil como se demostró robar un identificador de sesión del manejador de cookies.
|
||||
|
||||
El segundo paso es agregar un token a cada petición. Similar a la manera en la que lidiamos con la repetición de subida de formularios en una sección anterior. Agregamos un campo oculto que contenga un token. Cuando una petición es enviada al servidor, podemos verificar el token para saber si es único.
|
||||
```
|
||||
h := md5.New()
|
||||
salt:="astaxie%^7&8888"
|
||||
io.WriteString(h,salt+time.Now().String())
|
||||
token:=fmt.Sprintf("%x",h.Sum(nil))
|
||||
if r.Form["token"]!=token{
|
||||
// ask to log in
|
||||
}
|
||||
sess.Set("token",token)
|
||||
```
|
||||
### Tiempo de agotamiento del identificador de sesión
|
||||
|
||||
Otra solución es crear un tiempo para cada sesión, y reemplazar los identificadors de sesión con algunos nuevos. Esto puede prevenir las sesiones de ser hijackeadas bajo ciertas circunstancias, como cuando el hijacking se hace tarde.
|
||||
```
|
||||
createtime := sess.Get("createtime")
|
||||
if createtime == nil {
|
||||
sess.Set("createtime", time.Now().Unix())
|
||||
} else if (createtime.(int64) + 60) < (time.Now().Unix()) {
|
||||
globalSessions.SessionDestroy(w, r)
|
||||
sess = globalSessions.SessionStart(w, r)
|
||||
}
|
||||
```
|
||||
Cuando definimos el valor a guardar el tiempo de creación, si no ha expirado (60 segundos aquí). Este paso puede muy a menudo fristrar intentos de ataques de hijacking.
|
||||
|
||||
Al combinar las dos técnias de arriba podemos ser capaces de prevenir la mayoría de ataques de hijacking de que sean exitosos. Por otra parte, los identificadores de sesión que son reestablecidos frecuentemente resultan en que el atacante siempre obtiene identificadores de sesión expirados. También definiendo la configuración httponly en cookies y asegurarnos que la sesión solo pueda ser pasada por medio de cookies, todos los ataques por URL pueden ser mitigados. Finalmente definimos `MaxAge=0` en nuestras cookies, lo que significa que el identificador de sesión no será almacenado en el historial del navegador.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección previa: [Almacenamiento de sesiones](06.3.md)
|
||||
- Siguiente sección: [Resumen](06.5.md)
|
||||
9
es/06.5.md
Normal file
9
es/06.5.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 6.5 Resumen
|
||||
|
||||
En este capítulo, aprendimos la definición y propósito de las sesiones y la cookies y la relación entre ellas dos. Desde que Go no soporta las sesiones en su librería estándar, nosotros también diseñamos un manejador de sesión. Pasamos através de crear un cliente de sesiones hasta la eliminación. También definimos una interfaz llamada `Provider` que soporta todas las estructuras de almacenamiento de sesiones. En la sección 6.3 implementamos un manejador de sesiones para hacer persistir la información de usuario entre secciones. En la sección 6.4 mostramos una manera de hijackear una sesión. Luego miramos algunas herramientas para prevenir las sesiones de este ataque. Espero que ahora entiendas mas de los principio básicos sobre las sesiones y saber que las puedes usar con seguridad en tus aplicaciones.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección previa: [Prevenir el hijacking de sesión](06.4.md)
|
||||
- Siguiente capítulo: [Archivos de texto](07.0.md)
|
||||
11
es/07.0.md
Normal file
11
es/07.0.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 7 Archivos de texto
|
||||
|
||||
Manejar archivos de texto es una gran parte del desarrollo web. A menudo para producir o manejar contenido el contenido de texto recibido, incluyendo cadenas, números, JSON, XML, etc. Como un lenguaje de alto rendimiento, Go tiene gran soporte para esto en su librería estándar. Encontrarás que las librerías de soporte son mamravillosas, y te permitirán fácilmente lidiar con cuanlquier contenido de texto que puedas encontrar. Este capítulo contiene 4 secciones y te dará una intruducción al procesamiento de texto en Go.
|
||||
|
||||
XML es un lenguaje interactivo comúnmente usado en muchas APIs, muchos servidores web que están escritos en Java, usan XML como su lenguaje de interacción estándar. Hablaremos mas de esto en la sección 7.1. En la sección 7.2, miraremos JSON, que se ha vuelto muy popular en los años recientes y es mucho mas conveniente que XML. En la sección 7.3, vamos a hablar sobre expresiones regulares que (para la mayoría de gente) parecen un lenguaje usado por aliens. En la sección 7.4, verás como el patrón MVS es usado para desarrollar aplicaciones en Go y también como usar el paquete `template` de Go para usar plantillas en tus vistas. En la sección 7.5, vamos a introducir las operaciones de archivos y carpetas. Finalmente explicaremos las operaciones de cadenas en la sección 7.6.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Capítulo previo: [Resumen del capítulo 6](06.5.md)
|
||||
- Siguiente sección: [XML](07.1.md)
|
||||
216
es/07.1.md
Normal file
216
es/07.1.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# 7.1 XML
|
||||
|
||||
XML es usado comúnmente como formato de comunicación de datos en servicios web. Hoy, está asumiento un rol mas y mas importante en el desarrollo web. En esta sección, vamos a introducir el cómo trabajar con XML a través de la librería estándar de Go.
|
||||
|
||||
No voy a hacer ningún intento de enseñar la sintaxis o convenciones. Por esto, por favor lea la documentación sobre XML. Nosotros nos enfocaremos en como codificar y decodificar archivos XML en Go.
|
||||
|
||||
Sopón que trabajas con información y tecnología y tienes que lidiar con el siguiente archivo de configuración en XML:
|
||||
```
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<servers version="1">
|
||||
<server>
|
||||
<serverName>Shanghai_VPN</serverName>
|
||||
<serverIP>127.0.0.1</serverIP>
|
||||
</server>
|
||||
<server>
|
||||
<serverName>Beijing_VPN</serverName>
|
||||
<serverIP>127.0.0.2</serverIP>
|
||||
</server>
|
||||
</servers>
|
||||
```
|
||||
El ejemplo superior contiene dos tipos de información sobre el servidor: El nombe del servidor y la IP. vamos a usar este documento en los siguientes ejemplos.
|
||||
|
||||
## Analizar XML
|
||||
|
||||
¿Cómo analizamos este documento XML? Podemos usar la función `Unmarshal` del paquete `xml` de Go para hace esto.
|
||||
```
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
```
|
||||
El parámetro `data` recibe un flujo de una fuente XML, y `v` es la estructura a la que quieres que vaya la salida analizada del XML. Esta es una interface, lo que significa que puedes convertir un XML a cualquier estructura que tu desees.Aquí, vamos a hablar de como convertir el XML al tipo `struct` desde que tenga estructuras similares.
|
||||
|
||||
Código de ejemplo:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Recurlyservers struct {
|
||||
XMLName xml.Name `xml:"servers"`
|
||||
Version string `xml:"version,attr"`
|
||||
Svs []server `xml:"server"`
|
||||
Description string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type server struct {
|
||||
XMLName xml.Name `xml:"server"`
|
||||
ServerName string `xml:"serverName"`
|
||||
ServerIP string `xml:"serverIP"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
file, err := os.Open("servers.xml") // Para acceso de lectura
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
return
|
||||
}
|
||||
v := Recurlyservers{}
|
||||
err = xml.Unmarshal(data, &v)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(v)
|
||||
}
|
||||
```
|
||||
XML es actualmente una estructura de datos tipo arbol, y podemos definir una estructura muy similar en Go, luego usamos `xml.Unmarshal` para convertir el XML a la estructura del objeto. El ejemplo de código imprimirá el siguiente contenido:
|
||||
```
|
||||
{{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}]
|
||||
<server>
|
||||
<serverName>Shanghai_VPN</serverName>
|
||||
<serverIP>127.0.0.1</serverIP>
|
||||
</server>
|
||||
<server>
|
||||
<serverName>Beijing_VPN</serverName>
|
||||
<serverIP>127.0.0.2</serverIP>
|
||||
</server>
|
||||
}
|
||||
```
|
||||
Usamos `xml.Unmarshal` para analizar el documento XML a la correspondiente estructura de objetos. También puedes ver que tenemo algo como `xml:serverName` en nuestra estructura. Esta es una característica de las estructuras llamadas `struct tags` para ayudar con la reflexión. Veamos la definición de `Unmarshal` de nuevo:
|
||||
```
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
```
|
||||
El primer argumento es un flujo de datos XML. El segundo argumento es el tipo de almacenamiento y soporta estructuras, segmentos y cadenas. El paquete XML de Go usa reflexión para el mapeo de datos, entonces todos los campos de v deberían ser exportados. Sin embargo, esto causa un problema: ¿Cómo sabemos que campo XML corresponde a la campo de la estructura mapeada? La respuesta está en el analizador de XML que aaliza la información en cierto orden. La librería permite encontrar la mejor coincidencia en la etiqueta de la estructura primero. Si una coincidencia no puede ser encontrada, entonces busca por los nombre de los campos. Se conciente que todas las etiquetas, nombres de campos y elementos del XML son sensibles a mayúsculas y minúsculas, entonces debes asegurarte que hay una correspondencia uno a uno para que el mapeo sea exitoso.
|
||||
|
||||
El mecanismo de reflexión de Go permite usar la información de la etiqueta para reflectar información del XML a la estructura del objeto. Si quieres saber mas sobre reflexión en Go, por favor dirígete a la documentación del paquete de reflexión y etiqueas en Go.
|
||||
|
||||
Aquí hay algunas reglas para el uso del paquete `xml` para la transformación de documentos XMl a estructuras:
|
||||
|
||||
- Si el campo de la es una cadena o un []byte con la etiqueta `",innerxml"`, `Unmarshal` asignará la información XML pura a el, como la descripción del ejemplo superior:
|
||||
```
|
||||
Shanghai_VPN127.0.0.1Beijing_VPN127.0.0.2
|
||||
```
|
||||
- Si el campo es llamado `XMLName` y su tipo es `xml.Name` entonces obtendrá el nombre del elemento, como `servers` en el ejemplo superior.
|
||||
- Si la etiqueta de un campo contiene el nombre de un elemento, entonces obtendrá el nombre del elemento como `servername` y `serverip` en el ejemplo superior.
|
||||
- Si el la etiqueta del campo contiene `",attr"`, entonces el obtendrá el atributo del elemento, como `version` en el ejemplo superior.
|
||||
- Si una etiqueta de campo contiene algo como `"a>b>c"`, esta obtendrá el valor del elemento c del nodo b del nodo a
|
||||
- Si un campo contiene `"="`, entonces obtendrá nada.
|
||||
- Si un campo contiene `",any"` entonces obtendrá todos los elementos que no encaje con las otras reglas.
|
||||
- Si el elemento XML tiene uno o mas comentarios, todos esos comentarios serán agregados al primer campo que contenga `",comments"`. Este tipo de campo puede ser una cadeana o un []byte. Si este no existe, todos los comentarios serán eliminados.
|
||||
|
||||
Estas reglas te dirán como definir etiqueta en estructuras. Una vez entiendas estas reglas, el mapeo de XML a estructuras será tan fácil como el ejemplo de arriba. Porque las etiquetas XML y los elementos XML tieen una correspondencia uno a uno. también podemos usar segmentos para representar múltiples elementos en el mismo nivel.
|
||||
|
||||
Nota que todos los campos de la estructura deben ser exportados (Primera en mayúscula) para que se puedan analizar correctamente.
|
||||
|
||||
## Producir XML
|
||||
|
||||
Si lo que queremos es producir un documento XML en vez de analizar uno. ¿Cómo hacemos esto en Go? Sorprendentemente, el paquete `xml` provee dos funciones que son `Marshal` y `MarshalIndent`, donde la segunda opción automáticamente identa el documento XML 'marshalizado'. Las definiciones son las siguientes:
|
||||
```
|
||||
func Marshal(v interface{}) ([]byte, error)
|
||||
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
|
||||
```
|
||||
El primer argumento de ambas de estas funciones es para almacenar el flujo de datos XML.
|
||||
|
||||
Veamos un ejemplo de como esto funciona:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Servers struct {
|
||||
XMLName xml.Name `xml:"servers"`
|
||||
Version string `xml:"version,attr"`
|
||||
Svs []server `xml:"server"`
|
||||
}
|
||||
|
||||
type server struct {
|
||||
ServerName string `xml:"serverName"`
|
||||
ServerIP string `xml:"serverIP"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
v := &Servers{Version: "1"}
|
||||
v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"})
|
||||
v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"})
|
||||
output, err := xml.MarshalIndent(v, " ", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
}
|
||||
os.Stdout.Write([]byte(xml.Header))
|
||||
|
||||
os.Stdout.Write(output)
|
||||
}
|
||||
```
|
||||
El ejemplo superior imprime la siguiente información:
|
||||
```
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<servers version="1">
|
||||
<server>
|
||||
<serverName>Shanghai_VPN</serverName>
|
||||
<serverIP>127.0.0.1</serverIP>
|
||||
</server>
|
||||
<server>
|
||||
<serverName>Beijing_VPN</serverName>
|
||||
<serverIP>127.0.0.2</serverIP>
|
||||
</server>
|
||||
</servers>
|
||||
```
|
||||
Como hemos definido en el ejemplo anterior, la razón por que que tenemos un `os.Stdout.Write([]byte(xml.Header))` es porque ambos `xml.MarshalIndent` y `xml.Marshal` no generan los encabezados XML por ellos mismos, entonces tenemos que específicamente imprimirlos para tener los documentos XML producidos correctamente.
|
||||
|
||||
Aquí vemos que `Marshal` también recibe un parámetro de tipo `interface{}`. Entonces ¿Cuáles son las reglas cuando 'marshaleamos' un documento XML?
|
||||
|
||||
- Si v es un arreglo o segmento, imprimirá los valores como un valor.
|
||||
- Si v es un puntero, imprimirá el contenido al que v está apuntando, imprimiendo nada cuando v es nil.
|
||||
- Si v es una interface, manejará la interface también.
|
||||
- Si v es de cualquier otro tipo, imprimirá el valor de ese tipo.
|
||||
|
||||
¿Cómo `xml.Marshal` decide el nombre de los elementos? Sigue las siguientes reglas:
|
||||
|
||||
- Si v es una estructura, define el en la etiqueta de XMLName.
|
||||
- Si el campo nombre es XMLName, y el tipo es xml.Name
|
||||
- Campo etiqueta en la estructura.
|
||||
- Campo nombre de la estructura
|
||||
- Nombre de tipo marshal.
|
||||
|
||||
Entonces necesitamos definir como asignar las etiquetas en orden de producil el documento XML final.
|
||||
|
||||
- XMLName no será impreso.
|
||||
- Campos que tengan etiquetas que contengan `"-"` no serán impresos.
|
||||
- Si una etiqueta contiene `"name,attr"` se usará como el nombre del atributo y el valor del campo como valor, como versión en el ejemplo superior.
|
||||
- Si la etiqueta contiene `",attr"`, usará el nombre del campo nombre como nombre y el valor del campo como atributo.
|
||||
- Si una etiqueta contiene `",chardata"`, imprimirá el caracter en vez del elemento.
|
||||
- Si una etiqueta contiene `",innerxml"`, imprimirá el valor plano.
|
||||
- Si una etiqueta contiene `",comment"`, la imprimirá como comentario sin escapar, entonces no debes tener "--" en su valor.
|
||||
- Si una etiqueta contiene `"omitempty"`, omitirá este campo si tiene valor cero, incluyendo false, 0, puntero nulo o interfaz nula, arreglos, segmentos, mapas y cadenas de longitud 0 .
|
||||
- Si una etiqueta contiene `"a>b>c"` imprimirá los tres elementos donde a contiene a b y b contienen a c, como el siguiente código.
|
||||
```
|
||||
FirstName string `xml:"name>first"`
|
||||
LastName string `xml:"name>last"`
|
||||
|
||||
<name>
|
||||
<first>Asta</first>
|
||||
<last>Xie</last>
|
||||
</name>
|
||||
```
|
||||
Puedes notar que las etiquetas de estructuras son muy útiles para lidiar con XML, y lo mismo va para otros tipos de formatos de datos, que discutiremos en las siguientes secciones. Si todavía encuentras que tienes problemas con las etiquetas de las estructuras, deberías posiblemente leer sobre la documentación de ellas antes de ver la siguiente sección.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior: [Archivos de texto](07.0.md)
|
||||
- Siguiente sección: [JSON](07.2.md)
|
||||
231
es/07.2.md
Normal file
231
es/07.2.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# 7.2 JSON
|
||||
|
||||
JSON (Javascript Object Notation) es un lenguaje de intercambio de datos liviano que está basado en descripción en texto. Sus ventajas incluyen ser auto descriptivo, fácil de entender, etc. Incluso aunque es un subconjunto de Javascript, JSON utiliza un formato distinto de texto. El resultado es que puede ser considerado un lenguaje independiente. JSON tiene una similaridad con la familia de los lenguajes C
|
||||
|
||||
La gran diferencia entre JSON y XML es que XML es un lenguaje de etiquetado, y JSON no lo es. JSON es mas pequeño y más rápido que XML, por eso es mucho mas rápido y fácil de analizar en navegadores, la cuel es una de las razones por la cual muchas plataformas prefieren usar JSON como su lenguaje de intercambio de datos.
|
||||
|
||||
Desde que JSON se ha convertido más y más importante en el desarrollo web, vamos a echarle un vistazo al soporte que le da Go a JSON. Encontrarás que la librería estándar de Go tiene muy buen soporte para codificar y decodificar JSON.
|
||||
|
||||
Aquí usamos JSON para representar el ejemplo de la sección anterior:
|
||||
```
|
||||
{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}
|
||||
```
|
||||
El resto de la sección usaremos este JSON para introducir los conceptos de JSON en Go.
|
||||
|
||||
## Analizar JSON
|
||||
|
||||
### Analizar a estructura
|
||||
|
||||
Supon que tienes un el JSON del ejemplo anterior. ¿Cómo podemos analizar esta información y mapearla a una estructura en Go? Go provee las siguientes funciones para este propósito.
|
||||
```
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
```
|
||||
Nosotros usar esta función como:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
ServerName string
|
||||
ServerIP string
|
||||
}
|
||||
|
||||
type Serverslice struct {
|
||||
Servers []Server
|
||||
}
|
||||
|
||||
func main() {
|
||||
var s Serverslice
|
||||
str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}`
|
||||
json.Unmarshal([]byte(str), &s)
|
||||
fmt.Println(s)
|
||||
}
|
||||
```
|
||||
En el ejemplo de arriba, definimos una estructura correspondiente en Go para nuestro JSON, utilizando un segmento para un arreglo de objetos JSON y nombre de campos como nuestras llaves JSON. Pero ¿cómo Go sabe que objeto JSON corresponde a que campo específico? Supón que tenemos una llave llamada `Foo` en JSON. ¿Cómo encontramos el campo correspondiente?
|
||||
|
||||
- Primero, Go intenta encontrar el campo exportado (en mayúscula) que contiene la etiqueta `Foo`.
|
||||
- Si ninguna coincidencia es encontrada, busca un campo cuyo nombre sea `Foo`
|
||||
- Si todavía no hay coincidencias, busca por algo como `Foo` o `FoO`, ignorando si es mayúscula o minúscula.
|
||||
|
||||
Puedes haber notado que todos los campos que van a ser asignados deberían ser exportados, y Go solamente asigna campos que pueden ser encontrados, ignorando todos los demás. Esto puede ser útil si tu quieres lidiar con pedazos grande de JSON pero solamete usar un subconjunto específico de él; la información que no necesitas puede ser fácilmente descartada.
|
||||
|
||||
### Analizar a una interfaz
|
||||
|
||||
Cuando sabemos que tipo de JSON esperar por adelantado, nosotros podemos analizarlo a una estructura específica. pero ¿Si no sabemos?
|
||||
|
||||
Sabemos que una interfaz {} puede ser cualquier cosa en Go, así que es el mejor contenedor para guardar nuestro JSON con un formato desconocido. El paquete JSON usa `map[String]interface{}` y `[]interface` para guardar todos los tipos de objetos JSON y arreglos. Aquí está una lista de las relaciones de mapeo de JSON:
|
||||
|
||||
- `bool` representan `booleanos JSON`,
|
||||
- `float64` representan `números JSON`,
|
||||
- `string` representan `cadenas JSON`
|
||||
- `nil` representan `JSON null`.
|
||||
|
||||
Supón que tienes la siguiente información JSON:
|
||||
```
|
||||
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
|
||||
```
|
||||
Ahora analizamos este JSON a una interface{}:
|
||||
```
|
||||
var f interface{}
|
||||
err := json.Unmarshal(b, &f)
|
||||
```
|
||||
`f` almacena un mapa, donde las llaves osn strings y los valores son interfaces.
|
||||
```
|
||||
f = map[string]interface{}{
|
||||
"Name": "Wednesday",
|
||||
"Age": 6,
|
||||
"Parents": []interface{}{
|
||||
"Gomez",
|
||||
"Morticia",
|
||||
},
|
||||
}
|
||||
```
|
||||
Entonces, ¿cómo accedemos a la información? Aserción de tipos.
|
||||
```
|
||||
m := f.(map[string]interface{})
|
||||
```
|
||||
Después de la aserión, puede usar el siguiente código para acceder a la información:
|
||||
```
|
||||
for k, v := range m {
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
fmt.Println(k, "is string", vv)
|
||||
case int:
|
||||
fmt.Println(k, "is int", vv)
|
||||
case float64:
|
||||
fmt.Println(k,"is float64",vv)
|
||||
case []interface{}:
|
||||
fmt.Println(k, "is an array:")
|
||||
for i, u := range vv {
|
||||
fmt.Println(i, u)
|
||||
}
|
||||
default:
|
||||
fmt.Println(k, "is of a type I don't know how to handle")
|
||||
}
|
||||
}
|
||||
```
|
||||
Como puedes ver, podemos analizar el JSON como un formato anónimo a través de interfaces y aserción de tipos.
|
||||
|
||||
El ejemplo superior es la solución oficial, pero la aserción de tipos no siempre es conveniente. Entonces, recomedo un proyecto de código abierto llamado `simplejson`, creado y mantenido por bitly. Aquí hay un ejemplo de cómo usarlo en un proyecto con JSON de formatos desconocidos:
|
||||
```
|
||||
js, err := NewJson([]byte(`{
|
||||
"test": {
|
||||
"array": [1, "2", 3],
|
||||
"int": 10,
|
||||
"float": 5.150,
|
||||
"bignum": 9223372036854775807,
|
||||
"string": "simplejson",
|
||||
"bool": true
|
||||
}
|
||||
}`))
|
||||
|
||||
arr, _ := js.Get("test").Get("array").Array()
|
||||
i, _ := js.Get("test").Get("int").Int()
|
||||
ms := js.Get("test").Get("string").MustString()
|
||||
```
|
||||
No es difícil ver que conveniente es esto. Revisa el repositorio para ver mas información: [https://github.com/bitly/go-simplejson](https://github.com/bitly/go-simplejson).
|
||||
|
||||
## Producir JSON
|
||||
|
||||
En muchas situaciones, necesitamos producir información JSON y responder a los clientes. En Go, el paquete JSON tiene una función llamada `Marshal` para hacer justamente esto:
|
||||
```
|
||||
func Marshal(v interface{}) ([]byte, error)
|
||||
```
|
||||
Supón que necesitas producir una lista de información de servidores. Tenemos el siguiente ejemplo:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
ServerName string
|
||||
ServerIP string
|
||||
}
|
||||
|
||||
type Serverslice struct {
|
||||
Servers []Server
|
||||
}
|
||||
|
||||
func main() {
|
||||
var s Serverslice
|
||||
s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"})
|
||||
s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"})
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
fmt.Println("json err:", err)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
}
|
||||
```
|
||||
Salida:
|
||||
```
|
||||
{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]}
|
||||
```
|
||||
Como sabes, todos los nombres de campo tienen la primera letra en mayúscula, pero si quieres ver los campos de tu JSON con la primera en minúscula, deberías usar las `etiquetas de estructura`. De otra manera no producirá información para los campos privados.
|
||||
```
|
||||
type Server struct {
|
||||
ServerName string `json:"serverName"`
|
||||
ServerIP string `json:"serverIP"`
|
||||
}
|
||||
|
||||
type Serverslice struct {
|
||||
Servers []Server `json:"servers"`
|
||||
}
|
||||
```
|
||||
Después de esta modificación, podemos producir la misma información JSON que antes.
|
||||
|
||||
Aquí hay algunos puntos que necesitas tener en mente cuando tratas de producir JSON:
|
||||
|
||||
- Etiquetas de campos que contengan `"-"` no estarán en la salida.
|
||||
- Si una etiqueta contiene un nombre personalizado, Go usará esto en lugar del nombre del campo, como `serverName` en el ejemplo superior.
|
||||
- Si una etiqueta contiene `omitempty`, este campo no estará en la salida si tiene un valor 0.
|
||||
- Si el campo es de tipo booleano, cadena, entero, etc, y su etiqueta contiene `",string"`, Go convertirá este campo en su tipo correspondiente de JSON.
|
||||
|
||||
Ejemplo:
|
||||
```
|
||||
type Server struct {
|
||||
// ID will not be outputed.
|
||||
ID int `json:"-"`
|
||||
|
||||
// ServerName2 will be converted to JSON type.
|
||||
ServerName string `json:"serverName"`
|
||||
ServerName2 string `json:"serverName2,string"`
|
||||
|
||||
// If ServerIP is empty, it will not be outputted.
|
||||
ServerIP string `json:"serverIP,omitempty"`
|
||||
}
|
||||
|
||||
s := Server {
|
||||
ID: 3,
|
||||
ServerName: `Go "1.0" `,
|
||||
ServerName2: `Go "1.0" `,
|
||||
ServerIP: ``,
|
||||
}
|
||||
b, _ := json.Marshal(s)
|
||||
os.Stdout.Write(b)
|
||||
```
|
||||
Salida:
|
||||
```
|
||||
{"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""}
|
||||
```
|
||||
La función `Marshal` solamente retorna información cuando termina exitosamente, entonces aquí hay algunos puntos para tener en cuenta:
|
||||
|
||||
- JSON únicamente soporta cadenas como llaves, entonces si quieres codificar un mapa, deber ser de tipo `map[string]T`, donde `T` es el tipo en Go.
|
||||
- Tipos como canales, tipos complejos y funciones no pueden ser codificadas en JSON.
|
||||
- No trates de codificar información cíclica, llevará a una reecursión infinita.
|
||||
- Si el campo es un puntero, Go mostrará la información a la que apunta, o sino mostrará null si apunta a nil.
|
||||
|
||||
En esta sección, introducimos como decodificar y codificar información JSON en Go. También miramos a un proyecto de terceros llamado `simplejson` que es útil para analizar JSON de formato desconocido. Estos son todos los conceptos útilos para desarrolloar aplicaciones web en Go.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección previa: [XML](07.1.md)
|
||||
- Siguiente sección: [Expresiones regulares](07.3.md)
|
||||
240
es/07.3.md
Normal file
240
es/07.3.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# 7.3 Expresiones regulares
|
||||
|
||||
Las expresiones regulares ("Regexp") son una herramienta compliacada pero poderosa para la coincidencia de patrones y manipulación del texto. Sin embargo no se desempeñan tan bien como las coincidencias de texto puro, son mas flexibles. Basadas en su sintaxis, puedes filtrar casi cualquier tipo de texto de tu fuente d econtenidos. Si quieres recolectar datos en desarrollo web, no es dificil utilizar las Expresiones regulares para obtener datos importantes.
|
||||
|
||||
Go tiene un paquete `regexp`, que provee el soporte oficial para expresiones regulares. Si ya has usado expresiones regulares en otros lenguajes de programación, puedes estar familiarizado con ellas. Nota que Go implemente el estándar RE2, excepto por `\C`. Para mas detalles sigue el siguiente enlace: [http://code.google.com/p/re2/wiki/Syntax](http://code.google.com/p/re2/wiki/Syntax).
|
||||
|
||||
El paquete `strings` de Go puede hacer muchos trabajos como búsqueda (Contains, Index), reemplace, (Replace), análisis (Split, Join), etc. y es mucho mas rápido que las expresiones regulares. Sin embargo, todas esas son operaciones triviales. Si quieres buscar una cadena sin tener en cuenta mayúsculas o minúsculas, las expresiones regualres serán tu mejor opción. Entonces, si el paquete `strings` es suficiente para tus necesidades, úsalo porque es mas fácil de leer y entender; si necesitas hacer operaciones mas avanzadas, usa las expresiones regulares.
|
||||
|
||||
Si recuerdas la sección anterior de validación de formularios, usamos expresiones regulares para validar la información de entrada del usuario. Se conciente que todos los caracteres están en UTF-8. ¡Vamos a aprender mas del paquete `regexp`!.
|
||||
|
||||
## Coincidencias
|
||||
|
||||
El paquete `regexp` tiene tres funciones para coincidencias: Si encuentra una coincidencia retorna true, de lo contrario false.
|
||||
```
|
||||
func Match(pattern string, b []byte) (matched bool, error error)
|
||||
func MatchReader(pattern string, r io.RuneReader) (matched bool, error error)
|
||||
func MatchString(pattern string, s string) (matched bool, error error)
|
||||
```
|
||||
Las tres funciones verifican si `pattern` tiene una coincidencia en la entrada. retornando true si hay coincidencia. Sin embargo, si la expresión regular tiene errores, retornará un error. Los tres tipos de entradas aquí son `[]byte`, `RuneReader` y `string`.
|
||||
|
||||
Aquí está un ejemplo de como verificar una dirección IP:
|
||||
```
|
||||
func IsIP(ip string) (b bool) {
|
||||
if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
```
|
||||
Como puedes ver, usando un patrón en el paquete `regexp` no es tan diferente. Aquí hay otro ejemplo de verificar si la entrada de un usuario es válida:
|
||||
```
|
||||
func main() {
|
||||
if len(os.Args) == 1 {
|
||||
fmt.Println("Usage: regexp [string]")
|
||||
os.Exit(1)
|
||||
} else if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m {
|
||||
fmt.Println("Number")
|
||||
} else {
|
||||
fmt.Println("Not number")
|
||||
}
|
||||
}
|
||||
```
|
||||
En el ejemplo anterior usamos `Match(Reader|String)` para verificar si el contenido es álido, y ambos son fáciles de usar.
|
||||
|
||||
## Filtros
|
||||
|
||||
El modo Match puede verificar el contenido pero no cortarlo, filtrarlo o recoger información de él. Si quieres hacer esto, tienes que usar el modo complejo de las `regexp`.
|
||||
|
||||
Digamos que necesitamos escribir un crawler. Aquí un ejemplo de como usar las expresiones regulares para filtrar y cortar los datos.
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
resp, err := http.Get("http://www.baidu.com")
|
||||
if err != nil {
|
||||
fmt.Println("http get error.")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("http read error")
|
||||
return
|
||||
}
|
||||
|
||||
src := string(body)
|
||||
|
||||
// Convertimos las etiquetas HTML a minúsculas.
|
||||
re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
|
||||
src = re.ReplaceAllStringFunc(src, strings.ToLower)
|
||||
|
||||
// Removemos los estilos..
|
||||
re, _ = regexp.Compile("\\<style[\\S\\s]+?\\</style\\>")
|
||||
src = re.ReplaceAllString(src, "")
|
||||
|
||||
// Removemos los scripts.
|
||||
re, _ = regexp.Compile("\\<script[\\S\\s]+?\\</script\\>")
|
||||
src = re.ReplaceAllString(src, "")
|
||||
|
||||
// Removemos todas las etiquetas html y las reemplazamos con saltos de línea.
|
||||
re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
|
||||
src = re.ReplaceAllString(src, "\n")
|
||||
|
||||
// Removemos los saltos de línea contínuos.
|
||||
re, _ = regexp.Compile("\\s{2,}")
|
||||
src = re.ReplaceAllString(src, "\n")
|
||||
|
||||
fmt.Println(strings.TrimSpace(src))
|
||||
}
|
||||
```
|
||||
En este ejemplo, usamos Compile como un primer paso para el modo complejo. Esto verifica que la sintaxis de la expresión regular esté correcta, entonces retorna la `regexp` para analizar el contenido en otras operaciones.
|
||||
|
||||
Aquí están algunas funciones para analizar la sintaxis de las expresiones regulares:
|
||||
```
|
||||
func Compile(expr string) (*Regexp, error)
|
||||
func CompilePOSIX(expr string) (*Regexp, error)
|
||||
func MustCompile(str string) *Regexp
|
||||
func MustCompilePOSIX(str string) *Regexp
|
||||
```
|
||||
La diferencua entre `ComplePOSIX` y `Compile` es que el formador no tiene que usar la sintaxis POSIX cuando es una búsqueda de mas a la izquierda, y en el otro, únicamente la búsqueda de mas a la izquierda. Por ejemplo, para la expresión `[a-z]{2,4}` y el contenido `"aa09aaa88aaaa"`, `CompilePOSIX` retornará `aaa` pero `Compile` retornará `aa`. El prefijo `Must` significa un panic cuando la expresión regular no es correcta, retornando un error de otra manera.
|
||||
|
||||
Ahora que sabemos como crear expresiones regulares, vamos a ver cómo los métodos nos proveen una manera para operar con el contenido:
|
||||
```
|
||||
func (re *Regexp) Find(b []byte) []byte
|
||||
func (re *Regexp) FindAll(b []byte, n int) [][]byte
|
||||
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindAllString(s string, n int) []string
|
||||
func (re *Regexp) FindAllStringIndex(s string, n int) [][]int
|
||||
func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string
|
||||
func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int
|
||||
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
|
||||
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindIndex(b []byte) (loc []int)
|
||||
func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int)
|
||||
func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int
|
||||
func (re *Regexp) FindString(s string) string
|
||||
func (re *Regexp) FindStringIndex(s string) (loc []int)
|
||||
func (re *Regexp) FindStringSubmatch(s string) []string
|
||||
func (re *Regexp) FindStringSubmatchIndex(s string) []int
|
||||
func (re *Regexp) FindSubmatch(b []byte) [][]byte
|
||||
func (re *Regexp) FindSubmatchIndex(b []byte) []int
|
||||
```
|
||||
Estos 18 métodos contienen funciones idénticas para distintos tipos de entredad (segmentos, cadenas e io.RuneReader), entonces vamos a simplificar la lista ignorando algunos tipos de entrada como sigue:
|
||||
```
|
||||
func (re *Regexp) Find(b []byte) []byte
|
||||
func (re *Regexp) FindAll(b []byte, n int) [][]byte
|
||||
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
|
||||
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
|
||||
func (re *Regexp) FindIndex(b []byte) (loc []int)
|
||||
func (re *Regexp) FindSubmatch(b []byte) [][]byte
|
||||
func (re *Regexp) FindSubmatchIndex(b []byte) []int
|
||||
```
|
||||
Código de ejemplo:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := "I am learning Go language"
|
||||
|
||||
re, _ := regexp.Compile("[a-z]{2,4}")
|
||||
|
||||
// Encontrar la primera coincidencia.
|
||||
one := re.Find([]byte(a))
|
||||
fmt.Println("Find:", string(one))
|
||||
// Encontrar todas las coincidencias y guardarlas en un segmento, n menor a 0 significa todas las coincidencias, indicar el tamaño del segmento si es mayor a 0.
|
||||
all := re.FindAll([]byte(a), -1)
|
||||
fmt.Println("FindAll", all)
|
||||
|
||||
// Encuentra el índice de la primera coincidencia, posición de inicio y fin.
|
||||
index := re.FindIndex([]byte(a))
|
||||
fmt.Println("FindIndex", index)
|
||||
|
||||
// Encontrar los índices de todas las coincidencias, n hace lo mismo que el de arriba.
|
||||
allindex := re.FindAllIndex([]byte(a), -1)
|
||||
fmt.Println("FindAllIndex", allindex)
|
||||
|
||||
re2, _ := regexp.Compile("am(.*)lang(.*)")
|
||||
|
||||
// Encuentra la primera subcoincidencia y retorna un arreglo, el primero elemento contiene todos los elementos, el segundo contiene el resultado del primer (), en tercero, los resultados del segundo ()
|
||||
// Salida:
|
||||
// Primer elemento: "am learning Go language"
|
||||
// Segundo elemento: " learning Go ", Note que los espacios están en la salida.
|
||||
// Tercer elemento: "uage"
|
||||
submatch := re2.FindSubmatch([]byte(a))
|
||||
fmt.Println("FindSubmatch", submatch)
|
||||
for _, v := range submatch {
|
||||
fmt.Println(string(v))
|
||||
}
|
||||
|
||||
// Igual que FindIndex().
|
||||
submatchindex := re2.FindSubmatchIndex([]byte(a))
|
||||
fmt.Println(submatchindex)
|
||||
|
||||
// FindAllSubmatch, Encontrar todas las subcoincidencias.
|
||||
submatchall := re2.FindAllSubmatch([]byte(a), -1)
|
||||
fmt.Println(submatchall)
|
||||
|
||||
// FindAllSubmatchIndex, Encontrar todas las subcoincidencias.
|
||||
submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1)
|
||||
fmt.Println(submatchallindex)
|
||||
}
|
||||
```
|
||||
Como mencionamos anteriormente, Las expresiones regulares tienen 3 métodos para coincidencias, ellos hace exactamente lo mismo que las funciones exportadas. De hecho, estas funciones pueden ser exportadas:
|
||||
```
|
||||
func (re *Regexp) Match(b []byte) bool
|
||||
func (re *Regexp) MatchReader(r io.RuneReader) bool
|
||||
func (re *Regexp) MatchString(s string) bool
|
||||
```
|
||||
Ahora veamos como reemplazar cadenas usando expresiones regulares:
|
||||
```
|
||||
func (re *Regexp) ReplaceAll(src, repl []byte) []byte
|
||||
func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte
|
||||
func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte
|
||||
func (re *Regexp) ReplaceAllLiteralString(src, repl string) string
|
||||
func (re *Regexp) ReplaceAllString(src, repl string) string
|
||||
func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string
|
||||
```
|
||||
Estos son usadas en el ejemplo del crawler, entonces no se explicarán mas aquí.
|
||||
|
||||
Veamos la definición de `Expand`:
|
||||
```
|
||||
func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte
|
||||
func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte
|
||||
```
|
||||
¿Cómo usamos `Expand`?
|
||||
```
|
||||
func main() {
|
||||
src := []byte(`
|
||||
call hello alice
|
||||
hello bob
|
||||
call hello eve
|
||||
`)
|
||||
pat := regexp.MustCompile(`(?m)(call)\s+(?P<cmd>\w+)\s+(?P<arg>.+)\s*$`)
|
||||
res := []byte{}
|
||||
for _, s := range pat.FindAllSubmatchIndex(src, -1) {
|
||||
res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s)
|
||||
}
|
||||
fmt.Println(string(res))
|
||||
}
|
||||
```
|
||||
A este punto, hemos aprendido el paquete entero de `regexp` en Go, espero que ahora puedas entender más usando los métodos claves, entonces podrás hacer algo interesante por ti mismo.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección previa: [JSON](07.2.md)
|
||||
- Siguiente sección: [Plantillas](07.4.md)
|
||||
463
es/07.4.md
Normal file
463
es/07.4.md
Normal file
@@ -0,0 +1,463 @@
|
||||
# 7.4 Plantillas
|
||||
|
||||
## ¿Qué es una plantilla?
|
||||
|
||||
Afortunadamente eres conciente del modelo MVC (Modelo, Vista, Controlador), donde los modelos procesan datos, las vistas muestran los restulados y los controladores manejan las peticiones. Para las vistas, muchos lenguajes dinámicos generan dataos escribiendo código en archivos HTML estáticos. Por ejemplo JSP, los implementa insertando `<%=...=%>`, PHP insertando `<?php ... ?>`, etc.
|
||||
|
||||
La siguiente imagen muestra el mecanismo de las plantillas:
|
||||

|
||||
|
||||
Figura 7.1 Mecanismo de Plantillas
|
||||
|
||||
La mayoría del contenido que las aplicaciones web responden a los clientes es estático, y el contenido dinámico usualmente es muy pequeño. Por ejemplo, si necesitas mostrar una lista de usuarios que han visitado la página, solo el nombre de usuario necesita ser dinámico. El estilo de la lista es el mismo. Como puedes ver, las plantillas son útiles para reusar contenido.
|
||||
|
||||
## Plantillas en Go
|
||||
|
||||
En Go, tenemos el paquete `templata` que nos ayuda a manejar plantillas. Podemos usar funciones como `Pare`, `ParseFile` y `Execute` para cargar las plantillas desde texto plano o archivos, luego evaluar las partes dinámicas, como se muestra en la figura 7.1
|
||||
|
||||
Ejemplo:
|
||||
```
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
t := template.New("some template") // Create a template.
|
||||
t, _ = t.ParseFiles("tmpl/welcome.html", nil) // Parse template file.
|
||||
user := GetUser() // Get current user infomration.
|
||||
t.Execute(w, user) // merge.
|
||||
}
|
||||
```
|
||||
Como puedes ver, son muy fáciles de usar, cargar la información en plantillas de Go, como en cualquier otro lenguaje de programación.
|
||||
|
||||
Para conveniencia, vamos a usar las siguientes reglas en nuestros ejemplos:
|
||||
|
||||
- Usaremos `Parse` para reemplazar `ParseFiles`, porque `Parse` puede probar texto directamente desde cadenas, entonces no necesitaremos archivos extra.
|
||||
- Usaremos `main` en cada ejemplo, y no usaremos `handler`.
|
||||
- Usaremos `os.Stdout` para reemplazar `http.ResponseWriter`, desde que `os.Stdout` también implementa la interfaz `io.Writer`.
|
||||
|
||||
## Insertando información en las plantillas
|
||||
|
||||
Hemos mostrado como puedes analizar y renderizar plantillas. Vamos a dar un paso mas adelante para renderizar información en nuestras plantillas. Cada plantilla es un objeto en Go, entonces ¿cómo insertamos campos en nuestras plantillas?
|
||||
|
||||
### Campos
|
||||
|
||||
En Go, cada campo que intentas renderizar en una plantilla debería ser colocado dentro de `{{}}`. `{{.}}` es un atajo para el objeto actual, que es similar a su contraparte en Java o C++. Si necesitas accesar a los campos del objeto actual, deperías usar `{{.NombreDelCampo}}`. Nota que solament elos campos exportados pueden ser accesados en las plantillas. Aquí está un ejemplo:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
UserName string
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := template.New("fieldname example")
|
||||
t, _ = t.Parse("hello {{.UserName}}!")
|
||||
p := Person{UserName: "Astaxie"}
|
||||
t.Execute(os.Stdout, p)
|
||||
}
|
||||
```
|
||||
El ejemplo superior muestra `hello Astaxie` correctamente, pero si modificamos la estructura un poco, el siguiente error aparecerá:
|
||||
```
|
||||
type Person struct {
|
||||
UserName string
|
||||
email string // Field is not exported.
|
||||
}
|
||||
|
||||
t, _ = t.Parse("hello {{.UserName}}! {{.email}}")
|
||||
```
|
||||
Esta parte del código no será compilado porque intenta acceder a un campo que no ha sido exportado. Sin embargo, si tratamos de usar un campo que no existe, Go simplemente mostrará una cadena vacía en vez de un error.
|
||||
|
||||
Si imprimes `{{.}}` en una plantilla, Go mostrará una cadena formateada de este objeto, llamando `fmt` por debajo.
|
||||
|
||||
### Campos anidados
|
||||
|
||||
Sabemos como mostrar un campo. ¿Qué pasa si el campo es un objeto y también tiene sus propios campos? ¿Cómo los imprimimos todos en un ciclo? Podemos usar `{{with ...}}... {{end}}` y `{{range ...}}{{end}}` para este propósito.
|
||||
|
||||
- {% raw %}`{{range}}`{% endraw %} funciona como `range` en Go.
|
||||
- {% raw %}`{{with}}`{% endraw %} Permite escribir el mismo objeto una vez mas usando `.` como abreviación (***Similar al `with` de CB***).
|
||||
|
||||
Mas ejemplos:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Friend struct {
|
||||
Fname string
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
UserName string
|
||||
Emails []string
|
||||
Friends []*Friend
|
||||
}
|
||||
|
||||
func main() {
|
||||
f1 := Friend{Fname: "minux.ma"}
|
||||
f2 := Friend{Fname: "xushiwei"}
|
||||
t := template.New("fieldname example")
|
||||
t, _ = t.Parse(`hello {{.UserName}}!
|
||||
{{range .Emails}}
|
||||
an email {{.}}
|
||||
{{end}}
|
||||
{{with .Friends}}
|
||||
{{range .}}
|
||||
my friend name is {{.Fname}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`)
|
||||
p := Person{UserName: "Astaxie",
|
||||
Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"},
|
||||
Friends: []*Friend{&f1, &f2}}
|
||||
t.Execute(os.Stdout, p)
|
||||
}
|
||||
```
|
||||
### Condicionales
|
||||
|
||||
Si necesitas verificar por condicionales en las plantillas, puedes usar la sintaxis `if-else` como lo haces en programas regulares en Go. Si el argumento estéa vacío, el valor por defecto del `if` es `false`. El siguiente ejemplo muestra como se usa `if-else` en las plantillas:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tEmpty := template.New("template test")
|
||||
tEmpty = template.Must(tEmpty.Parse("Empty pipeline if demo: {{if ``}} will not be outputted. {{end}}\n"))
|
||||
tEmpty.Execute(os.Stdout, nil)
|
||||
|
||||
tWithValue := template.New("template test")
|
||||
tWithValue = template.Must(tWithValue.Parse("Not empty pipeline if demo: {{if `anything`}} will be outputted. {{end}}\n"))
|
||||
tWithValue.Execute(os.Stdout, nil)
|
||||
|
||||
tIfElse := template.New("template test")
|
||||
tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if part {{else}} else part.{{end}}\n"))
|
||||
tIfElse.Execute(os.Stdout, nil)
|
||||
}
|
||||
```
|
||||
Como puedes ver, es fácil usar `if-else` en las plantillas.
|
||||
|
||||
** Atención ** No puedes usar expresiones condicionales dentro del if, por ejemplo: `.Mail=="astaxie@gmail.com"`. Solo variables booleanas son aceptadas.
|
||||
|
||||
### Filtros (`|`)
|
||||
|
||||
Los usuarios de Unix deben estar familiarizados con el operador `pipe`, como `ls | gre "beego"`. Este comando filtra archivos y solamente muestra los que contienen la palabra "beego". Una cosa que me gusta sobre las plantillas de Go es que soportan filtros. Cualquier cosa en `{{}}` puede ser información de los filtros. El e-mail que usamos para renderizar en nuestra aplicación puede ser vulnerable a un ataque XSS. ¿Cómo podemos prevenir esta problemática usando filtros?
|
||||
```
|
||||
{{. | html}}
|
||||
```
|
||||
Podemos usar este comando para escapar el cuerpo del correo a HTML. Es muy similar a trabajar con el comando de Unix, es muy conveniente en las funciones de plantillas.
|
||||
|
||||
### Variables de plantillas
|
||||
|
||||
A veces necesitamos usar variables locales en las plantillas. Podemos usarlas con las palabras reservadas `with`, `range`, e `if`, y estará en el ámbito hasta que se use `{{end}}`. Aquí está un ejemplo de ocmo declarar una variable global:
|
||||
```
|
||||
$variable := pipeline
|
||||
```
|
||||
Mas ejemplos:
|
||||
```
|
||||
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
||||
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
||||
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
||||
```
|
||||
### Funciones de plantillas
|
||||
|
||||
Go usa el paquete `fmt` para darle formato a la salida de las plantillas. pero a veces necesitamos hacer algo mas. Por ejemplo considera el siguiente escenario: vamos a decir que queremos reemplazar `@` por `de` en nuestra dirección de email, como `astaxie de beego.me`. En este punto tenemos que escribir nuestra función personalizada.
|
||||
|
||||
Acada función de plantilla tiene un único nombre y está asociada con nuestro programa en Go como sigue:
|
||||
```
|
||||
type FuncMap map[string]interface{}
|
||||
```
|
||||
Supón que tienes una función de plantilla llamada `emailDeal` asociada con una función `EmailDealWith` en nuestro progrmaa en Go. Usamos el siguiente código para registrar esta función:
|
||||
```
|
||||
t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
|
||||
```
|
||||
definición de `EmailDealWith`:
|
||||
```
|
||||
func EmailDealWith(args …interface{}) string
|
||||
```
|
||||
Ejemplo:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Friend struct {
|
||||
Fname string
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
UserName string
|
||||
Emails []string
|
||||
Friends []*Friend
|
||||
}
|
||||
|
||||
func EmailDealWith(args ...interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
// encontrar el símbolo @
|
||||
substrs := strings.Split(s, "@")
|
||||
if len(substrs) != 2 {
|
||||
return s
|
||||
}
|
||||
// reemplazar el símbolo @ por " de "
|
||||
return (substrs[0] + " at " + substrs[1])
|
||||
}
|
||||
|
||||
func main() {
|
||||
f1 := Friend{Fname: "minux.ma"}
|
||||
f2 := Friend{Fname: "xushiwei"}
|
||||
t := template.New("fieldname example")
|
||||
t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
|
||||
t, _ = t.Parse(`hello {{.UserName}}!
|
||||
{{range .Emails}}
|
||||
an emails {{.|emailDeal}}
|
||||
{{end}}
|
||||
{{with .Friends}}
|
||||
{{range .}}
|
||||
my friend name is {{.Fname}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`)
|
||||
p := Person{UserName: "Astaxie",
|
||||
Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"},
|
||||
Friends: []*Friend{&f1, &f2}}
|
||||
t.Execute(os.Stdout, p)
|
||||
}
|
||||
```
|
||||
Aquí está una lista de las funciones de plantillas por defecto:
|
||||
```
|
||||
var builtins = FuncMap{
|
||||
"and": and,
|
||||
"call": call,
|
||||
"html": HTMLEscaper,
|
||||
"index": index,
|
||||
"js": JSEscaper,
|
||||
"len": length,
|
||||
"not": not,
|
||||
"or": or,
|
||||
"print": fmt.Sprint,
|
||||
"printf": fmt.Sprintf,
|
||||
"println": fmt.Sprintln,
|
||||
"urlquery": URLQueryEscaper,
|
||||
}
|
||||
```
|
||||
|
||||
## Must
|
||||
|
||||
El paquete de plantillas tiene una función llamada `Must` que es para validar plantillas, como verificar las llaves, comentarios y variables. Miremos un ejemplo de `Must`:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tOk := template.New("first")
|
||||
template.Must(tOk.Parse(" algún texto estático /* y un comentario */"))
|
||||
fmt.Println("Analizis de la primera plantilla: OK.")
|
||||
|
||||
template.Must(template.New("second").Parse("algún texto estático {{ .Name }}"))
|
||||
fmt.Println("Análisis de la segunda plantilla OK.")
|
||||
|
||||
fmt.Println("La siguiente plantilla va a fallar.")
|
||||
tErr := template.New("Verificar el error de análisi con Must")
|
||||
template.Must(tErr.Parse(" some static text {{ .Name }"))
|
||||
}
|
||||
```
|
||||
Salida:
|
||||
```
|
||||
Analizis de la primera plantilla: OK.
|
||||
Análisis de la segunda plantilla OK.
|
||||
La siguiente plantilla va a fallar.
|
||||
panic: template: Verificar el error de análisi con Must:1: unexpected "}" in command
|
||||
```
|
||||
## Plantillas anidadas
|
||||
|
||||
Como en la mayoría de aplicaciones web, ciertas partes de las plantillas pueden ser reusada en otras plantillas, como encabezados, pies de páginas de un blog. Podemos declarar `header`, `content` y `footer` como sub plantillas y luego declararlas en go usando la siguiente sintaxis:
|
||||
```
|
||||
{{define "sub-template"}}content{{end}}
|
||||
```
|
||||
La subplantilla es llamada usando la siguiente sintaxis:
|
||||
```
|
||||
{{template "sub-template"}}
|
||||
```
|
||||
Aquí está un ejemplo completo, suponiendo que tenemos los siguientes tres archivos: `header.tmpl`, `content.tmpl` y `footer.tmpl` in la carpeta `templates`, también leeremos la carpeta y almacenaremos los nombres en un arreglo de cadenas, que luego usaremos para analizar los archivos.
|
||||
|
||||
Plantilla principal:
|
||||
```
|
||||
{% raw %}
|
||||
//header.tmpl
|
||||
{{define "header"}}
|
||||
<html>
|
||||
<head>
|
||||
<title>Something here</title>
|
||||
</head>
|
||||
<body>
|
||||
{{end}}
|
||||
|
||||
//content.tmpl
|
||||
{{define "content"}}
|
||||
{{template "header"}}
|
||||
<h1>Nested here</h1>
|
||||
<ul>
|
||||
<li>Nested usag</li>
|
||||
<li>Call template</li>
|
||||
</ul>
|
||||
{{template "footer"}}
|
||||
{{end}}
|
||||
|
||||
//footer.tmpl
|
||||
{{define "footer"}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
||||
// Cuando usamos subplantillas, asegúrate de haber analizado cada archivo de subplantillas, de otra manera el compilador no entenderá que sustituir cuando lea {{template "header"}}
|
||||
|
||||
{% endraw %}
|
||||
```
|
||||
Código:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var templates *template.Template
|
||||
|
||||
func main() {
|
||||
var allFiles []string
|
||||
files, err := ioutil.ReadDir("./templates")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
if strings.HasSuffix(filename, ".tmpl") {
|
||||
allFiles = append(allFiles, "./templates/"+filename)
|
||||
}
|
||||
}
|
||||
|
||||
templates, err = template.ParseFiles(allFiles...) #parses all .tmpl files in the 'templates' folder
|
||||
|
||||
s1, _ := templates.LookUp("header.tmpl")
|
||||
s1.ExecuteTemplate(os.Stdout, "header", nil)
|
||||
fmt.Println()
|
||||
s2, _ := templates.LookUp("content.tmpl")
|
||||
s2.ExecuteTemplate(os.Stdout, "content", nil)
|
||||
fmt.Println()
|
||||
s3, _ := templates.LookUp("footer.tmpl")
|
||||
s3.ExecuteTemplate(os.Stdout, "footer", nil)
|
||||
fmt.Println()
|
||||
s3.Execute(os.Stdout, nil)
|
||||
}
|
||||
```
|
||||
Como podemos ver aquí `template.ParseFiles` analiza todos las plantillas analizadas en un caché y cada plantilla definida por `{{define}}` es independiente de la otra. Ellas persisten en algo como un mapa, donde el nombre de la plantilla es la llave y el valor es el cuerpo de la plantilla. Podemos entonces usar `ExecuteTemplate` para ejecutar el subtemplate correspondiente, entonces el encavezado y el pié de página sin independientes y el contenido los tiene a ambos. Nota que si tratamos de ejecutar `s1.Execute` nada saldrá porque no hay ningún subtemplate disponible.
|
||||
|
||||
Cuando quieras usar `define`, tienes que crear un archivo de texto con el mismo nombre de la subplantilla, por ejemplo `_head.tmpl` es una subplantila que se usa al rededor de tuproyecto, entonces crea este archivo en una carpeta para palanillas y úsala la sintaxis normal. El caché de verificación es creado básicamente para que no tengas que leer la plantilla cada vez que sirvas la petición, porque si lo haces, estás gastando un montón de recursos para leer un archivo que no cambia a menos que el código base sea reescrito, lo cual no tiene sentido para hacer en cada petición GET, entonces esta técnica es usada para analizar los archivos y luego usar un `LookUp()` en el caché para ejecutar la plantilla cuando se necesite mostrar la información.
|
||||
|
||||
Las plantillas en un conjunto se pueden conocer unas a otras, pero debes analizar por cada conjunto en específico.
|
||||
|
||||
Algunas veces vas a querer contextualizar las plantillas, por ejemplo, si tienes un `_head.html`, puedes tener un encabezado que que muestre información basada en loque estes cargando, por ejemplo una lista de tareas por hacer, que puede tener categorías como `pendiente`, `completada`, `eliminada` Supón que tienes el siguiente texto:
|
||||
```
|
||||
<title>{{if eq .Navigation "pendiente"}} Tareas
|
||||
{{ else if eq .Navigation "completada"}}Completada
|
||||
{{ else if eq .Navigation "eliminada"}}Eliminada
|
||||
{{ else if eq .Navigation "editar"}} Editar
|
||||
{{end}}
|
||||
</title>
|
||||
```
|
||||
Nota: Las platillas en Go siguen la notación polaca para realizar la comparación, donde tienes el operador primero y luego los valores de comparación. La parte del `else if` es igual.
|
||||
|
||||
Típicamente, usamos un operador `{{range}}` para recorrer las variales de contexto las cuales son pasadas a la plantilla de la siguiente manera:
|
||||
```
|
||||
//present in views package
|
||||
context := db.GetTasks("pending") //true when you want non deleted notes
|
||||
homeTemplate.Execute(w, context)
|
||||
```
|
||||
Tenemos un objeto de contexto de la base de datos como una estructura, la definición está abajo:
|
||||
```
|
||||
// Estructura de tareas usada para la identificación de ellas
|
||||
type Task struct {
|
||||
Id int
|
||||
Title string
|
||||
Content string
|
||||
Created string
|
||||
}
|
||||
// Context es la estructura pasada a las plantillas
|
||||
type Context struct {
|
||||
Tasks []Task
|
||||
Navigation string
|
||||
Search string
|
||||
Message string
|
||||
}
|
||||
|
||||
// Presente en el paquete de bases de Datos
|
||||
var task []types.Task
|
||||
var context types.Context
|
||||
context = types.Context{Tasks: task, Navigation: status}
|
||||
|
||||
// Esta línea está en el paquete de bases de datos, donde el contexto es retornado a la vista
|
||||
```
|
||||
Usamos el arreglo de `Task` y `Navigation` en nuestras plantillas , vimos como usar `Navigation` en nuestras plantillas. Veremos como usar el arreglo e nuestra plantilla.
|
||||
|
||||
Aquí en el ``{{if .Tast}}`` primero verificamos si el campo `Tasks` que pasamos a nuestra plantilla en el contexto está vacío o no. Si no está vacío, entonces colocamos `range` a través del arreglo para llenar el título y el contenido de cada `Task`. El ejemplo de abajo es muy importante en lo que se refiere a recorrer un arreglo en una plantilla, iniciamos usando un operador `Range`, luego le damos un miembro de la estructura como `{{.Name}}`, la estructura de `Task` tiene un `Title` y un `Content` (Nota que Title y Content tienen la primera letra en mayúscula, por lo tanto está exportadas).
|
||||
```
|
||||
{{ range .Tasks }}
|
||||
{{ .Title }}
|
||||
{{ .Content }}
|
||||
{{ end }}
|
||||
```
|
||||
Este bloque de código imprimirá el título y el contenido del arreglo de `Task` Debajo hay un ejemplo completo de `github.com/thewhitetulip/Tasks`. Plantilla home.html:
|
||||
```
|
||||
<div class="timeline">
|
||||
{{ if .Tasks}} {{range .Tasks}}
|
||||
<div class="note">
|
||||
<p class="noteHeading">{{.Title}}</p>
|
||||
<hr>
|
||||
<p class="noteContent">{{.Content}}</p>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
{{end}} {{else}}
|
||||
<div class="note">
|
||||
<p class="noteHeading">No Tasks here</p>
|
||||
<p class="notefooter">
|
||||
Create new task<button class="floating-action-icon-add" > here </button> </p>
|
||||
</div>
|
||||
{{end}}
|
||||
```
|
||||
|
||||
## Resumen
|
||||
|
||||
En esta sección aprendiste como combinar datos dinámicos con plantillas usando técnicas de impresión de información en ciclos, funciones de plantillas y plantillas anidadas. Al aprender sobre las plantillas, podemos concluir la discusión de la V (Vista) de la arquitectura MVC. En los siguientes capítulos cubriremos los aspectos M (Modelo) y C (Controlador) del MVC.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección Anterior: [Expresiones regulares](07.3.md)
|
||||
- Siguiente sección: [Archivos](07.5.md)
|
||||
144
es/07.5.md
Normal file
144
es/07.5.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# 7.5 Archivos
|
||||
|
||||
Los archivos son objetos esenciales en cada computador. No es una sorpresa que las aplicaciones web también hagan un uso de ellos. En esta sección vamos a aprender cómo operar archivos en Go.
|
||||
|
||||
## Directorios
|
||||
|
||||
En Go, la mayoría de las operaciones de archivos están localizadas en el paquete `os`. Aquí hay algunas de las funciones:
|
||||
|
||||
- `func Mkdir(name string, perm FileMode) error`
|
||||
Crea un directorio `name` y los permisos `perm`, ejemplo: 0777.
|
||||
|
||||
- `func MkdirAll(path string, perm FileMode) error`
|
||||
Crea múltiples directorios de acuerdo al `path`, como `astaxie/test1/test2`.
|
||||
|
||||
- `func Remove(name string) error`
|
||||
Elimina el directorio `name`. Retorna un error si no hay directorio o no está vacío.
|
||||
|
||||
- `func RemoveAll(path string) error`
|
||||
Elimina múltiples directorios de acuerdo al `path`. Los directorios no serán eliminados si `path` es una ruta simple.
|
||||
|
||||
Código de ejemplo:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Mkdir("astaxie", 0777)
|
||||
os.MkdirAll("astaxie/test1/test2", 0777)
|
||||
err := os.Remove("astaxie")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
os.RemoveAll("astaxie")
|
||||
}
|
||||
```
|
||||
## Archivos
|
||||
|
||||
### Crear y abrir archivos
|
||||
|
||||
Existen dos funciones para crear archivos:
|
||||
|
||||
- `func Create(name string) (file *File, err Error)`
|
||||
Crea un archivo `name` y retorna un objeto de lectura escritura con los permisos 0666.
|
||||
|
||||
- `func NewFile(fd uintptr, name string) *File`
|
||||
Crea un archivo y retorna un retorna un objeto archivo.
|
||||
|
||||
También hay dos funciones para abrir archivos:
|
||||
|
||||
- `func Open(name string) (file *File, err Error)`
|
||||
Abre un archivo `name` con permisos de solo lectura, llamando a `OpenFile` por debajo.
|
||||
|
||||
- `func OpenFile(name string, flag int, perm uint32) (file *File, err Error)`
|
||||
Abre un archivo `name`. `flag` es el modo de abertura, como solo lectura, lectura-escritura, etc. `perm` son los permisos del archivo.
|
||||
|
||||
### Escribir archivos
|
||||
|
||||
Funciones para escribir archivos:
|
||||
|
||||
- `func (file *File) Write(b []byte) (n int, err Error)`
|
||||
Escribr contenido binario a un archivo.
|
||||
|
||||
- `func (file *File) WriteAt(b []byte, off int64) (n int, err Error)`
|
||||
Escribe un archivo binario en una posición específica del archivo.
|
||||
|
||||
- `func (file *File) WriteString(s string) (ret int, err Error)`
|
||||
Escribe una cadena en un archivo.
|
||||
|
||||
Código de ejemplo:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
userFile := "astaxie.txt"
|
||||
fout, err := os.Create(userFile)
|
||||
if err != nil {
|
||||
fmt.Println(userFile, err)
|
||||
return
|
||||
}
|
||||
defer fout.Close()
|
||||
for i := 0; i < 10; i++ {
|
||||
fout.WriteString("Just a test!\r\n")
|
||||
fout.Write([]byte("Just a test!\r\n"))
|
||||
}
|
||||
}
|
||||
```
|
||||
### Read files
|
||||
|
||||
Funciones para leer archivos:
|
||||
|
||||
- `func (file *File) Read(b []byte) (n int, err Error)`
|
||||
Lee la información a `b`
|
||||
|
||||
- `func (file *File) ReadAt(b []byte, off int64) (n int, err Error)`
|
||||
Lee l ainformación de `off` a `b`
|
||||
|
||||
Código de ejemplo:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
userFile := "asatxie.txt"
|
||||
fl, err := os.Open(userFile)
|
||||
if err != nil {
|
||||
fmt.Println(userFile, err)
|
||||
return
|
||||
}
|
||||
defer fl.Close()
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, _ := fl.Read(buf)
|
||||
if 0 == n {
|
||||
break
|
||||
}
|
||||
os.Stdout.Write(buf[:n])
|
||||
}
|
||||
}
|
||||
```
|
||||
### Eliminar archivos
|
||||
|
||||
Go usa la misma función para eliminar archivos y directorios:
|
||||
|
||||
- `func Remove(name string) Error`
|
||||
Elimina un archivo o directorio `name` (***si termina en `/` significa que es un directorio***)
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección previa: [Plantillas](07.4.md)
|
||||
- Siguiente sección: [Cadenas](07.6.md)
|
||||
158
es/07.6.md
Normal file
158
es/07.6.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# 7.6 Cadenas
|
||||
|
||||
En la web, casi todo lo que vemos (incluidos entradas de usuario, accesos a bases de datos, etc), es representado como cadenas. Las cadenas son una parte importante del desarrollo web. En muchos casos también necesitamos separarlas, unirlas, convertirlas o de otra manera, manipular las cadenas. En esta sección vamos a introducir los paquetes `strings` y `strconv` de la librería estándar de Go.
|
||||
|
||||
## strings
|
||||
|
||||
Las siguientes funciones son del paquete `strings`. Veamos la documentación oficial para mas detalles:
|
||||
|
||||
- `func Contains(s, substr string) bool`
|
||||
Verifica si `s` contiene una subcadena `substr`, retorna un valor booleano.
|
||||
```
|
||||
fmt.Println(strings.Contains("seafood", "foo"))
|
||||
fmt.Println(strings.Contains("seafood", "bar"))
|
||||
fmt.Println(strings.Contains("seafood", ""))
|
||||
fmt.Println(strings.Contains("", ""))
|
||||
//Salida:
|
||||
//true
|
||||
//false
|
||||
//true
|
||||
//true
|
||||
```
|
||||
- `func Join(a []string, sep string) string`
|
||||
Combina cadenas de un segmento con un separador `sep`.
|
||||
```
|
||||
s := []string{"foo", "bar", "baz"}
|
||||
fmt.Println(strings.Join(s, ", "))
|
||||
//Salida:
|
||||
//foo, bar, baz
|
||||
```
|
||||
|
||||
- `func Index(s, sep string) int `
|
||||
Encuentra el índice de `sep` en la cadena `s`, retorna -1 si no es encontrado.
|
||||
```
|
||||
fmt.Println(strings.Index("chicken", "ken"))
|
||||
fmt.Println(strings.Index("chicken", "dmr"))
|
||||
|
||||
//Salida:
|
||||
//4
|
||||
//-1
|
||||
```
|
||||
- `func Repeat(s string, count int) string`
|
||||
Repite `s` `count` veces.
|
||||
```
|
||||
fmt.Println("ba" + strings.Repeat("na", 2))
|
||||
//Salida: banana
|
||||
```
|
||||
|
||||
- f`unc Replace(s, old, new string, n int) string`
|
||||
Reemplaza la cadena `old` con la cadena `new` en `s`. `n` es el número de reemplazos. Si n es menos que 0, se reemplaza en todos los casos.
|
||||
```
|
||||
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
|
||||
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
|
||||
//Output:oinky oinky oink
|
||||
//moo moo moo
|
||||
```
|
||||
- `func Split(s, sep string) []string`
|
||||
Separa una cadena `s` con el separador `sep` en un segmento.
|
||||
```
|
||||
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
|
||||
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
|
||||
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
|
||||
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
|
||||
//Output:["a" "b" "c"]
|
||||
//["" "man " "plan " "canal panama"]
|
||||
//[" " "x" "y" "z" " "]
|
||||
//[""]
|
||||
```
|
||||
- `func Trim(s string, cutset string) string`
|
||||
Elimina `cutset` de `s` si está al final o al comienzo.
|
||||
```
|
||||
fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! "))
|
||||
Output:["Achtung"]
|
||||
```
|
||||
- `func Fields(s string) []string`
|
||||
Elimina los espacios de una cadena y la separa en un segmento.
|
||||
```
|
||||
fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz "))
|
||||
//Output:Fields are: ["foo" "bar" "baz"]
|
||||
```
|
||||
|
||||
## strconv
|
||||
|
||||
Las siguientes funciones son del paquete `strconv`. Como es usual, por favor vea la documentación oficial para mas detalles:
|
||||
|
||||
- Concatenar series, convertir información a cadenas y concatenar a un segmento de bytes.
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
str := make([]byte, 0, 100)
|
||||
str = strconv.AppendInt(str, 4567, 10)
|
||||
str = strconv.AppendBool(str, false)
|
||||
str = strconv.AppendQuote(str, "abcdefg")
|
||||
str = strconv.AppendQuoteRune(str, '单')
|
||||
fmt.Println(string(str))
|
||||
}
|
||||
```
|
||||
- Formatear series, convertir tipos de datos a cadenas.
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := strconv.FormatBool(false)
|
||||
b := strconv.FormatFloat(123.23, 'g', 12, 64)
|
||||
c := strconv.FormatInt(1234, 10)
|
||||
d := strconv.FormatUint(12345, 10)
|
||||
e := strconv.Itoa(1023)
|
||||
fmt.Println(a, b, c, d, e)
|
||||
}
|
||||
```
|
||||
- Analizar series, convertir cadenas a otros tipos.
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a, err := strconv.ParseBool("false")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
b, err := strconv.ParseFloat("123.23", 64)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
c, err := strconv.ParseInt("1234", 10, 64)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
d, err := strconv.ParseUint("12345", 10, 64)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
e, err := strconv.Itoa("1023")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(a, b, c, d, e)
|
||||
}
|
||||
```
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección previa: [Archivos](07.5.md)
|
||||
- Siguiente sección: [Resumen](07.7.md)
|
||||
9
es/07.7.md
Normal file
9
es/07.7.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 7.7 Resumen
|
||||
|
||||
En este capítulo, introducimos algunas herraminetas para el procesamiento de texto, como XML, JSON, Expresiones Regulares, y también hablamos de plantillas. XML y JSON son herramientas para transferencia de datos, puedes representar casi cualquier tipo de información usando estos dos formatos. Las expresiones regulares son una herramienta poderosa para buscar, reemplazar y cortar contenido de texto. Con las plantillas, fácilmente puedes combinar información dinámica con archivos estáticos. Estas herramientas son útiles para el desarrollo de aplicaciones web. Espero que ahora tengas una mejor idea de como procesar y mostrar contenido usando Go.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección previa: [Cadenas](07.6.md)
|
||||
- Siguiente capítulo: [Servicios web](08.0.md)
|
||||
Reference in New Issue
Block a user