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

Cowboy 用户指南 (二十一) - Websocket handlers

严宏朗
2023-12-01

Websocket 处理程序

Websocket处理程序提供了一个升级到Websocket的HTTP/1.1连接的接口,并在Websocket连接上发送或接收帧。

由于Websocket连接是通过HTTP/1.1升级机制建立的,所以Websocket处理器需要能够首先接收升级的HTTP请求,然后切换到Websocket并接管连接。然后,它们可以接收或发送Websocket帧,处理传入的Erlang消息或关闭连接。

升级

当接收到请求时,调用init/2回调函数。要建立Websocket连接,你必须切换到cowboy_websocket模块:

init(Req, State) ->
    {cowboy_websocket, Req, State}.

Cowboy将立即执行Websocket握手。注意,如果客户端没有请求升级到Websocket,握手将失败。

此函数返回后,Req对象将不可用。正确执行Websocket处理程序所需的任何信息都必须保存在状态中。

子协议

客户端可以在sec-websocket-protocol报头中提供它支持的Websocket子协议列表。服务器必须选择其中一个并将其发送回客户机,否则握手将失败。

例如,客户端可以通过Websocket理解STOMP和MQTT,并提供报头:

sec-websocket-protocol: v12.stomp, mqtt

如果服务器只理解MQTT,它可以返回:

sec-websocket-protocol: mqtt

这个选择必须在init/2中完成。用法示例如下:

init(Req0, State) ->
    case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req0) of
        undefined ->
            {cowboy_websocket, Req0, State};
        Subprotocols ->
            case lists:keymember(<<"mqtt">>, 1, Subprotocols) of
                true ->
                    Req = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>,
                        <<"mqtt">>, Req0),
                    {cowboy_websocket, Req, State};
                false ->
                    Req = cowboy_req:reply(400, Req0),
                    {ok, Req, State}
            end
    end.

Post-upgrade初始化

Cowboy有单独的进程来处理连接和请求。因为Websocket接管了连接,所以Websocket协议处理和请求处理发生在不同的进程中。

这反映在Websocket处理程序的不同回调中。init/2回调函数从临时请求进程调用,websocket_回调函数从连接进程调用。

这意味着一些初始化不能从init/2中完成。任何需要当前pid或绑定到当前pid的操作都将无法正常工作。可以使用可选的websocket_init/1代替:

websocket_init(State) ->
    erlang:start_timer(1000, self(), <<"Hello!">>),
    {ok, State}.

所有的Websocket回调函数都有相同的返回值。这意味着我们可以在升级后立即向客户端发送帧:

websocket_init(State) ->
    {[{text, <<"Hello!">>}], State}.

接收帧

每当一个文本、二进制、ping或pong帧从客户端到达时,Cowboy将调用websocket_handle/2。

处理程序可以处理或忽略帧。它也可以发送帧回客户端或停止连接。

以下代码片段将回显接收到的任何文本帧,并忽略其他所有文本帧:

websocket_handle(Frame = {text, _}, State) ->
    {[Frame], State};
websocket_handle(_Frame, State) ->
    {ok, State}.

注意,ping和pong帧不需要管理员的操作,因为Cowboy会自动回复ping帧。它们仅供提供信息之用。

接收Erlang的信息

每当Erlang消息到达时,Cowboy将调用websocket_info/2。

处理程序可以处理或忽略消息。它也可以发送帧到客户端或停止连接。

下面的代码片段将日志消息转发给客户端,并忽略其他所有消息:

websocket_info({log, Text}, State) ->
    {[{text, Text}], State};
websocket_info(_Info, State) ->
    {ok, State}.

发送帧

所有websocket_回调函数共享返回值。它们可以向客户端发送0、1或多个帧。

不发送任何东西,只返回一个ok元组:

websocket_info(_Info, State) ->
    {ok, State}.

要发送一个帧,返回要发送的帧:

websocket_info(_Info, State) ->
    {[{text, <<"Hello!">>}], State}.

您可以发送任何类型的帧:text、binary、ping、pong或close frames

你可以在同一时间发送许多帧:

websocket_info(_Info, State) ->
    {[
        {text, "Hello"},
        {text, <<"world!">>},
        {binary, <<0:8000>>}
    ], State}.

它们是按给定的顺序发送的。

保持连接状态

Cowboy将自动响应客户端发送的ping帧。出于提供信息的目的,它们仍然被转发给处理程序,但不需要进一步的操作。

Cowboy自己不会发送ping帧。如果需要,处理程序可以这样做。在大多数情况下,更好的解决方案是让客户端处理ping。在处理程序中执行这一操作意味着为每个连接添加额外的计时器,对于需要处理大量连接的服务器来说,这可能会带来相当大的成本。

Cowboy可以配置为自动关闭空闲连接。强烈建议在这里配置一个超时,以避免进程停留的时间超过需要。

init/2回调函数可以设置连接的超时时间。例如,这将使Cowboy关闭空闲连接超过30秒:

init(Req, State) ->
    {cowboy_websocket, Req, State, #{
        idle_timeout => 30000}}.

该值一旦设置,就不能更改。默认值为60000。

限制帧大小

默认情况下Cowboy接受任何大小的帧。您应该根据处理程序可能处理的内容来限制大小。你可以通过init/2回调来做到这一点:

init(Req, State) ->
    {cowboy_websocket, Req, State, #{
        max_frame_size => 8000000}}.

缺乏限制是历史遗留问题。未来版本的Cowboy将会有一个更合理的违约。

节省内存

Websocket连接进程可以在回调函数返回后设置为hibernate。

只需将hibernate字段添加到返回的元组:

websocket_init(State) ->
    {[], State, hibernate}.

websocket_handle(_Frame, State) ->
    {[], State, hibernate}.

websocket_info(_Info, State) ->
    {[{text, <<"Hello!">>}], State, hibernate}.

强烈建议在编写处理程序时启用hibernate,因为这样可以大大减少内存使用。但是请注意,可以观察到CPU使用量或延迟的增加,特别是对于更繁忙的连接。

关闭连接

连接可以在任何时候关闭,要么告诉Cowboy停止它,要么发送一个close帧。

告诉Cowboy关闭连接,使用stop元组:

websocket_info(_Info, State) ->
    {stop, State}.

发送一个close帧将立即启动关闭Websocket连接。注意,当发送一个包含close帧的帧列表时,在close帧之后找到的任何帧都不会被发送。

下面的例子发送一个带有reason消息的close帧:

websocket_info(_Info, State) ->
    {[{close, 1000, <<"some-reason">>}], State}.
 类似资料: