Criando Servidores HTTP com GoLang

Criando Servidores HTTP com GoLang

Olá leitor, como estamos? Bem? Espero que sim 😀

Você sabia que é possível colocar suas aplicações de forma online usando a linguagem Golang?

Não? Pois saiba que esse tipo de situação é plenamente possível no universo do Go 🥳

Hoje nós iremos aprender a criar um servidor estático (e também dinâmico) com Golang, de modo que ele consiga servir arquivos do tipo HTML sempre que o usuário acessar o endereço localhost:3000 (ou qualquer outro) em seu navegador.

Em outras palavras, vamos aprender a servir um website com GoLang!

No Golang, nós temos a capacidade de criar servidores HTTP muito rapidamente. Sua biblioteca padrão já vem com poderosas ferramentas para lidar com requisições e respostas, o que torna o processo mais simples do que parece.

Vamos começar entendendo como inicializar um servidor básico, que é o primeiro passo para colocar um site no ar!

Para o nosso exemplo, utilizaremos a função http.HandleFunc, que nos permite associar um caminho específico com uma função que irá processar as requisições.

Com isso, além de aprender a servir arquivos estáticos como HTML, CSS e JavaScript, também abordaremos como trabalhar com rotas dinâmicas, criando servidores interativos e mais robustos.

Agora, sem mais delongas, vamos colocar a mão na massa e aprender passo a passo sobre como criar nosso primeiro servidor em Go! 🙃

Você sabe o que é uma requisição HTTP?

Não sabe? Estranho... ou você caiu aqui de paraquedas, ou pulou alguma de nossas lições 😅

De qualquer forma, eu recomendo que você aprenda a realizar requisições HTTP antes de prosseguirmos com esta lição.

Para aprender mais sobre requisições HTTP com GoLang, consulte este link. 🙂

Criando seu projeto de testes

Dentro da pasta JornadaGoLang, nós iremos criar uma nova pasta chamada de 23-servidores-http, onde dentro dela, vamos criar o nosso arquivo main.go:

package main

func main() {

}

Feito isso, vamos aprender a utilizar a biblioteca net/http 😉

Criando nosso primeiro servidor estático

Um servidor estático é um tipo de servidor que serve arquivos fixos diretamente para os clientes, sem realizar qualquer tipo de processamento ou modificação desses arquivos.

Esses arquivos podem ser imagens, arquivos HTML, CSS, JavaScript, entre outros. A principal característica de um servidor estático é que ele entrega conteúdo tal como ele está, sem gerar conteúdo dinâmico ou fazer alterações no arquivo antes de entregá-lo.

Para criar um servidor desse tipo com GoLang, primeiro vamos criar uma nova pasta dentro de 23-servidores-http chamada de static, e dentro dela, crie também uma pasta chamada public.

A estrutura de pasta ficará assim:

- 23-servidores-http
  - static
    - public
  - main.go

Preferi organizar dessa forma, pois em tópicos futuros você aprenderá a criar diferentes tipos de servidores além dos estáticos 🙂

Perfeito, já dentro da pasta static, vamos criar um novo arquivo em GoLang que vai representar o nosso servidor, no meu caso, criei um arquivo com o nome de servidor-estatico.go:

package main

import (
	"log"
	"net/http"
)

func main(){
	fs := http.FileServer(http.Dir("public"))
	http.Handle("/", fs)

	log.Println("Executando...")
	log.Fatal(http.ListenAndServe(":3000", nil))
}

Já dentro da pasta public, vamos criar um novo arquivo chamado de index.html com o seguinte conteúdo:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Meu Servidor em GoLang!</title>
</head>
<body>
    <h1>Aprenda GoLang com o Portal da Micilini!</h1>
</body>
</html>

O comando acima refere-se a um arquivo HTML que contém as marcações da estrutura da nossa página da web, e que será carregada de forma automática pela biblioteca net/http. E falando nela, vamos ás explicações 😁

fs := http.FileServer(http.Dir("public"))
http.Handle("/", fs)

