Aula 14 – Golang para Web – Variáveis de caminho – Path variables
Voltar para página principal do blog
Todas as aulas desse curso
Aula 13 Aula 15
Se gostarem do conteúdo dêem um joinha 👍 na página do Código Fluente no
Facebook
Link do código fluente no Pinterest
Meus links de afiliados:
Hostinger
Digital Ocean
One.com
Código da aula: Github
Melhore seu NETWORKING
Participe de comunidades de desenvolvedores:
Fiquem a vontade para me adicionar ao linkedin.
E também para me seguir no GITHUB.
Ah, se puder, clica na estrela nos meus repositórios pra dá uma força ao meu perfil no GITHUB.
Aula 14 – Golang para Web – Variáveis de caminho – Path variables
Agora iremos usar o pacote gorilla MUX para adicionar variáveis de caminho (path variables) ao nosso aplicativo.
Basicamente o que isso significa é que seremos capazes de criar endpoints que permitam que conteúdos de variáveis possam ser passados para uma URL.
Para isso, precisamos abrir nosso arquivo de rotas e criar um novo manipulador, isto é, um handle, que vai funcionar com esses argumentos, pegando o nome do usuário pela url.
Então, a gente vai poder pegar todos os posts de todos os usuários, ou todos os posts de um usuário específico.
Veja que o manipulador foi posto na parte inferior porque, o gorilla Mux lida com as rotas na ordem em que são definidos.
routes/routes.go
package routes
import (
"net/http"
"github.com/gorilla/mux"
"../middleware"
"../models"
"../sessions"
"../utils"
)
func NewRouter() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/", middleware.AuthRequired(indexGetHandler)).Methods("GET")
r.HandleFunc("/", middleware.AuthRequired(indexPostHandler)).Methods("POST")
r.HandleFunc("/login", loginGetHandler).Methods("GET")
r.HandleFunc("/login", loginPostHandler).Methods("POST")
r.HandleFunc("/register", registerGetHandler).Methods("GET")
r.HandleFunc("/register", registerPostHandler).Methods("POST")
fs := http.FileServer(http.Dir("./static/"))
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fs))
r.HandleFunc("/{userName}",
middleware.AuthRequired(userGetHandler)).Methods("GET")
return r
}
func indexGetHandler(w http.ResponseWriter, r *http.Request) {
updates, err := models.GetAllUpdates()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
utils.ExecuteTemplate(w, "index.html", struct {
Title string
Updates []*models.Update
} {
Title: "All updates",
Updates: updates,
})
}
func indexPostHandler(w http.ResponseWriter, r *http.Request) {
session, _ := sessions.Store.Get(r, "session")
untypedUserId := session.Values["user_id"]
userId, ok := untypedUserId.(int64)
if !ok {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
r.ParseForm()
body := r.PostForm.Get("update")
err := models.PostUpdate(userId, body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
http.Redirect(w, r, "/", 302)
}
func userGetHandler(w http.ResponseWriter, r *http.Request) {
//o mux irá analisar as variáveis de caminho de nosso objeto de
//solicitação r, e vai colocá-los nesta variável vars
vars := mux.Vars(r)
//pega no objeto vars o valor da chave username que veio na request
username := vars["username"]
user, err := models.GetUserByUsername(username)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
userId, err := user.GetId()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
updates, err := models.GetUpdates(userId)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
utils.ExecuteTemplate(w, "index.html", struct {
Title string
Updates []*models.Update
} {
Title: username,
Updates: updates,
})
}
func loginGetHandler(w http.ResponseWriter, r *http.Request) {
utils.ExecuteTemplate(w, "login.html", nil)
}
func loginPostHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
user, err := models.AuthenticateUser(username, password)
if err != nil {
switch err {
case models.ErrUserNotFound:
utils.ExecuteTemplate(w, "login.html", "unknown user")
case models.ErrInvalidLogin:
utils.ExecuteTemplate(w, "login.html", "invalid login")
default:
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
}
return
}
userId, err := user.GetId()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
session, _ := sessions.Store.Get(r, "session")
session.Values["user_id"] = userId
session.Save(r, w)
http.Redirect(w, r, "/", 302)
}
func registerGetHandler(w http.ResponseWriter, r *http.Request) {
utils.ExecuteTemplate(w, "register.html", nil)
}
func registerPostHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
err := models.RegisterUser(username, password)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
http.Redirect(w, r, "/login", 302)
}
Então temos o nome de usuário que precisa obter todas as atualizações para esse usuário específico e isso é um problema neste ponto em que estamos, porque todas as nossas atualizações são mantidas apenas neste objeto específico, update.
Todos os updates estão sendo inseridos no objeto update e não há como separar o usuário.
A gente poderia iterar em cada atualização (update) e verificar o ID do usuário e tentar pegar o usuário, mas tem uma forma mais eficiente de fazer isso.
Solução
O que faremos é acrescentar mais uma chave a updates, além do id da própria publicação, ele terá também um id do usuário que publicou.
Como é feito isso?
Com essa linha:
pipe.LPush(fmt.Sprintf(“user:%d:updates”, userId), id)
A chave será mais ou menos assim:
3) “user:2:updates”
Onde a parte em laranja é o id do usuário e a parte verde o id daquele objeto update.
Já que usamos o PIPE, vamos a mais uma explicação sobre como o modo PIPE funciona e porque usá-lo.
A mágica necessária dentro do modo pipe do redis-cli é ser tão rápido quanto o netcat e ainda ser capaz de entender ao mesmo tempo, quando a última resposta foi enviada pelo servidor.
Isso é obtido da seguinte maneira:
- O redis-cli –pipe tenta enviar dados o mais rápido possível para o servidor.
- Ao mesmo tempo, ele lê os dados quando disponíveis, tentando analisá-los.
- Assim que não houver mais dados para ler no stdin, ele envia um comando ECHO especial com uma string aleatória de 20 bytes: daí, temos certeza de que este é o último comando enviado e temos certeza também de que podemos comparar a resposta verificando se recebermos os mesmos 20 bytes como uma resposta em massa.
- Depois que esse comando final especial é enviado, o código que recebe as respostas começa a corresponder às respostas com esses 20 bytes. Quando a resposta correspondente é alcançada, ele pode sair com sucesso.
Usando esse truque, não precisamos analisar o protocolo que enviamos ao servidor para entender quantos comandos estamos enviando, mas apenas as respostas.
No entanto, ao analisar as respostas, pegamos um contador de todas as respostas analisadas para que, no final, possamos dizer ao usuário a quantidade de comandos transferidos para o servidor pela sessão de inserção em massa.
Voltando ao nosso app, vamos colocar mais uma chave no update.go então.
/models/update.go
package models
import (
"fmt"
)
type Update struct {
key string
}
func NewUpdate(userId int64, body string) (*Update, error) {
id, err := client.Incr("update:next-id").Result()
if err != nil {
return nil, err
}
key := fmt.Sprintf("update:%d", id)
pipe := client.Pipeline()
pipe.HSet(key, "id", id)
pipe.HSet(key, "user_id", userId)
pipe.HSet(key, "body", body)
pipe.LPush("updates", id)
pipe.LPush(fmt.Sprintf("user:%d:updates", userId), id)
_, err = pipe.Exec()
if err != nil {
return nil, err
}
return &Update{key}, nil
}
func (update *Update) GetBody() (string, error) {
return client.HGet(update.key, "body").Result()
}
func (update *Update) GetUser() (*User, error) {
userId, err := client.HGet(update.key, "user_id").Int64()
if err != nil {
return nil, err
}
return GetUserById(userId)
}
func GetAllUpdates() ([]*Update, error) {
updateIds, err := client.LRange("updates", 0, 10).Result()
if err != nil {
return nil, err
}
updates := make([]*Update, len(updateIds))
for i, id := range updateIds {
key := "update:" + id
updates[i] = &Update{key}
}
return updates, nil
}
func GetUpdates(userId int64) ([]*Update, error) {
key := fmt.Sprintf("user:%d:updates", userId)
updateIds, err := client.LRange(key, 0, 10).Result()
if err != nil {
return nil, err
}
updates := make([]*Update, len(updateIds))
for i, id := range updateIds {
key := "update:" + id
updates[i] = &Update{key}
}
return updates, nil
}
func PostUpdate(userId int64, body string) error {
_, err := NewUpdate(userId, body)
return err
}
Vamos alterar o index.html também para ficar da seguinte forma:
web_app/templates/index.html
<html>
<head>
<title>{{ .Title }}</title>
<link rel="stylesheet" type="text/css" href="/static/index.css">
</head>
<body>
<h1>{{ .Title }}</h1>
<form method="POST">
<textarea name="update"></textarea>
<div>
<button type="submit">Post Update</button>
</div>
</form>
{{ range .Updates }}
<div>
<div>
<strong>{{ .GetUser.GetUsername }} wrote:</strong>
</div>
<div>{{ .GetBody }}</div>
</div>
{{ end }}
</body>
</html>
Vamos testar!
Com todas as mudanças feitas, você pode testar para ver se tudo continua funcionando.
Ligue o redis-server:
redis-server
Ligue o servidor:
go run main.go
Acesse:
localhost:8000
Veja que não aparece os posts anteriores, porque quando a gente os criou, não tínhamos a chave que criamos no código dessa aula.
Por agora é só, nos vemos próxima. 😉
Código da aula: Github
Voltar para página principal do blog
Todas as aulas desse curso
Aula 13 Aula 15
Se gostarem do conteúdo dêem um joinha 👍 na página do Código Fluente no
Facebook
Link do código fluente no Pinterest
Novamente deixo meus link de afiliados:
Hostinger
Digital Ocean
One.com
Obrigado, até a próxima e bons estudos. 😉