Desenvolvimento de servidores REST em Go. Parte 2: aplicando o roteador gorila / mux

Este é o segundo artigo de uma série de artigos sobre o desenvolvimento de servidores REST em Go. No primeiro artigo desta série, criamos um servidor simples usando ferramentas Go padrão e, em seguida, refatoramos o código de geração de dados JSON, transformando-o em uma função auxiliar. Isso nos permitiu chegar a um código bastante compacto para manipuladores de rota.



Lá falamos sobre um problema com nosso servidor, que é que a lógica de roteamento está espalhada por vários lugares em nosso programa.







Esse é um problema que todos os que escrevem em servidores HTTP sem usar dependências enfrentam. A menos que o servidor, tendo em conta o sistema de suas rotas, não seja um design extremamente minimalista (por exemplo, são alguns servidores especializados que possuem apenas uma ou duas rotas), então verifica-se que o tamanho e a complexidade da organização de o código do roteador é algo que programadores experientes prestam atenção muito rapidamente.



▍ Sistema de roteamento aprimorado



O primeiro pensamento que pode ocorrer a alguém que decidiu melhorar nosso servidor pode ser a ideia de abstrair seu sistema de roteamento, talvez usando um conjunto de funções ou um tipo de dados com métodos. Existem muitas abordagens interessantes para resolver este problema, aplicáveis ​​a cada situação específica. O ecossistema Go tem muitas bibliotecas de terceiros poderosas que foram usadas com sucesso em vários projetos para implementar recursos de roteador. Recomendo enfaticamente que você dê uma olhada neste material, que compara várias abordagens para lidar com conjuntos simples de rotas.



Antes de passar para um exemplo prático, vamos lembrar como funciona a API do nosso servidor:



POST   /task/              :       ID
GET    /task/<taskid>      :       ID
GET    /task/              :    
DELETE /task/<taskid>      :     ID
GET    /tag/<tagname>      :       
GET    /due/<yy>/<mm>/<dd> :    ,    

      
      





Para tornar o sistema de roteamento mais conveniente, podemos fazer o seguinte:



  1. Você pode criar um mecanismo que permite definir manipuladores separados para diferentes métodos da mesma rota. Por exemplo, uma solicitação POST /task/



    deve ser processada por um manipulador e uma solicitação GET /task/



    por outro.
  2. Você pode fazer com que o manipulador de rota seja selecionado com base em uma análise mais profunda das solicitações do que é agora. Ou seja, por exemplo, com esta abordagem, devemos ser capazes de indicar que um manipulador processa uma solicitação para /task/



    , e outro manipulador processa uma solicitação para /task/<taskid>



    com um numérico ID



    .
  3. Neste caso, o sistema de processamento de rota deve simplesmente extrair o numérico ID



    de /task/<taskid>



    e passá-lo para o manipulador de alguma forma conveniente para nós.


Escrever seu próprio roteador no Go é muito fácil. Isso ocorre porque você pode organizar seu trabalho com manipuladores HTTP usando layout. Mas aqui não vou ceder ao meu desejo de escrever tudo sozinho. Em vez disso, proponho falar sobre como organizar um sistema de roteamento usando um dos roteadores mais populares, chamado gorila / mux .



▍ Servidor de aplicativos de gerenciamento de tarefas usando gorilla / mux



O pacote gorilla / mux é um dos roteadores HTTP mais antigos e populares para Go. A palavra "mux", de acordo com a documentação do pacote, significa "multiplexador de solicitação HTTP" ("mux" tem o mesmo significado na biblioteca padrão).



Por se tratar de um pacote destinado a resolver uma única tarefa altamente especializada, é muito fácil de usar. Uma variante do nosso servidor que usa gorilla / mux para roteamento pode ser encontrada aqui . Aqui está o código para definir as rotas:



router := mux.NewRouter()
router.StrictSlash(true)
server := NewTaskServer()