http.FileServer(http.Dir("public")): essa linha cria um handler (um tipo de função que processa requisições) que serve arquivos estáticos.

A função http.Dir("public") cria um filesystem que aponta para o diretório public, onde você colocará seus arquivos estáticos, e por fim, o http.FileServer transforma esse diretório em um handler HTTP que serve os arquivos presentes nele.

É importante ressaltar que a biblioteca http já faz tudo sozinha, e como qualquer handle, ele sempre vai buscar pelos arquivos index.html, o que consequentemente abre a estrutura inicial da nossa página.

http.Handle("/", fs): neste ponto o comando handler fs é registrado para a URL raiz ("/"). Isso significa que, sempre que uma requisição for feita à URL principal (por exemplo, http://localhost:3000/), o servidor usará o handler fs para procurar e servir os arquivos do diretório public.

log.Println("Executando...")

Em seguida, executamos um log para imprimir a mensagem "Executando...", informando que o servidor está em execução e pronto para começar a receber requisições.

log.Fatal(http.ListenAndServe(":3000", nil))

http.ListenAndServe(":3000", nil): aqui nós estamos inicializando o servidor HTTP na porta 3000 (ou seja, ele ficará escutando requisições HTTP na URL http://localhost:3000/). O nil indica que o servidor usará o mux padrão para roteamento de requisições.

log.Fatal(...): registra o erro, caso o servidor não consiga ser iniciado (por exemplo, se a porta já estiver sendo usada), e em seguida encerra o programa.

Se o servidor for iniciado com sucesso, o código continua a execução normalmente, e nunca termina!

Mas por que ele nunca termina?

A razão para isso é que a função http.ListenAndServe bloqueia a execução do programa, ou seja, ela fica aguardando continuamente as requisições HTTP.

Quando você chama essa função, ela inicia o servidor e começa a escutar as requisições na porta 3000.

Enquanto o servidor está rodando, ele fica "ouvindo" e processando requisições indefinidamente.

E sim, todo servidor da web é um programinha que nunca termina, ele precisa estar 24h por 7 dias ligado, pronto para receber e processar requisições.

Então, enquanto o servidor estiver ativo, o programa não vai terminar, porque ele está aguardando eventos (requisições HTTP) para processar.

Isso é esperado e desejado, já que estamos criando um servidor web, e ele precisa continuar funcionando até que algo o faça parar, como um erro grave ou o envio de um sinal de término (como pressionar Ctrl+C no terminal, por exemplo).

Em termos simples, o servidor continua "vivo" e em execução até que você o pare manualmente ou o sistema o encerre de alguma forma.

Perfeito, se você executar o servidor-estatico.go com o comando:

go run servidor-estatico.go

É bem provável que uma janelinha de confirmação (se você estiver usando Windows) apareça:

Basta clicar em permitir, e acessar a seguinte URL no seu navegador: http://localhost:3000.

E pronto, se tudo der certo, você verá um resultado como este:

O que indica que o nosso servidor foi criado com sucesso! 😁

Dúvida: Eu consigo mudar o endereço localhost?

Sim, é possível. O endereço localhost é um alias para o IP 127.0.0.1, que é usado para se referir ao próprio computador. No entanto, você pode mudar o endereço para um IP diferente, se necessário, ou até mesmo usar um nome de domínio.

Se você quiser que o servidor aceite requisições de outros dispositivos na mesma rede, basta substituir o localhost por 0.0.0.0. Isso fará com que o servidor escute em todas as interfaces de rede disponíveis. Por exemplo:

log.Fatal(http.ListenAndServe("0.0.0.0:3000", nil))

Se você tiver um domínio (como meusite.com), pode associar a porta 3000 ao seu servidor, mas precisará configurar o DNS e o servidor web para direcionar o tráfego para o seu servidor Go, mas você só fará isso quando tiver publicando a sua aplicação de forma inteiramente online.

Dúvida: Eu consigo mudar o número da porta?

Sim, você consegue mudar o número da porta facilmente. No código, a porta está configurada como :3000, mas você pode alterar esse número para qualquer outra porta disponível no seu sistema.

Por exemplo, para rodar na porta 8080, você pode usar o seguinte código:

log.Fatal(http.ListenAndServe(":8080", nil))

É importante verificar se a porta que você escolheu já não está em uso por outro processo, caso contrário, o servidor não conseguirá iniciar.

Dúvida: eu consigo criar um sistema de rotas? Fazer com que /sobre abra uma página completamente diferente de /?

Sim, você pode criar um sistema de rotas facilmente. Para isso, você pode usar o http.HandleFunc ou um router mais avançado, como o gorilla/mux ou chi.

Mas aguenta aí, que o nosso próximo tópico será justamente sobre isso 😃

Trabalhando com rotas em um servidor estático

Antes de mais nada, você sabe o que é uma rota de uma aplicação web? 🤓 

Em termos simples, rota é uma URL específica em uma aplicação web que é mapeada para uma ação ou resposta específica.

Por exemplo, quando você acessa a URL dessa jornada: https://micilini.com/conteudos/golang, por de baixo dos panos, o Portal da Micilini verifica qual "rota" está associada a essa URL e executa o código relacionado para responder àquela requisição.

No fundo no fundo, nós fazemos uma espécie de validação do tipo if/else ou switch case para oferecer o conteúdo daquela URL específica.

Tanto é que nosso portal conta com diversas rotas, cada qual abrindo um conteúdo diferente:

As rotas são essenciais para controlar o fluxo de requisições e determinar qual conteúdo será retornado para o usuário. Em muitas aplicações, as rotas estão associadas a diferentes páginas ou funcionalidades da aplicação.

Por exemplo, em uma aplicação de blog:

  • A rota / pode exibir a página inicial com uma lista de posts.
  • A rota /sobre pode exibir uma página com informações sobre o site.
  • A rota /post/{id} pode exibir um post específico, onde {id} é um parâmetro dinâmico que indica qual post será exibido.

Vejamos um pequeno exemplo de como o esquema de rotas pode ser construído com o GoLang:

package main

import (
	"log"
	"net/http"
)

func main() {
	// Rota para a página inicial
	http.Handle("/", http.FileServer(http.Dir("public")))

	// Rota para a página 'sobre'
	http.HandleFunc("/sobre", func(w http.ResponseWriter, r *http.Request) {
		// Aqui você pode criar uma resposta personalizada, como enviar um HTML diferente
		http.ServeFile(w, r, "public/sobre.html")
	})

	log.Println("Executando...")
	log.Fatal(http.ListenAndServe(":3000", nil))
}

Além disso, não se esqueça de criar um novo arquivo HTML chamado sobre.html dentro da pasta public:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sobre!</title>
</head>
<body>
    <h1>Página Sobre!</h1>
</body>
</html>

Se você abrir a URL http://localhost:3000/sobre, verá que o conteúdo mudou:

Além disso, a página principal (http://localhost:3000/) continua funcionando normalmente 😋

No Go, por exemplo, você pode definir essas rotas de maneira simples, como mostramos anteriormente com http.HandleFunc("/sobre", ...), que liga a URL /sobre a uma função específica.

Isso é um exemplo de roteamento básico, mas em sistemas mais complexos, você pode ter rotas dinâmicas, com parâmetros, e até mesmo rotas aninhadas.

Trabalhando com rotas dinâmicas em servidores estáticos

De vez em quando, o nosso sistema vai precisar receber alguns parâmetros em nossas rotas, como um {id}, um {sku} ou qualquer outra informação relevante.

Por baixo dos panos, o servidor precisa capturar essas informações fornecidas na URL para realizar validações internas e garantir que o usuário tenha permissão para acessar aquela URL ou obter as informações solicitadas.

Por exemplo, ao acessar uma URL como /produto/{sku}, o sistema pode verificar se o sku existe no banco de dados antes de retornar a página do produto.

Esses parâmetros dinâmicos são essenciais em muitas aplicações, como sistemas de e-commerce, blogs, ou qualquer plataforma que precise identificar recursos específicos com base na URL.

O nosso portal por exemplo, está cheio dessas URLs dinâmicas que usamos para mostrar nossas jornadas e lições 😆

No Go, o pacote net/http fornece um modo simples de lidar com rotas, mas ele não oferece suporte direto para parâmetros dinâmicos, como vemos em outras linguagens de programação.

No entanto, podemos fazer isso manualmente, utilizando funções como http.HandleFunc e fazendo a manipulação das URLs da seguinte forma:

package main

import (
	"fmt"
	"log"
	"net/http"
	"strings"
)

// Função para tratar a rota dinâmica de um produto
func produtoHandler(w http.ResponseWriter, r *http.Request) {
	// Capturando o parâmetro 'id' da URL
	// Exemplo de URL: /produto/12345
	parts := strings.Split(r.URL.Path, "/")
	if len(parts) < 3 {
		http.Error(w, "ID de produto inválido", http.StatusBadRequest)
		return
	}

	idProduto := parts[2] // Pegando o ID do produto da URL

	// Aqui você pode validar se o idProduto existe no banco de dados, por exemplo
	// Para fins de exemplo, vamos apenas simular a resposta
	fmt.Fprintf(w, "Mostrando o produto com ID: %s", idProduto)
}

func main() {
	// Rota para mostrar informações de um produto específico
	http.HandleFunc("/produto/", produtoHandler)

	log.Println("Servidor rodando na porta 3000...")
	log.Fatal(http.ListenAndServe(":3000", nil))
}

Se você rodar o servidor acima, e tentar acessar a rota: http://localhost:3000/produto/98, receberá a seguinte mensagem no navegador:

Mostrando o produto com ID: 98

Incrível, não? 😊

Dentro da função produtoHandler, usamos strings.Split(r.URL.Path, "/") para dividir a URL e capturar o valor do ID do produto que está na posição correta na URL (neste caso, é a terceira parte da URL).

E se o ID for válido, o servidor retorna uma mensagem simulando a exibição do produto. Caso contrário, ele retorna um erro com a mensagem "ID de produto inválido".

Vejamos um outro exemplo onde temos rotas complexas com múltiplos parâmetros dinâmicos, como é o caso de uma rota que inclui tanto o id do produto quanto seu sku:

package main

import (
	"fmt"
	"log"
	"net/http"
	"strings"
)

// Função para tratar a rota dinâmica de produto e SKU
func produtoHandler(w http.ResponseWriter, r *http.Request) {
	// Capturando os parâmetros 'id' e 'sku' da URL
	// Exemplo de URL: /produto/12345/sku1234
	parts := strings.Split(r.URL.Path, "/")
	if len(parts) < 4 {
		http.Error(w, "Parâmetros inválidos", http.StatusBadRequest)
		return
	}

	idProduto := parts[2] // ID do produto
	sku := parts[3]       // SKU do produto

	// Aqui você pode validar se o idProduto e sku existem no banco de dados, por exemplo
	// Para fins de exemplo, vamos apenas simular a resposta
	fmt.Fprintf(w, "Mostrando o produto com ID: %s e SKU: %s", idProduto, sku)
}

func main() {
	// Rota para mostrar informações de um produto com ID e SKU
	http.HandleFunc("/produto/", produtoHandler)

	log.Println("Servidor rodando na porta 3000...")
	log.Fatal(http.ListenAndServe(":3000", nil))
}

Se você acessar a URL: http://localhost:3000/produto/{id}/{sku}, verá que a sua aplicação identificou tanto o id quanto o sku de forma separada.

A lógica é bem similar ao comando visto anteriormente, a única diferença é que você captura o idProduto na posição 2 e o sku na posição 3.

Embora o Go padrão não tenha suporte a rotas dinâmicas avançadas como alguns frameworks web (como o Express no Node.js ou Flask no Python), é totalmente possível criar suas próprias rotas dinâmicas com algumas manipulações simples das URLs.

Para aplicações mais complexas, no entanto, pode ser interessante considerar o uso de pacotes de roteamento mais robustos, como o gorilla/mux, que oferece suporte a parâmetros dinâmicos e outras funcionalidades avançadas de roteamento.

Dúvida: mas e se uma rota for uma espécie de expressão regular? O GoLang possuí esse suporte?

Neste caso, você deverá fazer o uso da biblioteca regexp, que é capaz de lidar com expressões regulares nativamente no GoLang, observe:

package main

import (
	"fmt"
	"log"
	"net/http"
	"regexp"
)

func produtoHandler(w http.ResponseWriter, r *http.Request) {
	// Usando uma expressão regular para capturar o ID do produto
	// Exemplo de URL: /produto/12345 ou /produto/prod-12345
	re := regexp.MustCompile(`^/produto/([a-zA-Z0-9\-]+)$`)

	// Tentando combinar a URL com a expressão regular
	matches := re.FindStringSubmatch(r.URL.Path)
	if len(matches) < 2 {
		http.Error(w, "Produto não encontrado", http.StatusNotFound)
		return
	}

	// O primeiro item de matches é a string inteira, o segundo é o grupo capturado (ID do produto)
	idProduto := matches[1]

	// Lógica para buscar o produto pelo ID
	fmt.Fprintf(w, "Mostrando o produto com ID: %s", idProduto)
}

func main() {
	http.HandleFunc("/produto/", produtoHandler)

	log.Println("Servidor rodando na porta 3000...")
	log.Fatal(http.ListenAndServe(":3000", nil))
}

No comando acima, nós utilizamos o comando regexp.MustCompile para compilar uma expressão regular.

A expressão regular ^/produto/([a-zA-Z0-9\-]+)$ está buscando a URL que começa com /produto/ e depois captura um grupo que consiste em letras (maiúsculas ou minúsculas), números e hífens.

O que nos permite capturar tanto IDs simples quanto IDs com prefixos como "prod-12345" 😉

Criando nosso primeiro servidor dinâmico

Antes de mais nada, o que vem a ser um servidor dinâmico? Ou melhor dizendo... como funciona a geração de conteúdo dinâmico em GoLang?

Um servidor dinâmico é aquele capaz de gerar e retornar respostas adaptadas a cada requisição — seja exibindo dados de um banco, calculando alguma informação em tempo real ou simplesmente alterando o conteúdo conforme parâmetros recebidos.

Em contraste com um servidor estático (que serve sempre os mesmos arquivos HTML, CSS, JS, imagens etc.), o servidor dinâmico monta o corpo da resposta “por demanda”, de acordo com a lógica implementada em na linguagem específica.

Quando falamos em conteúdos dinâmicos em GoLang, estamos nos baseando em três grandes pilares:

🏛️ Handler Functions: são funções que recebem um objeto http.ResponseWriter (para escrever a resposta) e um *http.Request (que contém todas as informações do cliente: método, URL, cabeçalhos, parâmetros, corpo etc.). A cada requisição, o pacote net/http chama a função de handler associada à rota correspondente.

🏛️ Leitura de parâmetros: onde podemos extrair dados de query string (r.URL.Query()), rotas parametrizadas (bibliotecas de terceiros, mas o próprio net/http já permite pegar trechos da URL) ou corpo da requisição (r.Body). Com esses valores, decidimos o que será exibido.

🏛️ Templates (opcional, mas recomendado): é um pacote interno html/template (ou text/template) que permite separar HTML estático da lógica de preenchimento dinâmico de campos. Em vez de concatenar strings manualmente, usamos arquivos de template com placeholders ({{.Nome}}, {{range .Itens}}…{{end}} etc.) e chamamos template.Execute(w, dados) para renderizar.

Perfeito, agora que já temos uma noção de como isso funciona, vamos ver um pequeno exemplo na prática:

package main

import(
	"net/http"
	"time"
	"log"
	"fmt"
)

func mensagemLogin(w http.ResponseWriter, r *http.Request){
	s := time.Now().Format("02/01/2006 03:04:05") // Aqui não usamos o m/d/y h:i:s como em outras linguagens de programação
	fmt.Fprintf(w, "<h1>Você se Logou: %s</h1>", s)
}

func main(){
	http.HandleFunc("/login", mensagemLogin)
	log.Println("Executando Código...")
	log.Fatal(http.ListenAndServe(":3000", nil))
}

Se você abrir a URL localhost:3000/login, você vai se deparar com a seguinte mensagem:

Agora vamos ás explicações 😃

func mensagemLogin(w http.ResponseWriter, r *http.Request) {
    s := time.Now().Format("02/01/2006 03:04:05") // Aqui não usamos o m/d/y h:i:s como em outras linguagens de programação
    fmt.Fprintf(w, "<h1>Você se Logou: %s</h1>", s)
}

A função mensagemLogin é um handler HTTP que será chamada toda vez que uma requisição para uma determinada rota for executada.

w http.ResponseWriter: representa o “canal” pelo qual escreveremos a resposta HTTP de volta ao cliente (navegador, Postman etc.). Com ele podemos enviar cabeçalhos e corpo.

r *http.Request: contém todas as informações da requisição recebida—método (GET, POST etc.), URL, query-params, cabeçalhos, cookies, corpo, etc.

Após isso, usamos o comando s := time.Now().Format("02/01/2006 03:04:05"), para obter a hora e data atual que será mostrada no navegador do usuário.

Observação: a string de referência 02/01/2006 03:04:05 não é “day/month/year hour:minute:second” genérico; ela DEVE ser exatamente essa sequência para que o Format saiba qual é o “layout”. Ou seja, 02 representa dia, 01 mês, 2006 ano, 03 hora (12h), 04 minuto e 05 segundo.

fmt.Fprintf(w, "<h1>Você se Logou: %s</h1>", s)

Apesar de Fprintf ser um comando novo (que ainda não vimos nesta jornada), ele envia a saída formatada para um io.Writer. No nosso caso, o w (que implementa io.Writer) representa o corpo da resposta HTTP.

E por fim, temos os comandos HTTP que são responsáveis por "ligar" o nosso servidor:

func main() {
    http.HandleFunc("/login", mensagemLogin)
    log.Println("Executando Código...")
    log.Fatal(http.ListenAndServe(":3000", nil))
}

Incrível, não? 😉

Criando um servidor dinâmico usando router e template em HTML

Agora que você já sabe o funcionamento de um servidor dinâmico, vamos complexar ainda mais as coisas (no bom sentido, ok?).

No exemplo abaixo, nós vamos criar um servidor dinâmico com a ajuda de uma biblioteca chamada CHI, que vai ser capaz de identificar requisições via GET (com parâmetros) e chamar um template chamado pagina.html onde espera receber valores dinâmicos.

Vamos ver na prática como tudo isso funciona:

package main

import (
    "html/template"
    "log"
    "net/http"
    "time"

    "github.com/go-chi/chi/v5"
)

type PaginaData struct {
    Titulo   string
    Mensagem string
    Hora     string
}

func main() {
    r := chi.NewRouter()

    // Carrega templates no início (de preferência, de forma global)
    tmpl := template.Must(template.ParseGlob("templates/*.html"))

    // Rota dinâmica com rota amigável: /ola/{nome}
    r.Get("/ola/{nome}", func(w http.ResponseWriter, r *http.Request) {
        nome := chi.URLParam(r, "nome")
        dados := PaginaData{
            Titulo:   "Serviço Dinâmico em Go",
            Mensagem: "Bem-vindo, " + nome + "!",
            Hora:     time.Now().Format("02/01/2006 15:04:05"),
        }
        w.Header().Set("Content-Type", "text/html; charset=utf-8")
        if err := tmpl.ExecuteTemplate(w, "pagina.html", dados); err != nil {
            http.Error(w, "Erro ao renderizar template", http.StatusInternalServerError)
        }
    })

    // Rota raiz responde com hora atual em JSON
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json; charset=utf-8")
        hora := time.Now().Format(time.RFC3339)
        w.Write([]byte(`{"server_time":"` + hora + `"}`))
    })

    log.Println("Servidor dinâmico rodando em http://localhost:8080")
    if err := http.ListenAndServe(":8080", r); err != nil {
        log.Fatal(err)
    }
}

Não se esqueça de criar uma nova pasta chamada templates, onde dentro dela tenha o arquivo pagina.html com o seguinte conteúdo:

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <title>{{ .Titulo }}</title>
</head>
<body>
    <h1>{{ .Mensagem }}</h1>
    <p>Hora no servidor: {{ .Hora }}</p>
    <p><a href="/">Ver hora em JSON</a></p>
</body>
</html>

Além disso, tenha em mente que para rodar este projeto, você precisa executar um go mod tidy para baixar a biblioteca necessária (chi).

Observação: e se o go mod tidy der problemas? Neste caso, é bem provável que você ainda não tenha iniciado seu projeto em go (go.mod), portanto execute: go mod init servidores-http, antes de chamar o go mod tidy.

Se você rodar o projeto e abrir a seguinte URL em seu navegador: localhost:8080/ola/micilini, verá a seguinte mensagem:

Se clicar no botão [Ver hora em JSON] você será direcionado para /, onde verá a data e hora atual:

Vamos ás explicações? 🙃

r := chi.NewRouter(): cria uma nova instância de router utilizando a biblioteca chi. Em vez de usar o DefaultServeMux do pacote net/http, o chi.NewRouter() fornece um roteador mais flexível, leve e expressivo.

tmpl := template.Must(template.ParseGlob("templates/*.html")): carrega (parseia) todos os arquivos .html que estejam dentro da pasta templates/ e armazena esses templates na variável tmpl.

É importante ressaltar que a função template.ParseGlob("templates/*.html") percorre o padrão templates/*.html, parseando cada arquivo HTML como um template nomeado (geralmente usando o nome do arquivo como identificador).

r.Get("/ola/{nome}", func(w http.ResponseWriter, r *http.Request) {
    nome := chi.URLParam(r, "nome")
    dados := PaginaData{
        Titulo:   "Serviço Dinâmico em Go",
        Mensagem: "Bem-vindo, " + nome + "!",
        Hora:     time.Now().Format("02/01/2006 15:04:05"),
    }
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    if err := tmpl.ExecuteTemplate(w, "pagina.html", dados); err != nil {
        http.Error(w, "Erro ao renderizar template", http.StatusInternalServerError)
    }
})

r.Get("/ola/{nome}", ...): registra um handler que responde somente a requisições HTTP GET para o caminho especificado. Se alguém fizer uma requisição POST em /ola/algumcoisa, esse handler não será acionado (a menos que você registre também r.Post ou use um método genérico como r.MethodFunc).

dados := PaginaData{
    Titulo:   "Serviço Dinâmico em Go",
    Mensagem: "Bem-vindo, " + nome + "!",
    Hora:     time.Now().Format("02/01/2006 15:04:05"),
}

No código acima estamos montando o template inserindo todos os dados necessários que deverão ser embutidos dentro do arquivo pagina.html.

w.Header().Set("Content-Type", "text/html; charset=utf-8")

Para informar ao cliente (navegador) que a resposta que seguirá é do tipo HTML com codificação UTF-8. Se você não definir o cabeçalho Content-Type, o navegador pode tentar “adivinhar” o tipo (e possivelmente falhar em exibir corretamente caracteres especiais).

Repositório da lição

Todos os arquivos relacionados com esta lição, podem ser encontrados nos seguintes repositórios abaixo:

Conclusão

Nesta lição, você aprendeu a criar servidores estáticos e dinâmicos com GoLang.

Na próxima lição, nós ainda focaremos na construção de servidores dinâmicos, só que dessa vez, iremos fazer isso de forma mais estruturada e robusta, onde vamos aprender a criar uma aplicação HTTP completa.

Preparado? 😀

Criadores de Conteúdo

Foto do William Lima
William Lima
Fundador da Micilini

Inventor nato, escreve conteudos de programação para o portal da micilini.

Torne-se um MIC 🤖

Mais de 100 mic's já estão conectados na plataforma.