请求与响应 #
一、请求对象 #
1.1 Conn结构 #
elixir
%Plug.Conn{
host: "localhost",
method: "GET",
path_info: ["users", "1"],
request_path: "/users/1",
params: %{"id" => "1"},
query_string: "page=1",
req_headers: [{"content-type", "application/json"}],
req_cookies: %{"session" => "abc123"},
body_params: %{"name" => "John"},
assigns: %{},
status: nil,
resp_headers: [],
resp_cookies: %{},
state: :unset
}
1.2 请求信息 #
elixir
def show(conn, params) do
host = conn.host
method = conn.method
path = conn.request_path
query = conn.query_string
headers = conn.req_headers
json(conn, %{
host: host,
method: method,
path: path,
query: query
})
end
二、请求参数 #
2.1 参数来源 #
elixir
def create(conn, params) do
path_params = conn.path_params
query_params = conn.query_params
body_params = conn.body_params
all_params = conn.params
json(conn, %{
path: path_params,
query: query_params,
body: body_params,
all: all_params
})
end
2.2 路径参数 #
elixir
get "/users/:id", UserController, :show
get "/posts/:post_id/comments/:id", CommentController, :show
elixir
def show(conn, %{"id" => id}) do
user = Hello.Accounts.get_user!(id)
render(conn, :show, user: user)
end
2.3 查询参数 #
elixir
def index(conn, params) do
page = Map.get(params, "page", "1")
search = Map.get(params, "search", "")
users = Hello.Accounts.list_users(page: page, search: search)
render(conn, :index, users: users)
end
2.4 表单参数 #
elixir
def create(conn, %{"user" => user_params}) do
case Hello.Accounts.create_user(user_params) do
{:ok, user} ->
redirect(conn, to: ~p"/users/#{user.id}")
{:error, changeset} ->
render(conn, :new, changeset: changeset)
end
end
2.5 JSON参数 #
elixir
def create(conn, params) do
case Hello.Accounts.create_user(params) do
{:ok, user} ->
json(conn, %{data: user})
{:error, changeset} ->
json(conn, %{errors: format_errors(changeset)})
end
end
三、请求头 #
3.1 获取请求头 #
elixir
def show(conn, _params) do
content_type = Plug.Conn.get_req_header(conn, "content-type")
authorization = Plug.Conn.get_req_header(conn, "authorization")
user_agent = Plug.Conn.get_req_header(conn, "user-agent")
json(conn, %{
content_type: content_type,
authorization: authorization,
user_agent: user_agent
})
end
3.2 所有请求头 #
elixir
def headers(conn, _params) do
headers = Enum.into(conn.req_headers, %{})
json(conn, headers)
end
四、Cookie #
4.1 获取Cookie #
elixir
def show(conn, _params) do
session_id = conn.cookies["session_id"]
preferences = conn.cookies["preferences"]
json(conn, %{
session_id: session_id,
preferences: preferences
})
end
4.2 设置Cookie #
elixir
def set_preference(conn, %{"theme" => theme}) do
conn
|> put_resp_cookie("theme", theme, max_age: 365 * 24 * 60 * 60)
|> json(%{theme: theme})
end
4.3 删除Cookie #
elixir
def clear_preference(conn, _params) do
conn
|> delete_resp_cookie("theme")
|> json(%{message: "Preference cleared"})
end
五、Session #
5.1 获取Session #
elixir
def show(conn, _params) do
user_id = Plug.Conn.get_session(conn, :user_id)
user = Hello.Accounts.get_user(user_id)
render(conn, :show, user: user)
end
5.2 设置Session #
elixir
def create(conn, %{"email" => email, "password" => password}) do
case Hello.Accounts.authenticate_user(email, password) do
{:ok, user} ->
conn
|> Plug.Conn.put_session(:user_id, user.id)
|> put_flash(:info, "Logged in successfully")
|> redirect(to: ~p"/")
{:error, _} ->
conn
|> put_flash(:error, "Invalid credentials")
|> render(:new)
end
end
5.3 清除Session #
elixir
def delete(conn, _params) do
conn
|> Plug.Conn.clear_session()
|> put_flash(:info, "Logged out successfully")
|> redirect(to: ~p"/")
end
六、响应类型 #
6.1 HTML响应 #
elixir
def index(conn, _params) do
users = Hello.Accounts.list_users()
render(conn, :index, users: users)
end
6.2 JSON响应 #
elixir
def show(conn, %{"id" => id}) do
user = Hello.Accounts.get_user!(id)
json(conn, %{data: user})
end
6.3 文本响应 #
elixir
def health(conn, _params) do
text(conn, "OK")
end
6.4 文件下载 #
elixir
def download(conn, %{"id" => id}) do
file = Hello.Files.get_file!(id)
conn
|> put_resp_content_type(file.content_type)
|> send_download({:binary, file.content}, filename: file.name)
end
6.5 无内容响应 #
elixir
def delete(conn, %{"id" => id}) do
{:ok, _} = Hello.Accounts.delete_user(id)
send_resp(conn, :no_content, "")
end
七、响应头 #
7.1 设置响应头 #
elixir
def show(conn, %{"id" => id}) do
user = Hello.Accounts.get_user!(id)
conn
|> put_resp_header("x-request-id", generate_request_id())
|> put_resp_header("cache-control", "public, max-age=3600")
|> put_resp_header("x-content-type-options", "nosniff")
|> render(:show, user: user)
end
7.2 CORS头 #
elixir
def index(conn, _params) do
conn
|> put_resp_header("access-control-allow-origin", "*")
|> put_resp_header("access-control-allow-methods", "GET, POST, PUT, DELETE")
|> put_resp_header("access-control-allow-headers", "content-type, authorization")
|> json(%{data: []})
end
7.3 安全头 #
elixir
pipeline :browser do
plug :put_secure_browser_headers
end
默认安全头:
text
x-content-type-options: nosniff
x-download-options: noopen
x-frame-options: SAMEORIGIN
x-permitted-cross-domain-policies: none
x-xss-protection: 1; mode=block
八、状态码 #
8.1 成功状态码 #
elixir
def create(conn, params) do
case Hello.Accounts.create_user(params) do
{:ok, user} ->
conn
|> put_status(:created)
|> json(%{data: user})
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{errors: format_errors(changeset)})
end
end
8.2 错误状态码 #
elixir
def show(conn, %{"id" => id}) do
case Hello.Accounts.get_user(id) do
nil ->
conn
|> put_status(:not_found)
|> json(%{error: "User not found"})
user ->
json(conn, %{data: user})
end
end
8.3 重定向状态码 #
elixir
def redirect_permanent(conn, _params) do
conn
|> put_status(:moved_permanently)
|> redirect(to: ~p"/new-path")
end
def redirect_temporary(conn, _params) do
conn
|> put_status(:found)
|> redirect(to: ~p"/temp-path")
end
九、内容协商 #
9.1 Accepts Plug #
elixir
pipeline :api do
plug :accepts, ["json"]
end
pipeline :browser do
plug :accepts, ["html"]
end
9.2 多格式支持 #
elixir
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :api do
plug :accepts, ["json", "xml"]
end
scope "/api", HelloWeb.Api do
pipe_through :api
resources "/users", UserController
end
end
elixir
defmodule HelloWeb.Api.UserController do
use HelloWeb, :controller
def index(conn, _params) do
users = Hello.Accounts.list_users()
case get_format(conn) do
"json" -> json(conn, %{data: users})
"xml" -> render_xml(conn, users)
end
end
defp render_xml(conn, users) do
xml = """
<?xml version="1.0" encoding="UTF-8"?>
<users>
#{Enum.map(users, &user_xml/1)}
</users>
"""
conn
|> put_resp_content_type("application/xml")
|> send_resp(200, xml)
end
defp user_xml(user) do
"<user><id>#{user.id}</id><name>#{user.name}</name></user>"
end
end
十、文件上传 #
10.1 处理上传 #
elixir
def upload(conn, %{"file" => upload}) do
%Plug.Upload{
filename: filename,
content_type: content_type,
path: temp_path
} = upload
case save_file(temp_path, filename) do
{:ok, file_path} ->
json(conn, %{path: file_path, filename: filename})
{:error, reason} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: reason})
end
end
defp save_file(temp_path, filename) do
upload_dir = Application.app_dir(:hello, "priv/uploads")
File.mkdir_p(upload_dir)
dest_path = Path.join(upload_dir, filename)
File.cp(temp_path, dest_path)
{:ok, dest_path}
end
10.2 多文件上传 #
elixir
def upload_multiple(conn, %{"files" => files}) do
results =
files
|> Enum.map(fn upload ->
%Plug.Upload{filename: filename, path: temp_path} = upload
save_file(temp_path, filename)
end)
json(conn, %{results: results})
end
10.3 文件验证 #
elixir
def upload(conn, %{"file" => upload}) do
with :ok <- validate_size(upload),
:ok <- validate_type(upload) do
save_and_respond(conn, upload)
else
{:error, reason} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: reason})
end
end
defp validate_size(%Plug.Upload{path: path}) do
case File.stat(path) do
{:ok, %{size: size}} when size <= 10_000_000 -> :ok
_ -> {:error, "File too large (max 10MB)"}
end
end
defp validate_type(%Plug.Upload{content_type: type}) do
allowed = ["image/jpeg", "image/png", "image/gif", "application/pdf"]
if type in allowed do
:ok
else
{:error, "Invalid file type"}
end
end
十一、流式响应 #
11.1 流式数据 #
elixir
def stream(conn, _params) do
conn
|> put_resp_content_type("text/plain")
|> send_chunked(200)
|> stream_data()
end
defp stream_data(conn) do
Enum.reduce(1..100, conn, fn i, conn ->
chunk(conn, "Line #{i}\n")
end)
end
11.2 Server-Sent Events #
elixir
def sse(conn, _params) do
conn
|> put_resp_content_type("text/event-stream")
|> send_chunked(200)
|> send_events()
end
defp send_events(conn) do
Enum.reduce(1..100, conn, fn i, conn ->
event = "data: Message #{i}\n\n"
chunk(conn, event)
end)
end
十二、总结 #
12.1 请求处理 #
| 操作 | 函数 |
|---|---|
| 获取参数 | conn.params |
| 获取请求头 | get_req_header/2 |
| 获取Cookie | conn.cookies |
| 获取Session | get_session/2 |
12.2 响应生成 #
| 操作 | 函数 |
|---|---|
| HTML响应 | render/3 |
| JSON响应 | json/2 |
| 文本响应 | text/2 |
| 文件下载 | send_download/3 |
| 状态码 | put_status/2 |
| 响应头 | put_resp_header/3 |
12.3 下一步 #
现在你已经了解了请求与响应,接下来让我们学习 视图基础,深入了解视图和模板!
最后更新:2026-03-28