发送一个响应
必须使用Req对象发送响应。
Cowboy提供了两种不同的发送响应的方式:直接发送或通过流传递主体。响应报头和响应主体可以预先设置。一旦响应或流应答函数被调用,响应就被发送。
Cowboy还提供了一个发送文件的简化接口。它也只能发送文件的特定部分。
虽然每个请求只允许一个响应,但HTTP/2引入了一种机制,允许服务器推送与响应相关的额外资源。本章还描述了这个特性在Cowboy中的工作原理。
响应
Cowboy提供了三个函数来发送整个回复,这取决于您是否需要设置报头和正文。在任何情况下,Cowboy都将添加协议所需的任何报头(例如,总是发送日期报头)。
当你只需要设置状态码时,使用cowboy_req:reply/2:
Req = cowboy_req:reply(200, Req0).
当你需要同时设置响应报头时,使用cowboy_req:reply/3:
Req = cowboy_req:reply(303, #{
<<"location">> => <<"https://ninenines.eu">>
}, Req0).
注意报头名称必须总是小写的二进制。
当你还需要设置响应主体时,使用cowboy_req:reply/4:
Req = cowboy_req:reply(200, #{
<<"content-type">> => <<"text/plain">>
}, "Hello world!", Req0).
当响应有主体时,您应该始终设置content-type报头。但是,不需要设置content-length报头;Cowboy是自动的。
响应主体和报头值必须是binary或iolist。iolist是一个包含二进制、字符、字符串或其他iolist的列表。这允许你从不同的部分构建响应,而不需要做任何连接:
Title = "Hello world!",
Body = <<"Hats off!">>,
Req = cowboy_req:reply(200, #{
<<"content-type">> => <<"text/html">>
}, ["<html><head><title>", Title, "</title></head>",
"<body><p>", Body, "</p></body></html>"], Req0).
这种构造响应的方法比串联法更有效。在幕后,列表的每个元素都只是一个指针,这些指针在写入socket时直接使用。
流响应
Cowboy提供了两个用于初始化响应的函数,以及一个用于流化响应主体的附加函数。Cowboy将向响应添加任何所需的标题。
当你只需要设置状态码时,使用cowboy_req:stream_reply/2:
Req = cowboy_req:stream_reply(200, Req0),
cowboy_req:stream_body("Hello...", nofin, Req),
cowboy_req:stream_body("chunked...", nofin, Req),
cowboy_req:stream_body("world!!", fin, Req).
cowboy_req:stream_body/3的第二个参数指示该数据是否终止主体。使用fin作为最终标志,否则使用nofin。
此代码片段没有设置content-type报头。不建议这样做。所有带有主体的响应都应该具有content-type。报头可以预先设置,或者使用cowboy_req:stream_reply/3:
Req = cowboy_req:stream_reply(200, #{
<<"content-type">> => <<"text/html">>
}, Req0),
cowboy_req:stream_body("<html><head>Hello world!</head>", nofin, Req),
cowboy_req:stream_body("<body><p>Hats off!</p></body></html>", fin, Req).
HTTP提供了几种不同的方式来传输响应主体。Cowboy将根据HTTP版本以及请求和响应主头选择最合适的一个。
虽然不需要,但是如果提前知道的话,建议您在响应中设置content-length报头。这将确保选择最佳响应方法,并帮助客户了解何时完全接收到响应。
Cowboy还提供了一个发送响应trailers的功能。响应尾部在语义上与响应中发送的报头等价,只是它们是在最后发送的。这对于在响应主体完全生成之前无法生成的响应附加信息特别有用。
尾部字段必须在尾部标题中列出。没有列出的任何字段都可能被客户机或中介删除。
Req = cowboy_req:stream_reply(200, #{
<<"content-type">> => <<"text/html">>,
<<"trailer">> => <<"expires, content-md5">>
}, Req0),
cowboy_req:stream_body("<html><head>Hello world!</head>", nofin, Req),
cowboy_req:stream_body("<body><p>Hats off!</p></body></html>", nofin, Req),
cowboy_req:stream_trailers(#{
<<"expires">> => <<"Sun, 10 Dec 2017 19:13:47 GMT">>,
<<"content-md5">> => <<"c6081d20ff41a42ce17048ed1c0345e2">>
}, Req).
流以trailers结束。在发送trailers后不再可能发送数据。当流化主体时,你不能在设置fin标志后发送trailers。
预设响应报头
Cowboy提供了一些函数来设置响应报头,而不需要立即发送它们。它们存储在Req对象中,并在调用应答函数时作为响应的一部分发送。
设置响应报头:
Req = cowboy_req:set_resp_header(<<"allow">>, "GET", Req0).
报头名必须是小写的二进制。
请勿使用该功能设置cookie。更多信息请参考Cookies章节。
检查是否已经设置了响应报头:
cowboy_req:has_resp_header(<<"allow">>, Req).
如果报头被设置,则返回true,否则返回false。
要删除先前设置的响应报头:
Req = cowboy_req:delete_resp_header(<<"allow">>, Req0).
重写报头
由于Cowboy提供了设置响应报头和响应主体的不同方法,可能会发生冲突,所以理解当报头被设置两次时会发生什么是很重要的。
报头来自五个不同的来源:
- 特定于协议的头(例如HTTP/1.1的连接报头)
- 其他必需的报头(例如日期报头)
- 预设报头
- 给响应函数的报头
- 设置cookie报头
Cowboy不允许重写协议特定的报头。
在发送响应之前,Set-cookie报头总是被附加在报头列表的末尾。
给reply函数的报头将总是覆盖预设报头和必需的报头。如果在其中的两个或三个中找到一个报头,则选择reply函数中的一个报头,并删除其他报头。
类似地,预设报头将始终覆盖所需的报头。
为了说明这一点,请看下面的代码片段。默认情况下,Cowboy发送带有值“Cowboy”的服务器报头。我们可以重写它:
Req = cowboy_req:reply(200, #{
<<"server">> => <<"yaws">>
}, Req0).
预设响应主体
Cowboy提供了一些函数来设置响应主体,而不需要立即发送它。它存储在Req对象中,并在调用应答函数时发送。
设置响应主体:
Req = cowboy_req:set_resp_body("Hello world!", Req0).
检查是否已经设置了响应主体:
cowboy_req:has_resp_body(Req).
如果主体已设置且非空,则返回true,否则返回false。
只有当使用的回复函数为cowboy_req:reply/2或cowboy_req:reply/3时,才会发送预设的响应主体。
发送文件
Cowboy提供了一个发送文件的快捷方式。当使用cowboy_req:reply/4,或者预先设置响应报头时,你可以给Cowboy一个sendfile元组:
{sendfile, Offset, Length, Filename}
根据偏移量或长度的值,可以发送整个文件,或者只是其中的一部分。
即使是发送整个文件也需要这个长度。Cowboy在content-length报头中发送它。
在回复时发送文件:
Req = cowboy_req:reply(200, #{
<<"content-type">> => "image/png"
}, {sendfile, 0, 12345, "path/to/logo.png"}, Req0).
信息响应
Cowboy允许你发送信息响应。
信息性响应是状态码在100到199之间的响应。任何号码都可以在适当的回复之前发送。发送信息性响应并不会改变正确响应的行为,客户端应该会忽略他们不理解的任何信息性响应。
下面的代码片段发送了一个103信息响应,其中包含一些预计将出现在最终响应中的报头。
Req = cowboy_req:inform(103, #{
<<"link">> => <<"</style.css>; rel=preload; as=style, </script.js>; rel=preload; as=script">>
}, Req0).
Push
HTTP/2协议引入了推送与响应中发送的资源相关的资源的能力。为此,Cowboy提供了两个函数:cowboy_req:push/3,4。
Push仅对HTTP/2有效。如果协议不支持,Cowboy将自动忽略推送请求。
push函数必须在任何一个reply函数之前调用。否则将导致崩溃。
要推送资源,您需要提供与执行请求的客户机相同的信息。这包括HTTP方法、URI和任何必要的请求报头。
默认情况下,Cowboy只要求您提供资源的路径和请求报头。URI的其余部分取自当前请求(不包括查询字符串,设置为空),默认情况下该方法为GET。
下面的代码片段推送一个CSS文件,该文件在响应中被链接到:
cowboy_req:push("/static/style.css", #{
<<"accept">> => <<"text/css">>
}, Req0),
Req = cowboy_req:reply(200, #{
<<"content-type">> => <<"text/html">>
}, ["<html><head><title>My web page</title>",
"<link rel='stylesheet' type='text/css' href='/static/style.css'>",
"<body><p>Welcome to Erlang!</p></body></html>"], Req0).
要覆盖method, scheme, host, port或查询字符串,只需传入第四个参数。以下代码片段使用了不同的主机名:
cowboy_req:push("/static/style.css", #{
<<"accept">> => <<"text/css">>
}, #{host => <<"cdn.example.org">>}, Req),
推送的资源不一定是文件。只要推送请求是可缓存的、安全的,并且不包含主体,就可以推送资源。
在底层,Cowboy处理推送请求的方式与处理普通请求相同:创建一个不同的进程,最终将向客户机发送响应。