Skip to content

Commit c54ffbb

Browse files
author
ma0
committed
finished full translation to chapter 6
1 parent 8bc6fbb commit c54ffbb

File tree

4 files changed

+462
-0
lines changed

4 files changed

+462
-0
lines changed

Diff for: es/06.2.md

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# 6.2 ¿Cómo usar sesiones en Go?
2+
3+
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.
4+
5+
## Creando sesiones
6+
7+
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:
8+
9+
- Crear un identificador de sesión único.
10+
- 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.
11+
- Enviar el identificador de sesión único al cliente.
12+
13+
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.
14+
15+
- 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.
16+
- 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.
17+
18+
## Usando Go para manejar sesiones
19+
20+
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.
21+
22+
### Diseño del manejo de sesión
23+
24+
Aquí está una lista de algunas de las consideraciones claves para el diseño del manejo de sesión
25+
26+
- Manejador de sesiones Global.
27+
- Mantener el identificador de sesión único.
28+
- Tener una sesión por cada usuario.
29+
- Almacenamiento de la sesión en memoria, archivos o base de datos.
30+
- Manejar las sesiones expiradas.
31+
32+
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.
33+
34+
### Manejador de sesión
35+
36+
Definir un manejador de sesiones global:
37+
```
38+
type Manager struct {
39+
cookieName string //private cookiename
40+
lock sync.Mutex // protects session
41+
provider Provider
42+
maxlifetime int64
43+
}
44+
45+
func NewManager(provideName, cookieName string, maxlifetime int64) (*Manager, error) {
46+
provider, ok := provides[provideName]
47+
if !ok {
48+
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
49+
}
50+
return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil
51+
}
52+
```
53+
Crear un manejador de sesiones global en la función `main()`:
54+
```
55+
var globalSessions *session.Manager
56+
// Then, initialize the session manager
57+
func init() {
58+
globalSessions = NewManager("memory","gosessionid",3600)
59+
}
60+
```
61+
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:
62+
```
63+
type Provider interface {
64+
SessionInit(sid string) (Session, error)
65+
SessionRead(sid string) (Session, error)
66+
SessionDestroy(sid string) error
67+
SessionGC(maxLifeTime int64)
68+
}
69+
```
70+
- `SessionInit` implementa la inicialización de una sesión y retorna una nueva sesión si es exitoso.
71+
- `SessionRead` retorna una sesión representada con el identificador de sesión sid. Crea una nueva sesión y la retorna si no existe.
72+
- `SessionDestroy` dado un sid, elimina la sesión correspondiente.
73+
- `SessionGC` elimina las variables de sesión basado en el criterio `maxLifeTime`.
74+
75+
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.
76+
```
77+
type Session interface {
78+
Set(key, value interface{}) error //set session value
79+
Get(key interface{}) interface{} //get session value
80+
Delete(key interface{}) error //delete session value
81+
SessionID() string //back current sessionID
82+
}
83+
```
84+
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.
85+
```
86+
var provides = make(map[string]Provider)
87+
88+
// Register makes a session provider available by the provided name.
89+
// If a Register is called twice with the same name or if the driver is nil,
90+
// it panics.
91+
func Register(name string, provider Provider) {
92+
if provider == nil {
93+
panic("session: Register provider is nil")
94+
}
95+
if _, dup := provides[name]; dup {
96+
panic("session: Register called twice for provider " + name)
97+
}
98+
provides[name] = provider
99+
}
100+
```
101+
### Identificacores de sesión únicos.
102+
103+
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:
104+
```
105+
func (manager *Manager) sessionId() string {
106+
b := make([]byte, 32)
107+
if _, err := io.ReadFull(rand.Reader, b); err != nil {
108+
return ""
109+
}
110+
return base64.URLEncoding.EncodeToString(b)
111+
}
112+
```
113+
### Crear una sesión
114+
115+
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.
116+
```
117+
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
118+
manager.lock.Lock()
119+
defer manager.lock.Unlock()
120+
cookie, err := r.Cookie(manager.cookieName)
121+
if err != nil || cookie.Value == "" {
122+
sid := manager.sessionId()
123+
session, _ = manager.provider.SessionInit(sid)
124+
cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxlifetime)}
125+
http.SetCookie(w, &cookie)
126+
} else {
127+
sid, _ := url.QueryUnescape(cookie.Value)
128+
session, _ = manager.provider.SessionRead(sid)
129+
}
130+
return
131+
}
132+
```
133+
Aquí hay un ejemplo que usa la sesión de usuario para el ingreso.
134+
```
135+
func login(w http.ResponseWriter, r *http.Request) {
136+
sess := globalSessions.SessionStart(w, r)
137+
r.ParseForm()
138+
if r.Method == "GET" {
139+
t, _ := template.ParseFiles("login.gtpl")
140+
w.Header().Set("Content-Type", "text/html")
141+
t.Execute(w, sess.Get("username"))
142+
} else {
143+
sess.Set("username", r.Form["username"])
144+
http.Redirect(w, r, "/", 302)
145+
}
146+
}
147+
```
148+
### Operaciones con valores: definir, obtener y eliminar.
149+
150+
La función `SessionStart` retorna una variable que implementa la interfaz de sesión. ¿Cómo la usamos?
151+
152+
Viste un `session.Get("uid")` en el ejemplo anterior para una operación básica. Ahora examinémolo en mejor detalle.
153+
```
154+
func count(w http.ResponseWriter, r *http.Request) {
155+
sess := globalSessions.SessionStart(w, r)
156+
createtime := sess.Get("createtime")
157+
if createtime == nil {
158+
sess.Set("createtime", time.Now().Unix())
159+
} else if (createtime.(int64) + 360) < (time.Now().Unix()) {
160+
globalSessions.SessionDestroy(w, r)
161+
sess = globalSessions.SessionStart(w, r)
162+
}
163+
ct := sess.Get("countnum")
164+
if ct == nil {
165+
sess.Set("countnum", 1)
166+
} else {
167+
sess.Set("countnum", (ct.(int) + 1))
168+
}
169+
t, _ := template.ParseFiles("count.gtpl")
170+
w.Header().Set("Content-Type", "text/html")
171+
t.Execute(w, sess.Get("countnum"))
172+
}
173+
```
174+
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.
175+
176+
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.
177+
178+
### Definir sesiones.
179+
180+
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.
181+
```
182+
//Destroy sessionid
183+
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){
184+
cookie, err := r.Cookie(manager.cookieName)
185+
if err != nil || cookie.Value == "" {
186+
return
187+
} else {
188+
manager.lock.Lock()
189+
defer manager.lock.Unlock()
190+
manager.provider.SessionDestroy(cookie.Value)
191+
expiration := time.Now()
192+
cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1}
193+
http.SetCookie(w, &cookie)
194+
}
195+
}
196+
```
197+
### Eliminar sesiones
198+
199+
Vamos a ver como dejar al manejador de sesiones eliminar una sesión. Necesitamos iniciar el recolector de baseura en la función `main()`:
200+
```
201+
func init() {
202+
go globalSessions.GC()
203+
}
204+
205+
func (manager *Manager) GC() {
206+
manager.lock.Lock()
207+
defer manager.lock.Unlock()
208+
manager.provider.SessionGC(manager.maxlifetime)
209+
time.AfterFunc(time.Duration(manager.maxlifetime), func() { manager.GC() })
210+
}
211+
```
212+
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.
213+
214+
## Resumen
215+
216+
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.
217+
218+
## Enlaces
219+
220+
- [Índice](preface.md)
221+
- Sección anterior: [Sesiones y cookies](06.1.md)
222+
- Siguiente sección: [Almacenamiento de sesiones](06.3.md)

