web_socket: s/ws_origin/origin/
[sunshowers.git] / lib / sunshowers / web_socket.rb
blobbeace6fbe081bf236edf8d649c8da6ff0f0ee29d
1 # -*- encoding: binary -*-
2 module Sunshowers
4   # This module is intended to be used in subclasses of Rack::Request
5   # This is normally used inside Sunshowers::Request, but may be included
6   # in Rack::Request-derived subclasses such as Sinatra::Request.
7   module WebSocket
9     # Raised when processing of the current request is halted by the server.
10     # Rainbows! will not log errors or backtraces for EOFError-derived
11     # exceptions.  You may rescue Sunshowers::WebSocket::Quit in other
12     # middlewares for logging (and re-raise it afterwards).
13     class Quit < EOFError; end
15     # the pywebsocket/src/example/echo_client.py has a hard-coded dependency
16     # on the first 3 lines of the following response.
17     # Order and case both matter for the first 3 lines
18     HANDSHAKE_HEAD =
19       "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" \
20       "Upgrade: WebSocket\r\n" \
21       "Connection: Upgrade\r\n" \
22       "WebSocket-Origin: %s\r\n" \
23       "WebSocket-Location: %s\r\n" \
24       "%s\r\n"
26     # returns true if WebSockets may be used, false otherwise
27     def ws?
28       # yes, case sensitive header values
29       @env["HTTP_CONNECTION"] =~ /\AUpgrade\z/ &&
30           @env["HTTP_UPGRADE"] =~ /\AWebSocket\z/ &&
31           origin &&
32           @env["HTTP_HOST"]
33     end
35     # returns the contents of the Origin: header
36     def origin
37       @env["HTTP_ORIGIN"]
38     end unless method_defined?(:origin)
40     # performs the Web Sockets handshake, only call this once
41     # per application dispatch (unless you want to test error
42     # handling behavior in your clients).
43     def ws_handshake!
44       ws? or raise HandShakeError, "not a Web Sockets request"
45       # get the bare IO-ish object that does not encode, this can be
46       # a Rainbows::Fiber::IO object
47       wsp = ws_protocol
48       wsp = wsp ? "WebSocket-Protocol: #{wsp}\r\n" : ""
49       ws_io.to_io.write(sprintf(HANDSHAKE_HEAD, origin, ws_location, wsp))
50     end
52     # returns the underlying protocol used
53     def ws_protocol
54       protocol = @env["HTTP_WEBSOCKET_PROTOCOL"] or return
55       protocol =~ /\A[\x20-\x7e]+\z/ or
56         raise HandshakeError, "Invalid WebSocket-Protocol: #{protocol}"
57       protocol
58     end
60     # returns the ws://... or ws:/// URL
61     def ws_location
62       s = ws_scheme
63       rv = "#{s}://#{host}"
64       if (s == "wss" && port != 443) || (s == "ws" && port != 80)
65         rv << ":#{port}"
66       end
67       rv
68     end
70     # returns a Sunshowers::IO object
71     def ws_io
72       @ws_io ||= IO.new(@env["hack.io"])
73     end
75     # returns "ws" (unencrypted) or "wss" (SSL)
76     def ws_scheme
77       scheme == "https" ? "wss" : "ws"
78     end
80     # Terminates the Web Sockets request/response cycle and
81     # Rack application dispatch.
82     def ws_quit!
83       raise Quit, "done processing WebSocket request"
84     end
86   end
87 end