router.HandleFunc("/task/", server.createTaskHandler).Methods("POST")
router.HandleFunc("/task/", server.getAllTasksHandler).Methods("GET")
router.HandleFunc("/task/", server.deleteAllTasksHandler).Methods("DELETE")
router.HandleFunc("/task/{id:[0-9]+}/", server.getTaskHandler).Methods("GET")
router.HandleFunc("/task/{id:[0-9]+}/", server.deleteTaskHandler).Methods("DELETE")
router.HandleFunc("/tag/{tag}/", server.tagHandler).Methods("GET")
router.HandleFunc("/due/{year:[0-9]+}/{month:[0-9]+}/{day:[0-9]+}/", server.dueHandler).Methods("GET")

      
      





Observe que essas definições sozinhas fecham imediatamente os dois primeiros itens da lista acima de tarefas que precisam ser resolvidas para melhorar a conveniência de trabalhar com rotas. Devido ao fato de que chamadas são usadas na descrição de rotas Methods



, podemos facilmente atribuir diferentes métodos para diferentes manipuladores em uma rota. Modelos de correspondência (usando expressões regulares) nas maneiras nos permite distinguir facilmente /task/



e /task/<taskid>



na descrição de rota de nível superior.



Para lidar com a tarefa, que está no terceiro parágrafo de nossa lista, vamos examinar o uso getTaskHandler



:



func (ts *taskServer) getTaskHandler(w http.ResponseWriter, req *http.Request) {
  log.Printf("handling get task at %s\n", req.URL.Path)

  //          Atoi,   
  //   ,    [0-9]+.
  id, _ := strconv.Atoi(mux.Vars(req)["id"])
  ts.Lock()
  task, err := ts.store.GetTask(id)
  ts.Unlock()

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

  renderJSON(w, task)
}

      
      





Em uma definição de rota, uma rota /task/{id:[0-9]+}/



descreve uma expressão regular usada para analisar um caminho e atribui um identificador a uma "variável" id



. Essa "variável" pode ser acessada chamando a função mux.Vars



e transmitindo-a a ela req



(o gorilla / mux armazena essa variável no contexto de cada solicitação e mux.Vars



é uma função auxiliar conveniente para trabalhar com ela).



▍ Comparação de diferentes abordagens para organizar o roteamento



É assim que a sequência de leitura do código se parece na versão original do servidor para aqueles que procuram entender como uma rota é processada GET /task/<taskid>



.





Aqui está o que você deve ler se quiser entender o código que usa gorilla / mux:





Ao usar o gorilla / mux, você não terá apenas que "pular" menos pelo texto do programa. Além disso, aqui você terá que ler muito menos código. Na minha humilde opinião, isso é muito bom em termos de melhorar a legibilidade do código. Descrever caminhos ao usar gorilla / mux é uma tarefa simples e requer apenas uma pequena quantidade de código para ser resolvido. E quem ler esse código entenderá imediatamente como ele funciona. Outra vantagem dessa abordagem é que todas as rotas podem ser vistas literalmente examinando o código em um só lugar. E, de fato, o código de configuração de roteamento agora se parece muito com a descrição de formato livre de nossa API REST.



Gosto de usar pacotes como gorilla / mux porque são ferramentas muito especializadas. Eles resolvem um único problema e o fazem bem. Eles não “se arrastam” para cada canto do código do programa do projeto, o que significa que, se necessário, podem ser facilmente removidos ou substituídos por qualquer outra coisa. Se você olhar o código completoda variante de servidor da qual estamos falando neste artigo, você pode ver que o escopo dos mecanismos gorila / mux é limitado a algumas linhas de código. Se, à medida que o projeto se desenvolve, alguma limitação for encontrada no pacote gorilla / mux que seja incompatível com as especificações deste projeto, a tarefa de substituir o gorilla / mux por outro roteador de terceiros (ou por seu próprio roteador) deve ser resolvida com rapidez e facilidade.



Qual roteador você usaria ao desenvolver um servidor REST em Go?








All Articles