Diff for: es/06.3.md

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# 6.3 Almacenamiento de sesiones
2+
3+
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.
4+
```
5+
package memory
6+
7+
import (
8+
"container/list"
9+
"github.com/astaxie/session"
10+
"sync"
11+
"time"
12+
)
13+
14+
var pder = &Provider{list: list.New()}
15+
16+
type SessionStore struct {
17+
sid string // identificador único de sesión
18+
timeAccessed time.Time // último tiempo de acceso
19+
value map[interface{}]interface{} // valor de la sesión accedida
20+
}
21+
22+
func (st *SessionStore) Set(key, value interface{}) error {
23+
st.value[key] = value
24+
pder.SessionUpdate(st.sid)
25+
return nil
26+
}
27+
28+
func (st *SessionStore) Get(key interface{}) interface{} {
29+
pder.SessionUpdate(st.sid)
30+
if v, ok := st.value[key]; ok {
31+
return v
32+
} else {
33+
return nil
34+
}
35+
return nil
36+
}
37+
38+
func (st *SessionStore) Delete(key interface{}) error {
39+
delete(st.value, key)
40+
pder.SessionUpdate(st.sid)
41+
return nil
42+
}
43+
44+
func (st *SessionStore) SessionID() string {
45+
return st.sid
46+
}
47+
48+
type Provider struct {
49+
lock sync.Mutex // bloqueo
50+
sessions map[string]*list.Element // guardar en memoria
51+
list *list.List // recikector de basura
52+
}
53+
54+
func (pder *Provider) SessionInit(sid string) (session.Session, error) {
55+
pder.lock.Lock()
56+
defer pder.lock.Unlock()
57+
v := make(map[interface{}]interface{}, 0)
58+
newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v}
59+
element := pder.list.PushBack(newsess)
60+
pder.sessions[sid] = element
61+
return newsess, nil
62+
}
63+
64+
func (pder *Provider) SessionRead(sid string) (session.Session, error) {
65+
if element, ok := pder.sessions[sid]; ok {
66+
return element.Value.(*SessionStore), nil
67+
} else {
68+
sess, err := pder.SessionInit(sid)
69+
return sess, err
70+
}
71+
return nil, nil
72+
}
73+
74+
func (pder *Provider) SessionDestroy(sid string) error {
75+
if element, ok := pder.sessions[sid]; ok {
76+
delete(pder.sessions, sid)
77+
pder.list.Remove(element)
78+
return nil
79+
}
80+
return nil
81+
}
82+
83+
func (pder *Provider) SessionGC(maxlifetime int64) {
84+
pder.lock.Lock()
85+
defer pder.lock.Unlock()
86+
87+
for {
88+
element := pder.list.Back()
89+
if element == nil {
90+
break
91+
}
92+
if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() {
93+
pder.list.Remove(element)
94+
delete(pder.sessions, element.Value.(*SessionStore).sid)
95+
} else {
96+
break
97+
}
98+
}
99+
}
100+
101+
func (pder *Provider) SessionUpdate(sid string) error {
102+
pder.lock.Lock()
103+
defer pder.lock.Unlock()
104+
if element, ok := pder.sessions[sid]; ok {
105+
element.Value.(*SessionStore).timeAccessed = time.Now()
106+
pder.list.MoveToFront(element)
107+
return nil
108+
}
109+
return nil
110+
}
111+
112+
func init() {
113+
pder.sessions = make(map[string]*list.Element, 0)
114+
session.Register("memory", pder)
115+
}
116+
```
117+
118+
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?
119+
```
120+
import (
121+
"github.com/astaxie/session"
122+
_ "github.com/astaxie/session/providers/memory"
123+
)
124+
```
125+
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.
126+
```
127+
var globalSessions *session.Manager
128+
129+
// initialize in init() function
130+
func init() {
131+
globalSessions, _ = session.NewManager("memory", "gosessionid", 3600)
132+
go globalSessions.GC()
133+
}
134+
```
135+
## Enlaces
136+
137+
- [Índice](preface.md)
138+
- Sección previa: [Cómo usar sesiones en Go](06.2.md)
139+
- Siguiente sección: [Prevenir el hijacking de sesión](06.4.md)

0 commit comments

Comments
 (0)