Expectativas

Nessa aula vamos criar nosso servidor HTTP, e aprender a renderizar HTML utilizando valores providos pelo nosso servidor. E no conteúdo adicional vamos ver como atualizar o servidor sempre que salvarmos.


Ponto de partida desse post:

Executando servidor

Para criar nosso servidor, nós vamos utilizar a Design Pattern Command, uma design pattern comportamental que vai ser muito útil para quando nós quisermos rodar o servidor em Go routines.

Nessa primeira versão nós vamos usar a Lib principal de HTTP no Go, a net/http. Comece rodando os seguintes comandos:

mkdir server
touch server/http.go

Abra o arquivo http.go e escreva o seguinte:

package server

import (
  "fmt"
  "net/http"

  io "github.com/joaomarcuslf/qr-generator/services/io"
)

var cli *io.CLI = io.NewCLI()

type Server struct {
  Port string
}

func NewServer(port string) *Server {
  return &Server{
    Port: port,
  }
}

func Index(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "OK")
}

func (a *Server) Run() {
  http.HandleFunc("/", Index)

  cli.Write("Server running on port: " + a.Port)

  http.ListenAndServe(":"+a.Port, nil)
}

Perceba que a função Index utiliza aquele mesmo padrão de writer recebendo um io.Writer. Se você quiser, você pode criar uma interface dentro do io que envolva o Fprintf, no momento será desnecessário pois logo vamos mudar essa função.

Agora abra o main.go, e vamos atualizar o arquivo para usarmos o nosso servidor:

package main

import (
  server "github.com/joaomarcuslf/qr-generator/server"
)

func main() {
  server := server.NewServer("8000")
  server.Run()
}

Basta executar seu código com go run main.go, e abrir seu navegador em http://localhost:8000/ para ver os resultados do seu código.

Para melhorar a organização, vamos separar nossos handlers em arquivos.

Não sabe o que é handler? Se convenciona chamar de handler uma função de rota, que é responsável por tratar o request e responder.

mkdir handlers
mkdir handlers/web
touch handlers/web/html.go

Abra o arquivo html.go e escreva o seguinte:

package handlers

import (
  "fmt"
  "net/http"
)

func Home(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "OK")
}

Em seguida vamos atualizar o http.go para usar o nosso handler:

import (
  /* ... */
  web "github.com/joaomarcuslf/qr-generator/handlers/web"
  /* ... */
)

/* ... */

func (a *Server) Run() {
  http.HandleFunc("/", web.Home)

  /* ... */
}

Você pode apagar a antiga função Index, e agora é só restartar nosso servidor, e nada deve ter mudado. Agora vamos começar a fazer nossos HTMLs, como essa aula não tem foco em HTML, eu vou só passar por cima, e explicar as partes que o Go entra.

mkdir static
touch static/main.css

Abra o main.css e preencha com o seguinte CSS:

@import "https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css";

.is-purple, .is-purple.is-dark {
  background-color: #5f163a;
  color: #f7e8f0;
}

.is-purple.is-light {
  background-color: #f7e8f0;
  color: #551637;
}

button.is-purple {
  background-color: #5f163a;
  color: #f7e8f0;
  transition: background-color 0.2s ease 0s;
  transition: color 0.2s ease 0s;
}

button.is-purple:hover {
  background-color: #d5a6bd;
  color: #5f163a;
}

Em seguida, vamos criar os templates.

mkdir templates
touch templates/index.html

Abra o index.html e preencha com o seguinte HTML:

<html>
<head>
  <title>{{.Title}}</title>
  <meta charset="utf-8" />
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=6.0">

  <meta name="description" content="{{.Description}}">
  <meta property="og:description_safe" content="{{.Description}}" />

  <link rel="stylesheet" type="text/css" href="/static/main.css" />
</head>

<body>
  <section class="hero is-purple is-light is-fullheight">
    <div class="hero-body">
      <div class="container">
        <div class="columns is-centered">
          <div class="column is-8-tablet is-7-desktop is-7-widescreen">
            <form action="/generator" method=post class="box">
              <div class="field">
                <label for="" class="label has-text-left">Please enter the string you want to QRCode.</label>

                <div class="control">
                  <input
                    {{if .Error}}
                      class="input is-danger"
                    {{else}}
                      class="input"
                    {{end}}
                    placeholder="e.g. www.google.com"
                    name="dataString" />
                </div>

                {{if .Error}}
                  <p class="help is-danger">{{.Error}}</p>
                {{end}}
              </div>

              <div class="field has-text-right">
                <button class="button is-purple is-rounded is-medium is-fullwidth">
                  Submit
                </button>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </section>
</body>
</html>

Quero chamar atenção para alguns trechos do HTML:

<title>{{.Title}}</title>

<meta name="description" content="{{.Description}}">
<meta property="og:description_safe" content="{{.Description}}" />

{{if .Error}}
  class="input is-danger"
{{else}}
  class="input"
{{end}}

{{if .Error}}
  <p class="help is-danger">{{.Error}}</p>
{{end}}

Nesses trechos nós estamos utilizando três variáveis, .Title, .Description e .Error. Essas variáveis são enviadas pelo Go para o template, como se fossem lacunas. Vamos atualizar nosso web/html.go.

package handlers

import (
  "net/http"
  "text/template"
)

type Page struct {
  Title       string
  Description string
  Error       string
}

func Home(w http.ResponseWriter, r *http.Request) {
  p := Page{
    Title:       "QR Code Generator",
    Description: "A page to generate QR",
  }

  t, err := template.ParseFiles("templates/index.html")

  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  t.Execute(w, p)
}

E vamos atualizar nosso http.go, pois agora nós utilizamos pastas estáticas, e precisamos dizer ao nosso servidor para olhar para elas.:

/* ... */

func (a *Server) Run() {
  fs := http.FileServer(http.Dir("./static"))
  http.Handle("/static/", http.StripPrefix("/static/", fs))

  http.HandleFunc("/", web.Home)

  /* ... */
}

Concluindo

Agora é só atualizar seu servidor, e abrir http://localhost:8000/, você vai ver o nosso HTML. Por hora ele não funciona, e é o que vamos fazer na nossa próxima aula. Revise o que fizemos e brinque com o HTML, CSS. Entenda também as variáveis.

Na próxima aula, nós vamos implementar a rota /generator.

Adicionais

Se você estiver cansado de ficar atualizando seu servidor, você pode utilizar o cosmtrek/air, siga o guia de instalação.

Depois rode:

air init

air server --port 8000

Agora sempre que você salvar seu código, o air irá atualizar seu servidor. E se você utilizar VS Code, você pode criar uma forma de rodar o servidor direto no editor.

mkdir .vscode

touch .vscode/launch.json

Abra .vscode/launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
          "command": "air server --port 8000",
          "name": "Live Reload Server",
          "request": "launch",
          "type": "node-terminal"
        },
        {
            "name": "Launch Package",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "${workspaceFolder}"
        }
    ]
}

Agora é só abrir a Tab Run and Debug e clique em Live Reload Server. Prontinho, agora você não precisará rodar tantos comandos para atualizar seu servidor. A outra opção(Launch Package), é para quando precisarmos debugar o servidor sem correr risco de ficar atualizando por qualquer mudança.

Lista de aulas: