Noções básicas de HTTP com Go 1.22
Jorge D. Ortiz-Fuentes7 min read • Published Apr 17, 2024 • Updated Apr 23, 2024
APLICATIVO COMPLETO
Avalie esse Tutorial
Go é uma linguagem de programação bonita, muito produtivo e com muitos recursos. Esta série de artigos foi criada para oferecer a você um passo a passo dos recursos da linguagem, enquanto criamos um programa realista do zero.
Para que isso funcione, há algumas coisas com as quais devemos concordar:
- Esta não é uma explicação abrangente da sintaxe Go. Explicarei apenas as partes estritamente necessárias para escrever o código desses artigos.
- Digitar o código é melhor do que apenas copiar e colar, mas faça como quiser.
- Os materiais estão disponíveis para serem testados por você mesmo, no seu próprio ritmo, mas é recomendável jogar junto se você fizer isso em uma sessão ao vivo.
- Se você é um novato no Golang, digite e confie. Se você escreveu algum Go, faça qualquer pergunta. Se você tiver experiência com a Golang, há comentários sobre as melhores práticas -vamos consultá-las. Em resumo: perguntar sobre a sintaxe, perguntar sobre a milagrosidade, fale sobre os tópicos avançados ou Go para a barra.
- Finalmente, embora abordemos apenas as partes essenciais, o produto desta série é a semente para um back-end de anotações, onde lidaremos com as notas e seus metadados. Espero que você goste.
- Vamos começar criando um diretório para nosso projeto e inicializando o projeto. Crie um diretório e entre nele. Inicialize o projeto como um módulo Go com o identificador “github.com/jdortiz/go-intro,”, que você deve alterar para algo exclusivo e de sua propriedade.
1 go mod init github.com/jdortiz/go-intro - No gerenciador de arquivos do VSCode, adicione um novo arquivo chamado
main.go
com o seguinte conteúdo:1 package main 2 3 import "fmt" 4 5 func main() { 6 fmt.Println("Hola Caracola") 7 } - Go juntos percorrer o conteúdo desse arquivo para entender o que estamos fazendo aqui.
- Cada arquivo de origem deve pertencer a um
package
. Todos os arquivos em um diretório devem pertencer ao mesmo pacote. O pacotemain
é onde você deve criar sua funçãomain
. func
é a palavra-chave para declarar funções emain
é onde seu programa começa a ser executado.fmt.Println()
é uma função da biblioteca padrão (stdlib) para imprimir algum texto na saída padrão. Pertence ao pacotefmt
.- Ter a instrução
import
nos permite usar o pacotefmt
no código, como estamos fazendo com a funçãofmt.Println()
.
- O ambiente está configurado para que possamos executar o programa a partir do VS Code. Use "Executar e depurar" na barra esquerda e execute o programa. A mensagem "Hola caracola" aparecerá no console de depuração.
- Você também pode executar o programa a partir do terminal incorporado usando
1 go run main.go
- A biblioteca padrão do Go inclui todas as peças necessárias para criar um servidor HTTP completo. Até a versão 1.22, usando pacotes de terceiros para funcionalidades adicionais, como rotear facilmente solicitações com base no verbo HTTP, era muito comum. Go 1.22 adicionou a maioria das funcionalidades desses pacotes de forma compatível com versões anteriores.
- Os servidores da web escutam solicitações feitas para um determinado endereço IP e porta. Vamos definir isso em uma constante dentro da função principal:
1 const serverAddr string = "127.0.0.1:8081" - Se quisermos responder às solicitações enviadas ao diretório raiz do nosso servidor web, devemos dizer a ele que estamos interessados nesse caminho de URL e no que queremos que aconteça quando uma solicitação for recebida. Fazemos isso usando
http.HandleFunc()
na parte inferior da função principal, com dois parâmetros: um padrão e uma função. O padrão indica o caminho em que estamos interessados (como em"/"
ou"/customers"
) mas, como Go 1.22, o padrão também pode ser usado para especificar o verbo HTTP, restringir a um determinado nome de host e/ou extrair parâmetros da URL. Usaremos"GET /"
, o que significa que estamos interessados em solicitações GET para a raiz. A função usa dois parâmetros: umhttp.ResponseWriter
, usado para produzir a resposta e umhttp.Request
que contém os dados da solicitação. Usaremos uma função anônima (também conhecida como lambda) que inicialmente não faz nada. Você precisará importar o pacote "net/http" e o VS Code pode fazer isso automaticamente usando seus recursosde correção rápida.1 http.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { 2 }) - Dentro de nossa lambda, podemos usar o escritor de resposta para adicionar uma mensagem à nossa resposta. Usamos o método
Write()
do escritor de resposta que usa uma fatia de bytes (ou seja, uma "visualização" de uma array), portanto, precisamos converter a string. HTML pode ser adicionado aqui.1 w.Write([]byte("HTTP Caracola")) - Diga ao servidor para aceitar conexões com o endereço IP e a porta com a funcionalidade que acabamos de configurar. Faça isso após toda a invocação para
http.HandleFunc()
.1 http.ListenAndServe(serverAddr, nil) http.ListenAndServe()
retorna um erro ao terminar. É uma boa ideia envolvê-lo com outra função que registrará a mensagem quando isso acontecer.log
também precisa ser importado: faça você mesmo se o VSCode não cuidou disso.1 log.Fatal(http.ListenAndServe(serverAddr, nil)) - Compile e execute. O codespace oferecerá o uso de um navegador ou a abertura da porta. Você pode ignorar isso por enquanto.
- Se você executar o programa a partir do terminal, abra um segundo terminal usando o endereço ".
1 curl -i localhost:8081/
- Os manipuladores de HTTP também podem ser implementados como funções regulares, ou seja, não anônimas, e são realmente mais fáceis de manter. Vamos definir um para um endpoint que pode ser usado para criar uma nota após a função
main
.1 func createNote(w http.ResponseWriter, r *http.Request) { 2 } - Antes de implementarmos esse manipulador, precisamos definir um tipo que conterá os dados de uma nota. A nota mais simples poderia ter um título e um texto. Colocaremos esse código antes da função
main
.1 type Note struct { 2 Title string 3 Text string 4 } - Mas podemos ter mais alguns dados, como uma lista de categorias, que em Go é representada como uma fatia de cadeias de caracteres (
[]string
), ou um campo que usa outro tipo que define o escopo dessa nota como uma combinação de um projeto e uma área. A definição completa desses tipos seria:1 type Scope struct { 2 Project string 3 Area string 4 } 5 6 type Note struct { 7 Title string 8 Tags []string 9 Text string 10 Scope Scope 11 } - Observe que os nomes dos tipos e dos campos começam com uma letra maiúscula. Essa é a maneira de dizer no Go que algo é exportado e também se aplica aos nomes das funções. É semelhante ao uso de um atributo
public
em outras linguagens de programação. - Além disso, observe que as declarações de campo têm o nome do campo primeiro e seu tipo depois. O campo mais recente é chamado " Scope, " porque é exportado, e seu tipo, definido algumas linhas acima, também é chamado de Scope. Não há problema aqui — Go entenderá a diferença com base na posição.
- Dentro do nosso manipulador
createNote()
, agora podemos definir uma variável para esse tipo. A ordem também é primeiro nome da variável e segundo tipo.note
é uma variável válida daqui em diante, mas no momento todos os campos estão vazios.1 var note Note - Os dados são trocados entre servidores e clientes HTTP usando algum formato de serialização. Um dos mais comuns atualmente é o JSON. Após a linha anterior, vamos criar um decodificador que possa converter bytes do fluxo de solicitação HTTP em um objeto real. O pacote
encoding/json
da biblioteca padrão fornece o que precisamos. Observe que eu não declarei a variáveldecoder
. Eu uso a "declaração de variável curta" (:=
), que declara e atribui valor à variável. Nesse caso, o Go também está fazendo inferência de tipo.1 decoder := json.NewDecoder(r.Body) - Este decodificador agora pode ser utilizado na próxima linha para desserializar os dados na solicitação HTTP. Esse método retorna um erro, que será
nil
(sem valor) se tudo correu bem, ou algum valor (erro) de outra forma. Observe que usamos&
para passar uma referência à variável, para que o método possa alterar seu valor.1 err := decoder.Decode(¬e) - A expressão pode ser agrupada para ser usada como condição em uma instrução if. É perfeitamente normal em Go obter algum valor e depois comparar em uma expressão após um ponto e vírgula. Não há parênteses ao redor da expressão condicional.
1 if err := decoder.Decode(¬e); err != nil { 2 } - Se algo der errado, queremos informar ao cliente HTTP que há um problema e encerrar a função. Essa saída antecipada é muito comum quando você lida com erros em Go. A função
http.Error()
é fornecida pelo pacotenet/http
, grava no gravador de resposta a mensagem de erro fornecida e define o status HTTP.1 http.Error(w, err.Error(), http.StatusBadRequest) 2 return - Se tudo correr bem, apenas imprimimos o valor da nota que foi enviada pelo cliente. Aqui, usamos outra função do pacote
fmt
que grava em um gravador os dados fornecidos, usando uma string de formato. As strings de formato são semelhantes às usadas em C, mas com algumas opções extras e mais segurança."%+v"
significa imprimir o valor em um formato padrão e incluir os nomes dos campos (% para indicar que este é um especificador de formato, v para imprimir o valor, o + para incluir os nomes dos campos).1 fmt.Fprintf(w, "Note: %+v", note) - Vamos adicionar este manipulador ao nosso servidor. Ele será usado quando uma solicitação POST for enviada para o caminho
/notes
.1 http.HandleFunc("POST /notes", createNote) - Execute esta nova versão.
- Vamos primeiro testar o que acontece quando não é possível desserializar os dados. Devemos receber um código de status 400 e a mensagem de erro no corpo.
1 curl -iX POST localhost:8081/notes - Finalmente, vamos ver o que acontece quando passamos alguns dados bons. Os dados desserializados serão impressos na saída padrão do programa.
1 curl -iX POST -d '{ "title": "Master plan", "tags": ["ai","users"], "text": "ubiquitous AI", "scope": {"project": "world domination", "area":"strategy"} }' localhost:8081/notes
Neste artigo, ensinamos:
- Como iniciar e inicializar um projeto Go.
- Como escrever um servidor HTTP básico do zero usando apenas a funcionalidade de biblioteca padrão Go.
- Como adicionar endpoints ao nosso servidor HTTP que fornecem solicitações diferentes para diferentes verbos HTTP na solicitação do cliente.
- Como desserializar dados JSON da solicitação e usá-los em nosso programa.
Desenvolver esse tipo de programa no Go é muito fácil e não requer pacotes externos ou, pelo menos, não muitos. Se este foi seu primeiro passo no mundo da programação Go, espero que tenha aproveitado e que, se teve alguma experiência anterior com Go, tivesse algo de valor para você.
No próximo artigo desta série, Go e persistiremos os dados que trocamos com o cliente HTTP. Este repositório com todo o código deste artigo e dos próximos para que você possa acompanhar.
Mantenha-se atento. Hackeie seu código. Até a próxima!
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.