O plugue é:
Em outras palavras, o Plug permite construir aplicativos web a partir de pequenos pedaços e executá-los em diferentes servidores web. Plug é usado por frameworks web como Phoenix para gerenciar solicitações, respostas e websockets. Esta documentação mostrará alguns exemplos de alto nível e apresentará os principais blocos de construção do Plug.
Para usar o Plug, você precisa de um servidor web e suas ligações para o Plug. Existem duas opções no momento:
Use o servidor web Cowboy (baseado em Erlang) adicionando o pacote plug_cowboy
ao seu mix.exs
:
def deps do
[
{ :plug_cowboy , "~> 2.0" }
]
end
Use o servidor web Bandit (baseado em Elixir) adicionando o pacote bandit
ao seu mix.exs
:
def deps do
[
{ :bandit , "~> 1.0" }
]
end
Este é um exemplo mínimo de hello world, usando o servidor web Cowboy:
Mix . install ( [ :plug , :plug_cowboy ] )
defmodule MyPlug do
import Plug.Conn
def init ( options ) do
# initialize options
options
end
def call ( conn , _opts ) do
conn
|> put_resp_content_type ( "text/plain" )
|> send_resp ( 200 , "Hello world" )
end
end
require Logger
webserver = { Plug.Cowboy , plug: MyPlug , scheme: :http , options: [ port: 4000 ] }
{ :ok , _ } = Supervisor . start_link ( [ webserver ] , strategy: :one_for_one )
Logger . info ( "Plug now running on localhost:4000" )
Process . sleep ( :infinity )
Salve esse trecho em um arquivo e execute-o como elixir hello_world.exs
. Acesse http://localhost:4000/ e você será saudado!
No exemplo acima, escrevemos nosso primeiro módulo plug , chamado MyPlug
. Os plugs de módulo devem definir a função init/1
e a função call/2
. call/2
é invocado com a conexão e as opções retornadas por init/1
.
O Plug v1.14 inclui uma API upgrade
de conexão, o que significa que fornece suporte WebSocket pronto para uso. Vejamos um exemplo, desta vez usando o servidor web Bandit e o projeto websocket_adapter
para os bits WebSocket. Como precisamos de rotas diferentes, usaremos o Plug.Router
integrado para isso:
Mix . install ( [ :bandit , :websock_adapter ] )
defmodule EchoServer do
def init ( options ) do
{ :ok , options }
end
def handle_in ( { "ping" , [ opcode: :text ] } , state ) do
{ :reply , :ok , { :text , "pong" } , state }
end
def terminate ( :timeout , state ) do
{ :ok , state }
end
end
defmodule Router do
use Plug.Router
plug Plug.Logger
plug :match
plug :dispatch
get "/" do
send_resp ( conn , 200 , """
Use the JavaScript console to interact using websockets
sock = new WebSocket("ws://localhost:4000/websocket")
sock.addEventListener("message", console.log)
sock.addEventListener("open", () => sock.send("ping"))
""" )
end
get "/websocket" do
conn
|> WebSockAdapter . upgrade ( EchoServer , [ ] , timeout: 60_000 )
|> halt ( )
end
match _ do
send_resp ( conn , 404 , "not found" )
end
end
require Logger
webserver = { Bandit , plug: Router , scheme: :http , port: 4000 }
{ :ok , _ } = Supervisor . start_link ( [ webserver ] , strategy: :one_for_one )
Logger . info ( "Plug now running on localhost:4000" )
Process . sleep ( :infinity )
Salve esse trecho em um arquivo e execute-o como elixir websockets.exs
. Acesse http://localhost:4000/ e você deverá ver mensagens no console do seu navegador.
Desta vez utilizamos Plug.Router
, que nos permite definir as rotas utilizadas pela nossa aplicação web e uma série de passos/plugs, como plug Plug.Logger
, a serem executados a cada requisição.
Além disso, como você pode ver, o Plug abstrai os diferentes servidores web. Ao inicializar seu aplicativo, a diferença está entre escolher Plug.Cowboy
ou Bandit
.
Por enquanto, iniciamos o servidor diretamente em um supervisor descartável, mas, para implantações de produção, você deseja iniciá-los na árvore de supervisão de aplicativos. Consulte a seção Manipuladores supervisionados a seguir.
Em um sistema de produção, você provavelmente desejará iniciar o pipeline do Plug sob a árvore de supervisão do seu aplicativo. Inicie um novo projeto Elixir com a flag --sup
:
$ mix new my_app --sup
Adicione :plug_cowboy
(ou :bandit
) como uma dependência ao seu mix.exs
:
def deps do
[
{ :plug_cowboy , "~> 2.0" }
]
end
Agora atualize lib/my_app/application.ex
da seguinte forma:
defmodule MyApp.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@ moduledoc false
use Application
def start ( _type , _args ) do
# List all child processes to be supervised
children = [
{ Plug.Cowboy , scheme: :http , plug: MyPlug , options: [ port: 4001 ] }
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [ strategy: :one_for_one , name: MyApp.Supervisor ]
Supervisor . start_link ( children , opts )
end
end
Finalmente crie lib/my_app/my_plug.ex
com o módulo MyPlug
.
Agora execute mix run --no-halt
e ele iniciará seu aplicativo com um servidor web rodando em http://localhost:4001.
Plug.Conn
No exemplo hello world, definimos nosso primeiro plug chamado MyPlug
. Existem dois tipos de plugues: plugues de módulo e plugues de função.
Um módulo plug implementa uma função init/1
para inicializar as opções e uma função call/2
que recebe a conexão e as opções inicializadas e retorna a conexão:
defmodule MyPlug do
def init ( [ ] ) , do: false
def call ( conn , _opts ) , do: conn
end
Uma função plug pega a conexão, um conjunto de opções como argumentos e retorna a conexão:
def hello_world_plug ( conn , _opts ) do
conn
|> put_resp_content_type ( "text/plain" )
|> send_resp ( 200 , "Hello world" )
end
Uma conexão é representada pela estrutura %Plug.Conn{}
:
% Plug.Conn {
host: "www.example.com" ,
path_info: [ "bar" , "baz" ] ,
...
}
Os dados podem ser lidos diretamente da conexão e também com correspondência de padrões. A manipulação da conexão geralmente acontece com o uso das funções definidas no módulo Plug.Conn
. Em nosso exemplo, put_resp_content_type/2
e send_resp/3
são definidos em Plug.Conn
.
Lembre-se que, como tudo no Elixir, uma conexão é imutável , então cada manipulação retorna uma nova cópia da conexão:
conn = put_resp_content_type ( conn , "text/plain" )
conn = send_resp ( conn , 200 , "ok" )
conn
Por fim, lembre-se de que uma conexão é uma interface direta com o servidor web subjacente . Quando você chama send_resp/3
acima, ele envia imediatamente o status e o corpo fornecidos de volta ao cliente. Isso torna muito fácil trabalhar com recursos como streaming.
Plug.Router
Para escrever um plug de "roteador" que é despachado com base no caminho e método das solicitações recebidas, Plug fornece Plug.Router
:
defmodule MyRouter do
use Plug.Router
plug :match
plug :dispatch
get "/hello" do
send_resp ( conn , 200 , "world" )
end
forward "/users" , to: UsersRouter
match _ do
send_resp ( conn , 404 , "oops" )
end
end
O roteador é um plug. Não só isso: ele também contém seu próprio pipeline de plug-in. O exemplo acima diz que quando o roteador é invocado, ele invocará o plug :match
, representado por uma função match/2
local (importada), e então chamará o plug :dispatch
que executará o código correspondente.
O plug vem com muitos plugs que você pode adicionar ao pipeline de plug do roteador, permitindo conectar algo antes que uma rota corresponda ou antes que uma rota seja despachada. Por exemplo, se você quiser adicionar log ao roteador, basta fazer:
plug Plug.Logger
plug :match
plug :dispatch
Nota Plug.Router
compila todas as suas rotas em uma única função e depende da VM Erlang para otimizar as rotas subjacentes em uma pesquisa em árvore, em vez de uma pesquisa linear que corresponderia a rota por rota. Isso significa que as pesquisas de rota são extremamente rápidas no Plug!
Isso também significa que é recomendado que um bloco catch all match
seja definido como no exemplo acima, caso contrário o roteamento falhará com um erro de cláusula de função (como aconteceria em qualquer função Elixir regular).
Cada rota precisa retornar a conexão conforme a especificação do Plug. Consulte a documentação Plug.Router
para obter mais informações.
O plug vem com um módulo Plug.Test
que facilita o teste de seus plugues. Veja como podemos testar o roteador acima (ou qualquer outro plugue):
defmodule MyPlugTest do
use ExUnit.Case , async: true
use Plug.Test
@ opts MyRouter . init ( [ ] )
test "returns hello world" do
# Create a test connection
conn = conn ( :get , "/hello" )
# Invoke the plug
conn = MyRouter . call ( conn , @ opts )
# Assert the response and status
assert conn . state == :sent
assert conn . status == 200
assert conn . resp_body == "world"
end
end
Este projeto visa fornecer diferentes plugues que podem ser reutilizados em várias aplicações:
Plug.BasicAuth
– fornece autenticação HTTP básica;Plug.CSRFProtection
- adiciona proteção contra falsificação de solicitação entre sites ao seu aplicativo. Normalmente necessário se você estiver usando Plug.Session
;Plug.Head
- converte solicitações HEAD em solicitações GET;Plug.Logger
- registra solicitações;Plug.MethodOverride
- substitui um método de solicitação por um especificado nos parâmetros de solicitação;Plug.Parsers
- responsável por analisar o corpo da solicitação de acordo com seu tipo de conteúdo;Plug.RequestId
- configura um ID de solicitação para ser usado em logs;Plug.RewriteOn
- reescreve o host/porta/protocolo da solicitação a partir dos cabeçalhos x-forwarded-*
;Plug.Session
- lida com gerenciamento e armazenamento de sessões;Plug.SSL
– impõe solicitações através de SSL;Plug.Static
- serve arquivos estáticos;Plug.Telemetry
- instrumenta o pipeline de plug com eventos :telemetry
;Você pode entrar em mais detalhes sobre cada um deles em nossos documentos.
Módulos que podem ser usados após usar Plug.Router
ou Plug.Builder
para ajudar no desenvolvimento:
Plug.Debugger
- mostra uma página de depuração útil sempre que há uma falha em uma solicitação;Plug.ErrorHandler
- permite que desenvolvedores personalizem páginas de erro em caso de travamentos ao invés de enviar uma em branco; Convidamos todos a contribuir com o Plug e nos ajudar a resolver os problemas existentes!
Use o rastreador de problemas para relatórios de bugs ou solicitações de recursos. Abra uma solicitação pull quando estiver pronto para contribuir. Ao enviar uma solicitação pull, você não deve atualizar o CHANGELOG.md
.
Se você planeja contribuir com documentação, verifique nossas práticas recomendadas para redigir documentação.
Por fim, lembre-se que todas as interações em nossos espaços oficiais seguem nosso Código de Conduta.
Filial | Apoiar |
---|---|
v1.15 | Correções de bugs |
v1.14 | Somente patches de segurança |
v1.13 | Somente patches de segurança |
v1.12 | Somente patches de segurança |
v1.11 | Somente patches de segurança |
v1.10 | Somente patches de segurança |
v1.9 | Sem suporte desde 10/2023 |
v1.8 | Não suportado desde 01/2023 |
v1.7 | Não suportado desde 01/2022 |
v1.6 | Não suportado desde 01/2022 |
v1.5 | Não suportado desde 03/2021 |
v1.4 | Não suportado desde 12/2018 |
v1.3 | Sem suporte desde 12/2018 |
v1.2 | Não suportado desde 06/2018 |
v1.1 | Não suportado desde 01/2018 |
v1.0 | Não suportado desde 05/2017 |
O código-fonte do plug é lançado sob a licença Apache 2.0. Verifique o arquivo LICENSE para obter mais informações.