Ring é uma biblioteca baseada no WSGI do Python e no Rack do Ruby que nos ajuda na criação de aplicações web em Clojure, abstraindo os detalhes do HTTP. Ring é a base para muitas aplicações e bibliotecas do mundo web em Clojure, pois ele trabalha com o baixo nível das requisições HTTP. Ring possui três conceitos principais: Handler, Adapter e Middleware.
Handler
Para trabalhar com as requisições, precisamos criar handlers, que nada mais são do que funções que recebem uma requisição e devolvem uma resposta. As requisições e as repostas são mapas que contém informações como chaves baseadas na API de Servlet.
Requisição
Uma requisição contém informações que são acessadas através das chaves, onde as principais são:
- :server-port: Porta do servidor que está recebendo a requisição
- :server-name: Nome ou IP do servidor que está recebendo a requisição
- :query-string: Query string da requisição (?q=name)
- :request-method: Método HTTP utilizado na requisição (:post, :get, etc)
- :body: Corpo da requisição
- :headers: Headers da requisição
Resposta
Uma resposta contém apenas três informações que são:
- :body: Corpo da resposta
- :headers: Header da resposta contendo um mapa com chave/valor
- :status: Status da resposta (200, 404, 500, etc)
Adapter
Um adapter é uma ponte entre a aplicação Ring e os detalhes de implementação do protocolo HTTP. O adapter recebe uma requisição, transforma a requisição em um mapa e passa esse mapa para a aplicação Ring realizar o processamento. Após processar o mapa recebido, a aplicação Ring retorna um mapa de resposta que é usado pelo adapter para retornar a resposta HTTP.
Alguns exemplos de adapter:
Middleware
Um middleware permite que alteremos a forma como a requisição é processada. Middlewares são utilizados para ampliar a funcionalidade dos handlers. Um middleware é uma função que recebe um handler e outros parâmetros opcionais, processa-os e retornando um novo handler com novos comportamentos. No exemplo a seguir veremos como criar e utilizar middlewares existentes.
Criando um projeto com Ring
Para criar um servidor com o Ring, precisamos primeiramente criar um projeto. No nosso caso, utilizaremos o Leiningen para a criação do projeto. Para instalar e configurar o Leiningen, acesse o site e siga as instruções. Após instalar o Leiningen, execute o seguinte comando no seu terminal:
*lein new hello-ring
Será gerado um projeto com a seguinte estrutura:
Diretórios mais importantes para nós nesse momento:
- doc: Diretório onde é colocada a documentação do projeto
- src: Diretório onde fica o código fonte do projeto
- test: Diretório onde ficam os testes
- project.clj: Arquivo responsável por toda configuração do projeto (semelhante ao pom.xml de projetos Java com Maven)
Abra o arquivo project.clj. Começaremos adicionando novas dependências para o projeto. No vetor :dependencies, adicione duas dependências do Ring:
:dependencies [[org.clojure/clojure "1.10.0"]
; principais funções do Ring
[ring/ring-core "1.8.1"]
; adapter Jetty para Ring
[ring/ring-jety-adapter "1.8.1"]]
Após adicionar as duas dependências, execute o comando para baixa-las no seu reposítorio local do Leiningen.
*lein deps
Agora, abra o arquivo core.clj que está dentro do diretório src/hello_ring. Nesse arquivo temos a declaração do namespace hello-ring.core e a importação do adapter do servidor Jetty.
(ns hello-ring.core
(:require [ring.adapter.jetty :as jetty]))
Vamos adicionar nosso primeiro handler que retornará um mapa como resposta contendo o status, o corpo da requisição e um header de Content-Type.
(defn ola-web [request]
{:status 200
:body "Olá web"
:headers {"Content-Type" "text/plain; charset=utf-8"}})
Por fim, criamos uma função chamada -main que será chamada pelo Leiningen. Dentro da função main, iniciamos o servidor Jetty associando a ele o handler (ola-web) e um mapa com a configuração da porta (3000).
(ns hello-ring.core
(:require [ring.adapter.jetty :as jetty]))
(defn ola-web [request]
{:status 200
:body "Olá web"
:headers {"Content-Type" "text/plain; charset=utf-8"}})
(defn -main [& args]
(jetty/run-jetty ola-web {:port 3000}))
Para inicializar o servidor pelo terminal, precisamos fazer uma configuração a mais. Abra o arquivo project.clj e adicione uma linha (:main hello-ring.core) para dizer ao Leiningen que o projeto inicializará pelo namespace hello-ring.core através da função -main:
(defproject hello-ring "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-core "1.8.1"]
[ring/ring-jetty-adapter "1.8.1"]]
:main hello-ring.core
:repl-options {:init-ns hello-ring.core})
Agora, abra novamente o terminal e execute o comando para incializar o projeto.
*lein run
O servidor será inicializado. Abra o navegador e digite localhost:3000. Deve ser mostrada a mensagem "Olá web".
Criando um middleware
Criaremos um middleware que adicionará a chave :query-params ao header da requisição com os dados da query-string.
(defn parse-query
"Transforma a query-string em um mapa de parâmetros"
[query-string]
(if (> (count query-string) 0)
(apply hash-map (str/split query-string #"[&=]"))))
(defn wrap-query-param
"Adiciona a chave :query-param ao header da requisição"
[handler]
(fn [request]
(let [query-param (parse-query (:query-string request))
nova-req (assoc request :query-params query-param)]
(handler nova-req))))
No código acima, criamos a função parse-query responsável por quebrar os parâmetros da requisição e criar um mapa de chave/valor. Nosso middleware (wrap-query-param) captura os parâmetros, recebe o mapa criado pela função anterior e associa esse mapa a requisição atual, criando uma nova requisição e passando-a para o handler recebido como parâmetro do middleware.
Para utilizar nosso middleware, passamos nosso handler como parâmetro para ele, que por sua vez, é passado como parâmetro para a função que cria o servidor.
(defn -main [& args]
(jetty/run-jetty (wrap-query-param ola-web) {:port 3000}))
Alteramos o código da função ola-web para pegar o parâmetro e retornar a mensagem. Abaixo o código completo:
(ns hello-ring.core
(:require [ring.adapter.jetty :as jetty]
[clojure.string :as str]))
(defn ola-web [request]
(let [nome (get (:query-params request) "nome")]
{:status 200
:body (str "Olá " nome)
:headers {"Content-Type" "text/plain; charset=utf-8"}}))
(defn parse-query
"Transforma a query-string em um mapa de parâmetros"
[query-string]
(if (> (count query-string) 0)
(apply hash-map (str/split query-string #"[&=]"))))
(defn wrap-query-param
"Adiciona a chave :query-param ao header da requisição"
[handler]
(fn [request]
(let [query-param (parse-query (:query-string request))
nova-req (assoc request :query-params query-param)]
(handler nova-req))))
(defn -main [& args]
(jetty/run-jetty (wrap-query-param ola-web) {:port 3000}))
Execute novamente o comando para subir o servidor:
*lein run
No navegador, digite localhost:3000/?nome=Guilherme e veja o resultado.
Utilizando um middleware pronto para retornar JSON
Agora utilizaremos dois middlewares existentes. Um vai capturar os dados da query-string como fizemos no nosso middleware e o outro retornará os dados da requisição como JSON. Abra o arquivo project.clj e adicione a seguinte dependência responsável pelo middleware de JSON:
*[ring/ring-json "0.5.0"]
No nosso namespace hello-ring.core, vamos adicionar as dependências dos dois middlewares, sendo o wrap-params pertencente à biblioteca ring-core e o wrap-json-response à biblioteca ring-json.
(ns hello-ring.core
(:require [ring.adapter.jetty :as jetty]
[ring.middleware.params :refer [wrap-params]]
[ring.middleware.json :refer [wrap-json-response]]))
(defn ola-web [request]
(let [nome (get (:query-params request) "nome")]
{:status 200
:body {:nome nome}
:headers {}}))
(defn -main [& args]
(jetty/run-jetty
(-> ola-web
wrap-params
wrap-json-response)
{:port 3000}))
No nosso código acima temos algo um pouco diferente na inicialização do servidor. Utilizamos uma thread-fist para processar nosso handler utilizando os dois middlewares. Thread-first é uma forma que temos em Clojure de processar informações de forma clara e encadeada, pois o resultado da primeira linha é passado como parâmetro para a função da linha de baixo. Nesse caso, passamos nosso handler como parâmetro para o middleware wrap-params e o resultado do retorno desse middleware é passado como parâmetro para o middleware de baixo (wrap-json-response).
Execute novamente o comando para subir o servidor:
*lein run
No navegador, digite localhost:3000/?nome=Guilherme. O resultado é mostrado no formato JSON.
Podemos melhorar o retorno do nosso handler adicionando uma biblioteca para utilizar os métodos HTTP. No arquivo project.clj, adicione a dependência abaixo:
*[metosin/ring-http-response "0.9.1"]
No namespace hello-ring.core, adicione a dependência do http-response para a utilização da função ok. Passamos o mesmo mapa para a função ok, que retornará o status 200 juntamente com o corpo da resposta.
(ns hello-ring.core
(:require [ring.adapter.jetty :as jetty]
[ring.middleware.params :refer [wrap-params]]
[ring.middleware.json :refer [wrap-json-response]]
[ring.util.http-response :refer [ok]]))
(defn ola-web [request]
(let [nome (get (:query-params request) "nome")]
(ok {:nome nome})))
(defn -main [& args]
(jetty/run-jetty
(-> ola-web
wrap-params
wrap-json-response)
{:port 3000}))
Ring é uma biblioteca potente e básica para inicializar o desenvolvimento web em Clojure, porém é a base para outras bibliotecas que veremos mais pra frente, como Compojure, Reitit e Pedestal. Nos vemos num próximo post. :)
Top comments (0)