当前位置: 首页 > 工具软件 > Cowboy > 使用案例 >

Cowboy 用户指南 (十四) - Sending a response

皮嘉德
2023-12-01

发送一个响应

必须使用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处理推送请求的方式与处理普通请求相同:创建一个不同的进程,最终将向客户机发送响应。

 类似资料: