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

Tutorial Go para Web com Redis

Go para Web usando Redis

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. 😉

 

Aula 14 – Golang para Web – Variáveis de caminho

About The Author
-

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>