Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 588 lines (510 sloc) 20.443 kB
7fdc54a @davide HTML5 Web Sockets support.
davide authored
1 %%%----------------------------------------------------------------------
2 %%% File : yaws_websockets.erl
bd0bf89 @klacke no utf8 in the author name
authored
3 %%% Author : Davide Marques <nesrait@gmail.com>
455578a @vinoski major trailing whitespace cleanup
vinoski authored
4 %%% Purpose :
bd0bf89 @klacke no utf8 in the author name
authored
5 %%% Created : 18 Dec 2009 by Davide Marques <nesrait@gmail.com>
455578a @vinoski major trailing whitespace cleanup
vinoski authored
6 %%% Modified:
7fdc54a @davide HTML5 Web Sockets support.
davide authored
7 %%%----------------------------------------------------------------------
8
9 -module(yaws_websockets).
10 -author('nesrait@gmail.com').
11
12 -include("../include/yaws.hrl").
13 -include("../include/yaws_api.hrl").
14 -include("yaws_debug.hrl").
15
16 -include_lib("kernel/include/file.hrl").
17
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
18 -define(MAX_PAYLOAD, 16777216). %16MB
19
20 %% API
21 -export([start/3, send/2]).
22
23 %% Exported for spawn
24 -export([receive_control/4]).
25
26 start(Arg, CallbackMod, Opts) ->
27 SC = get(sc),
28 CliSock = Arg#arg.clisock,
29 PrepdOpts = preprocess_opts(Opts),
30 OwnerPid = spawn(?MODULE, receive_control,
31 [Arg, SC, CallbackMod, PrepdOpts]),
32 CliSock = Arg#arg.clisock,
33 case SC#sconf.ssl of
34 undefined ->
35 inet:setopts(CliSock, [{packet, raw}, {active, once}]),
36 TakeOverResult =
37 gen_tcp:controlling_process(CliSock, OwnerPid);
38 _ ->
39 ssl:setopts(CliSock, [{packet, raw}, {active, once}]),
40 TakeOverResult =
41 ssl:controlling_process(CliSock, OwnerPid)
42 end,
43 case TakeOverResult of
44 ok ->
45 OwnerPid ! ok,
46 exit(normal);
47 {error, Reason} ->
48 OwnerPid ! {error, Reason},
49 exit({websocket, Reason})
50 end.
51
52 send(#ws_state{sock=Socket, vsn=ProtoVsn}, {Type, Data}) ->
53 DataFrame = frame(ProtoVsn, Type, Data),
54 case Socket of
55 {sslsocket,_,_} ->
56 ssl:send(Socket, DataFrame);
57 _ ->
58 gen_tcp:send(Socket, DataFrame)
59 end;
60
61 send(Pid, {Type, Data}) ->
62 Pid ! {send, {Type, Data}}.
63
64 preprocess_opts(GivenOpts) ->
65 Fun = fun({Key, Default}, Opts) ->
66 case lists:keyfind(Key, 1, Opts) of
67 false ->
68 [{Key, Default}|Opts];
69 _ -> Opts
70 end
71 end,
72 Defaults = [{origin, any},
73 {callback, basic}],
74 lists:foldl(Fun, GivenOpts, Defaults).
75
76 receive_control(Arg, SC, CallbackMod, Opts) ->
77 receive
78 ok ->
79 handshake(Arg, SC, CallbackMod, Opts);
80 {error, Reason} ->
81 exit(Reason)
82 end.
83
84 handshake(Arg, SC, CallbackMod, Opts) ->
7fdc54a @davide HTML5 Web Sockets support.
davide authored
85 CliSock = Arg#arg.clisock,
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
86 OriginOpt = lists:keyfind(origin, 1, Opts),
87 Origin = get_origin_header(Arg#arg.headers),
88 case origin_check(Origin, OriginOpt) of
89 {error, Error} ->
90 error_logger:error_msg(Error),
91 exit({error, Error});
92 ok ->
93 ProtocolVersion = ws_version(Arg#arg.headers),
94 Protocol = get_protocol_header(Arg#arg.headers),
95 Host = (Arg#arg.headers)#headers.host,
96 {abs_path, Path} = (Arg#arg.req)#http_request.path,
97
98 WebSocketLocation =
99 case SC#sconf.ssl of
6e96a14 @klacke indendation cleanup
authored
100 undefined -> "ws://" ++ Host ++ Path;
101 _ -> "wss://" ++ Host ++ Path
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
102 end,
103
104 Handshake = handshake(ProtocolVersion, Arg, CliSock,
4ebda22 @vinoski minor code cleanup
vinoski authored
105 WebSocketLocation, Origin, Protocol),
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
106 gen_tcp:send(CliSock, Handshake), % TODO: use the yaws way of
107 % supporting normal
108 % and ssl sockets
109 {callback, CallbackType} = lists:keyfind(callback, 1, Opts),
110 WSState = #ws_state{sock = CliSock,
111 vsn = ProtocolVersion,
112 frag_type = none},
113 CallbackState = case CallbackType of
114 basic ->
115 {none, <<>>};
116 {advanced, InitialState} ->
117 InitialState
118 end,
119 loop(CallbackMod, WSState, CallbackState, CallbackType)
120 end.
121
122 origin_check(_Origin, {origin, any}) ->
123 ok;
124 origin_check(Actual, {origin, _Expected=Actual}) ->
125 ok;
126 origin_check(Actual, {origin, Expected}) ->
127 Error = io_lib:format("Expected origin ~p but found ~p.",
128 [Expected, Actual]),
129 {error, Error}.
130
131 handshake(8, Arg, _CliSock, _WebSocketLocation, _Origin, _Protocol) ->
132 Key = get_nonce_header(Arg#arg.headers),
133 AcceptHash = hash_nonce(Key),
134 ["HTTP/1.1 101 Switching Protocols\r\n",
135 "Upgrade: websocket\r\n",
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
136 "Connection: Upgrade\r\n",
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
137 "Sec-WebSocket-Accept: ", AcceptHash , "\r\n",
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
138 "\r\n"].
139
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
140 loop(CallbackMod, #ws_state{sock=Socket}=WSState, CallbackState, CallbackType) ->
141 receive
142 {send, {Type, Data}} ->
143 send(WSState, {Type, Data}),
144 loop(CallbackMod, WSState, CallbackState, CallbackType);
145 {tcp, Socket, FirstPacket} ->
146 FrameInfos = unframe_active_once(WSState, FirstPacket),
147 case CallbackType of
148 basic ->
149 {BasicMessages, NewCallbackState} =
150 basic_messages(FrameInfos, CallbackState),
151 CallbackResults = lists:map(
152 fun(M) ->
153 CallbackMod:handle_message(M)
154 end,
155 BasicMessages),
156 lists:map(handle_result_fun(WSState), CallbackResults);
157 {advanced,_} ->
158 NewCallbackState = lists:foldl(
159 do_callback_fun(WSState, CallbackMod),
160 CallbackState,
161 FrameInfos)
162 end,
163 Last = lists:last(FrameInfos),
164 NewWSState = Last#ws_frame_info.ws_state,
165 loop(CallbackMod, NewWSState, NewCallbackState, CallbackType);
166 {tcp_closed, Socket} ->
167 ok;
168 _Any ->
169 loop(CallbackMod, WSState, CallbackState, CallbackType)
170 end.
171
172 handle_result_fun(WSState) ->
173 fun(Result) ->
174 case Result of
175 {reply, {Type, Data}} ->
176 send(WSState, {Type, Data});
177 noreply ->
178 ok;
179 {close, Reason} ->
180 exit(Reason)
181
182 end
183 end.
184
185 do_callback_fun(WSState, CallbackMod) ->
186 fun(FrameInfo, CallbackState) ->
187 case CallbackMod:handle_message(FrameInfo, CallbackState) of
188 {reply, {Type, Data}, NewCallbackState} ->
189 send(WSState, {Type, Data}),
190 NewCallbackState;
191 {noreply, NewCallbackState} ->
192 NewCallbackState;
193 {close, Reason} ->
194 exit(Reason)
195 end
196 end.
197
198 basic_messages(FrameInfos, {FragType, FragAcc}) ->
199 {Messages, NewFragType, NewFragAcc}
200 = lists:foldl(fun handle_message/2, {[], FragType, FragAcc}, FrameInfos),
201 {Messages, {NewFragType, NewFragAcc}}.
202
203 %% start of a fragmented message
204 handle_message(#ws_frame_info{fin=0,
205 opcode=FragType,
206 data=Data},
207 {Messages, none, <<>>}) ->
208 {Messages, FragType, Data};
209
210 %% non-final continuation of a fragmented message
211 handle_message(#ws_frame_info{fin=0,
212 data=Data,
213 opcode=continuation},
214 {Messages, FragType, FragAcc}) ->
215 {Messages, FragType, <<FragAcc/binary,Data/binary>>};
216
217 %% end of text fragmented message
218 handle_message(#ws_frame_info{fin=1,
219 opcode=continuation,
220 data=Data},
221 {Messages, text, FragAcc}) ->
222 Unfragged = <<FragAcc/binary, Data/binary>>,
223 NewMessage = {text, Unfragged},
224 {[NewMessage | Messages], none, <<>>};
225
226 %% unfragmented text message
227 handle_message(#ws_frame_info{opcode=text, data=Data},
228 {Messages, none, <<>>}) ->
229 NewMessage = {text, Data},
230 {[NewMessage | Messages], none, <<>>};
231
232 %% end of binary fragmented message
233 handle_message(#ws_frame_info{fin=1,
234 opcode=continuation,
235 data=Data},
236 {Messages, binary, FragAcc}) ->
237 Unfragged = <<FragAcc/binary, Data/binary>>,
238 NewMessage = {binary, Unfragged},
239 {[NewMessage|Messages], none, <<>>};
240
241 handle_message(#ws_frame_info{opcode=binary,
242 data=Data},
243 {Messages, none, <<>>}) ->
244 NewMessage = {binary, Data},
245 {[NewMessage|Messages], none, <<>>};
246
247 handle_message(#ws_frame_info{opcode=ping,
248 data=Data,
249 ws_state=State},
250 Acc) ->
251 send(State, {pong, Data}),
252 Acc;
253
254 handle_message(#ws_frame_info{opcode=pong}, Acc) ->
255 %% A response to an unsolicited pong frame is not expected.
256 %% http://tools.ietf.org/html/\
257 %% draft-ietf-hybi-thewebsocketprotocol-08#section-4
258 Acc;
259
260 handle_message(#ws_frame_info{}, Acc) ->
261 Acc.
262
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
263 ws_version(Headers) ->
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
264 VersionVal = query_header("sec-websocket-version", Headers),
265 case VersionVal of
266 "8" -> 8;
267 "13" -> 8 % treat 13 like 8. Right now 13 support is as good as that
268 % of 8, according to autobahn 0.4.3
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
269 end.
270
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
271 buffer(Socket, Len, Buffered) ->
272 case Buffered of
273 <<_Expected:Len/binary>> = Return -> % exactly enough
274 %% debug(val, {buffering, "got:", Len}),
275 Return;
276 <<_Expected:Len/binary,_Extra/binary>> = Return-> % more than expected
277 %% debug(val, {buffering, "got:", Len, "and more!"}),
278 Return;
279 _ ->
280 %% not enough
281 %% debug(val, {buffering, "need:", Len, "waiting for more..."}),
282 %% TODO: take care of ssl sockets
283 Needed = Len - binary_length(Buffered),
284 {ok, More} = gen_tcp:recv(Socket, Needed),
285 <<Buffered/binary, More/binary>>
7fdc54a @davide HTML5 Web Sockets support.
davide authored
286 end.
287
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
288 binary_length(<<>>) ->
289 0;
290 binary_length(<<_First:1/binary, Rest/binary>>) ->
291 1 + binary_length(Rest).
292
293
294 checks(Unframed) ->
295 check_reserved_bits(Unframed).
296
297 check_control_frame(Len, Opcode, Fin) ->
298 if
299 (Len > 125) and (Opcode > 7) ->
300 %% http://tools.ietf.org/html/\
301 %% draft-ietf-hybi-thewebsocketprotocol-08#section-4.5
302 {fail_connection, "control frame > 125 bytes"};
303 (Fin == 0) and (Opcode > 7) ->
304 {fail_connection, "control frame may not be fragmented"};
305 true ->
306 ok
307 end.
308
309 %% no extensions are supported yet.
310 %% http://tools.ietf.org/html/\
311 %% draft-ietf-hybi-thewebsocketprotocol-08#section-4.2
312 check_reserved_bits(Unframed = #ws_frame_info{rsv=0}) ->
313 check_utf8(Unframed);
314 check_reserved_bits(#ws_frame_info{rsv=RSV}) ->
315 {fail_connection,
316 "rsv bits were " ++ integer_to_list(RSV) ++ " but should be unset."}.
317
318 %% http://www.erlang.org/doc/apps/stdlib/unicode_usage.html#id191467
319 %% Heuristic identification of UTF-8
0944464 @vinoski minor cleanup
vinoski authored
320 check_utf8(Unframed = #ws_frame_info{opcode=text, data=Bin})
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
321 when is_binary(Bin) ->
322 case unicode:characters_to_binary(Bin,utf8,utf8) of
323 Bin ->
324 Unframed;
325 _ ->
326 {fail_connection, "not valid utf-8."}
327 end;
328 check_utf8(Unframed) ->
329 check_reserved_opcode(Unframed).
330
331 check_reserved_opcode(#ws_frame_info{opcode = undefined}) ->
332 {fail_connection, "Reserved opcode."};
333 check_reserved_opcode(Unframed) ->
334 Unframed.
335
336
337 ws_frame_info(#ws_state{sock=Socket},
338 <<Fin:1, Rsv:3, Opcode:4, Masked:1, Len1:7, Rest/binary>>) ->
339 case check_control_frame(Len1, Opcode, Fin) of
340 ok ->
341 {ws_frame_info_secondary, Length, MaskingKey, Payload, Excess}
342 = ws_frame_info_secondary(Socket, Len1, Rest),
343 FrameInfo = #ws_frame_info{fin=Fin,
0944464 @vinoski minor cleanup
vinoski authored
344 rsv=Rsv,
345 opcode=opcode_to_atom(Opcode),
346 masked=Masked,
347 masking_key=MaskingKey,
348 length=Length,
349 payload=Payload},
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
350 {FrameInfo, Excess};
351 Other ->
352 Other
353 end;
354
355 ws_frame_info(State = #ws_state{sock=Socket}, FirstPacket) ->
356 ws_frame_info(State, buffer(Socket, 2,FirstPacket)).
357
358 ws_frame_info_secondary(Socket, Len1, Rest) ->
359 case Len1 of
360 126 ->
361 <<Len:16, MaskingKey:4/binary, Rest2/binary>> =
362 buffer(Socket, 6, Rest);
363 127 ->
364 <<Len:64, MaskingKey:4/binary, Rest2/binary>> =
365 buffer(Socket, 12, Rest);
366 Len ->
367 <<MaskingKey:4/binary, Rest2/binary>> = buffer(Socket, 4, Rest)
368 end,
369 if
370 Len > ?MAX_PAYLOAD ->
371 Error = io_lib:format(
372 "Payload length ~p longer than max allowed of ~p",
373 [Len, ?MAX_PAYLOAD]),
374 exit({error, Error});
375 true ->
376 <<Payload:Len/binary, Excess/binary>> = buffer(Socket, Len, Rest2),
377 {ws_frame_info_secondary, Len, MaskingKey, Payload, Excess}
378 end.
379
380 unframe_active_once(State, FirstPacket) ->
381 Frames = unframe(State, FirstPacket),
382 websocket_setopts(State, [{active, once}]),
383 Frames.
384
385 %% Returns all the WebSocket frames fully or partially contained in FirstPacket,
386 %% reading exactly as many more bytes from Socket as are needed to finish
387 %% unframing the last frame partially included in FirstPacket, if needed.
388 %%
389 %% The length of this list and depth of this recursion is limited by
390 %% the size of your socket receive buffer.
391 %%
392 %% -> { #ws_state, [#ws_frame_info,...,#ws_frame_info] }
393 unframe(_State, <<>>) ->
394 [];
395 unframe(State, FirstPacket) ->
396 case unframe_one(State, FirstPacket) of
397 {FrameInfo = #ws_frame_info{ws_state = NewState}, RestBin} ->
398 %% Every new recursion uses the #ws_state from the calling recursion.
399 [FrameInfo | unframe(NewState, RestBin)];
400 Fail ->
401 [Fail]
402 end.
403
404 %% -> {#ws_frame_info, RestBin} | {fail_connection, Reason}
405 unframe_one(State = #ws_state{vsn=8}, FirstPacket) ->
406 {FrameInfo = #ws_frame_info{}, RestBin} = ws_frame_info(State, FirstPacket),
407 Unmasked = mask(FrameInfo#ws_frame_info.masking_key,
408 FrameInfo#ws_frame_info.payload),
409 NewState = frag_state_machine(State, FrameInfo),
410 Unframed = FrameInfo#ws_frame_info{data = Unmasked,
411 ws_state = NewState},
412
413 case checks(Unframed) of
414 #ws_frame_info{} when is_record(NewState, ws_state) ->
415 {Unframed, RestBin};
416 #ws_frame_info{} when not is_record(NewState, ws_state) ->
417 NewState; %% pass back the error details
418 Fail ->
419 Fail
420 end.
421
422 websocket_setopts(#ws_state{sock=Socket={sslsocket,_,_}}, Opts) ->
423 ssl:setopts(Socket, Opts);
424 websocket_setopts(#ws_state{sock=Socket}, Opts) ->
425 inet:setopts(Socket, Opts).
426
427 is_control_op(Op) ->
428 atom_to_opcode(Op) > 7.
429
430 %% Unfragmented message
431 frag_state_machine(State = #ws_state{frag_type = none},
432 #ws_frame_info{fin = 1}) ->
433 State;
434
435 %% Beginning of fragmented text message
436 frag_state_machine(State = #ws_state{frag_type = none},
437 #ws_frame_info{fin = 0,
438 opcode = text}) ->
439 State#ws_state{frag_type = text};
440
441 %% Beginning of fragmented binary message
442 frag_state_machine(State = #ws_state{frag_type = none},
443 #ws_frame_info{fin = 0,
444 opcode = binary}) ->
445 State#ws_state{frag_type = binary};
446
447 %% Expecting text continuation
448 frag_state_machine(State = #ws_state{frag_type = text},
449 #ws_frame_info{fin = 0,
450 opcode = continuation}) ->
451 State;
452
453 %% Expecting binary continuation
454 frag_state_machine(State = #ws_state{frag_type = binary},
455 #ws_frame_info{fin = 0,
456 opcode = continuation}) ->
457 State;
458
459 %% End of fragmented text message
460 frag_state_machine(State = #ws_state{frag_type = text},
461 #ws_frame_info{fin = 1,
462 opcode = continuation}) ->
463 State#ws_state{frag_type = none};
464
465 %% End of fragmented binary message
466 frag_state_machine(State = #ws_state{frag_type = binary},
467 #ws_frame_info{fin = 1,
468 opcode = continuation}) ->
469 State#ws_state{frag_type = none};
470
471
472 frag_state_machine(State, #ws_frame_info{opcode = Op}) ->
473 IsControl = is_control_op(Op),
474 if
475 IsControl == true ->
476 %% Control message never changes fragmentation state
477 State;
478 true ->
479 %% Everything else is wrong
480 {error, "fragmentation rules violated"}
481 end.
482
483
484 opcode_to_atom(16#0) -> continuation;
485 opcode_to_atom(16#1) -> text;
486 opcode_to_atom(16#2) -> binary;
487 opcode_to_atom(16#8) -> close;
488 opcode_to_atom(16#9) -> ping;
489 opcode_to_atom(16#A) -> pong;
490 opcode_to_atom(_) -> undefined.
491
492 atom_to_opcode(continuation) -> 16#0;
493 atom_to_opcode(text) -> 16#1;
494 atom_to_opcode(binary) -> 16#2;
495 atom_to_opcode(close) -> 16#8;
496 atom_to_opcode(ping) -> 16#9;
497 atom_to_opcode(pong) -> 16#A.
498
499
500 frame(8, Type, Data) ->
501 %% FIN=true because we're not fragmenting.
502 %% OPCODE=1 for text
503 FirstByte = 128 bor atom_to_opcode(Type),
504 ByteList = binary_to_list(Data),
505 Length = length(ByteList),
506 if
507 Length < 126 ->
508 << FirstByte, 0:1, Length:7, Data:Length/binary >>;
509 Length =< 65535 ->
510 << FirstByte, 0:1, 126:7, Length:16, Data:Length/binary >>;
511 true ->
512 Defined = Length =< math:pow(2,64),
513 %% TODO: Is the correctness of this pow call
514 %% better than the speed and danger of not checking?
515 case Defined of
516 true ->
517 << FirstByte, 0:1, 127:7, Length:64, Data:Length/binary >>;
518 _ ->
519 undefined
520 end
521 end.
522
523
524 mask(MaskBin, Data) ->
525 list_to_binary(rmask(MaskBin, Data)).
526
527 %% unmask == mask. It's XOR of the four-byte masking key.
528 rmask(_,<<>>) ->
529 [<<>>];
530
531 rmask(MaskBin = <<Mask:4/integer-unit:8>>,
532 <<Data:4/integer-unit:8, Rest/binary>>) ->
533 Masked = Mask bxor Data,
534 MaskedRest = rmask(MaskBin, Rest),
535 [<<Masked:4/integer-unit:8>> | MaskedRest ];
536
537 rmask(<<Mask:3/integer-unit:8, _Rest/binary>>, <<Data:3/integer-unit:8>>) ->
538 Masked = Mask bxor Data,
539 [<<Masked:3/integer-unit:8>>];
540
541 rmask(<<Mask:2/integer-unit:8, _Rest/binary>>, <<Data:2/integer-unit:8>>) ->
542 Masked = Mask bxor Data,
543 [<<Masked:2/integer-unit:8>>];
544
545 rmask(<<Mask:1/integer-unit:8, _Rest/binary>>, <<Data:1/integer-unit:8>>) ->
546 Masked = Mask bxor Data,
547 [<<Masked:1/integer-unit:8>>].
7fdc54a @davide HTML5 Web Sockets support.
davide authored
548
549
550 %% Internal functions
5a70e44 @schemeway Reverted some names to ease merge.
schemeway authored
551 get_origin_header(Headers) ->
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
552 case query_header("origin", Headers) of
553 undefined -> query_header("sec-websocket-origin", Headers);
554 Origin -> Origin
555 end.
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
556
5a70e44 @schemeway Reverted some names to ease merge.
schemeway authored
557 get_protocol_header(Headers) ->
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
558 query_header("sec-websocket-protocol", Headers, "unknown").
559
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
560 get_nonce_header(Headers) ->
561 query_header("sec-websocket-key", Headers).
562
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
563 query_header(HeaderName, Headers) ->
564 query_header(HeaderName, Headers, undefined).
4ebda22 @vinoski minor code cleanup
vinoski authored
565
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
566 query_header(Header, #headers{other=L}, Default) ->
7fdc54a @davide HTML5 Web Sockets support.
davide authored
567 lists:foldl(fun({http_header,_,K0,_,V}, undefined) ->
568 K = case is_atom(K0) of
569 true ->
570 atom_to_list(K0);
571 false ->
572 K0
573 end,
574 case string:to_lower(K) of
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
575 Header ->
7fdc54a @davide HTML5 Web Sockets support.
davide authored
576 V;
577 _ ->
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
578 Default
7fdc54a @davide HTML5 Web Sockets support.
davide authored
579 end;
580 (_, Acc) ->
581 Acc
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
582 end, Default, L).
583
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
584 hash_nonce(Nonce) ->
585 Salted = Nonce ++ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
586 HashBin = crypto:sha(Salted),
587 base64:encode_to_string(HashBin).
Something went wrong with that request. Please try again.