ftw 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +7 -8
- data/lib/ftw.rb +4 -0
- data/lib/ftw/agent.rb +203 -20
- data/lib/ftw/connection.rb +117 -63
- data/lib/ftw/cookies.rb +87 -0
- data/lib/ftw/crlf.rb +1 -1
- data/lib/ftw/dns.rb +14 -5
- data/lib/ftw/http/headers.rb +15 -1
- data/lib/ftw/http/message.rb +9 -1
- data/lib/ftw/namespace.rb +1 -0
- data/lib/ftw/pool.rb +50 -0
- data/lib/ftw/poolable.rb +19 -0
- data/lib/ftw/request.rb +92 -28
- data/lib/ftw/response.rb +179 -0
- data/lib/ftw/version.rb +1 -1
- data/lib/ftw/websocket.rb +194 -0
- data/lib/ftw/websocket/parser.rb +183 -0
- data/test/all.rb +16 -0
- data/test/ftw/crlf.rb +12 -0
- data/test/ftw/http/dns.rb +6 -0
- data/test/{net/ftw → ftw}/http/headers.rb +5 -5
- data/test/testing.rb +0 -9
- metadata +13 -26
- data/lib/net-ftw.rb +0 -1
- data/lib/net/ftw.rb +0 -5
- data/lib/net/ftw/agent.rb +0 -10
- data/lib/net/ftw/connection.rb +0 -296
- data/lib/net/ftw/connection2.rb +0 -247
- data/lib/net/ftw/crlf.rb +0 -6
- data/lib/net/ftw/dns.rb +0 -57
- data/lib/net/ftw/http.rb +0 -2
- data/lib/net/ftw/http/client.rb +0 -116
- data/lib/net/ftw/http/client2.rb +0 -80
- data/lib/net/ftw/http/connection.rb +0 -42
- data/lib/net/ftw/http/headers.rb +0 -122
- data/lib/net/ftw/http/machine.rb +0 -38
- data/lib/net/ftw/http/message.rb +0 -91
- data/lib/net/ftw/http/request.rb +0 -80
- data/lib/net/ftw/http/response.rb +0 -80
- data/lib/net/ftw/http/server.rb +0 -5
- data/lib/net/ftw/machine.rb +0 -59
- data/lib/net/ftw/namespace.rb +0 -6
- data/lib/net/ftw/protocol/tls.rb +0 -12
- data/lib/net/ftw/websocket.rb +0 -139
- data/test/net/ftw/crlf.rb +0 -12
- data/test/net/ftw/http/dns.rb +0 -6
data/lib/net/ftw/http/message.rb
DELETED
@@ -1,91 +0,0 @@
|
|
1
|
-
require "net/ftw/namespace"
|
2
|
-
require "net/ftw/http/headers"
|
3
|
-
|
4
|
-
# HTTP Message, RFC2616
|
5
|
-
class Net::FTW::HTTP::Message
|
6
|
-
include Net::FTW::CRLF
|
7
|
-
|
8
|
-
# The HTTP headers. See Net::FTW::HTTP::Headers
|
9
|
-
# RFC2616 5.3 - <http://tools.ietf.org/html/rfc2616#section-5.3>
|
10
|
-
attr_reader :headers
|
11
|
-
|
12
|
-
# The HTTP version. See VALID_VERSIONS for valid versions.
|
13
|
-
# This will always be a Numeric object.
|
14
|
-
# Both Request and Responses have version, so put it in the parent class.
|
15
|
-
attr_accessor :version
|
16
|
-
VALID_VERSIONS = [1.0, 1.1]
|
17
|
-
|
18
|
-
# A new HTTP Message. You probably won't use this class much.
|
19
|
-
# See RFC2616 section 4: <http://tools.ietf.org/html/rfc2616#section-4>
|
20
|
-
# See Request and Response.
|
21
|
-
public
|
22
|
-
def initialize
|
23
|
-
@headers = Net::FTW::HTTP::Headers.new
|
24
|
-
@body = nil
|
25
|
-
end # def initialize
|
26
|
-
|
27
|
-
# get a header value
|
28
|
-
public
|
29
|
-
def [](header)
|
30
|
-
return @headers[header]
|
31
|
-
end # def []
|
32
|
-
|
33
|
-
public
|
34
|
-
def []=(header, value)
|
35
|
-
@headers[header] = header
|
36
|
-
end # def []=
|
37
|
-
|
38
|
-
# See RFC2616 section 4.3: <http://tools.ietf.org/html/rfc2616#section-4.3>
|
39
|
-
public
|
40
|
-
def body=(message_body)
|
41
|
-
# TODO(sissel): if message_body is a string, set Content-Length header
|
42
|
-
# TODO(sissel): if it's an IO object, set Transfer-Encoding to chunked
|
43
|
-
# TODO(sissel): if it responds to each or appears to be Enumerable, then
|
44
|
-
# set Transfer-Encoding to chunked.
|
45
|
-
@body = message_body
|
46
|
-
end # def body=
|
47
|
-
|
48
|
-
public
|
49
|
-
def body
|
50
|
-
# TODO(sissel): verification todos follow...
|
51
|
-
# TODO(sissel): RFC2616 section 4.3 - if there is a message body
|
52
|
-
# then one of "Transfer-Encoding" *or* "Content-Length" MUST be present.
|
53
|
-
# otherwise, if neither header is present, no body is present.
|
54
|
-
# TODO(sissel): Responses to HEAD requests or those with status 1xx, 204,
|
55
|
-
# or 304 MUST NOT have a body. All other requests have a message body,
|
56
|
-
# even if that body is of zero length.
|
57
|
-
return @body
|
58
|
-
end # def body
|
59
|
-
|
60
|
-
# Does this message have a message body?
|
61
|
-
public
|
62
|
-
def body?
|
63
|
-
return @body.nil?
|
64
|
-
end # def body?
|
65
|
-
|
66
|
-
# Set the HTTP version. Must be a valid version. See VALID_VERSIONS.
|
67
|
-
public
|
68
|
-
def version=(ver)
|
69
|
-
# Accept string "1.0" or simply "1", etc.
|
70
|
-
ver = ver.to_f if !ver.is_a?(Float)
|
71
|
-
|
72
|
-
if !VALID_VERSIONS.include?(ver)
|
73
|
-
raise ArgumentError.new("#{self.class.name}#version = #{ver.inspect} is" \
|
74
|
-
"invalid. It must be a number, one of #{VALID_VERSIONS.join(", ")}")
|
75
|
-
end
|
76
|
-
@version = ver
|
77
|
-
end # def version=
|
78
|
-
|
79
|
-
# Serialize this Request according to RFC2616
|
80
|
-
# Note: There is *NO* trailing CRLF. This is intentional.
|
81
|
-
# The RFC defines:
|
82
|
-
# generic-message = start-line
|
83
|
-
# *(message-header CRLF)
|
84
|
-
# CRLF
|
85
|
-
# [ message-body ]
|
86
|
-
# Thus, the CRLF between header and body is not part of the header.
|
87
|
-
public
|
88
|
-
def to_s
|
89
|
-
return [start_line, @headers].join(CRLF)
|
90
|
-
end
|
91
|
-
end # class Net::FTW::HTTP::Message
|
data/lib/net/ftw/http/request.rb
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
require "net/ftw/namespace"
|
2
|
-
require "net/ftw/http/message"
|
3
|
-
require "addressable/uri" # gem addressable
|
4
|
-
require "uri" # ruby stdlib
|
5
|
-
require "http/parser" # gem http_parser.rb
|
6
|
-
|
7
|
-
# An HTTP Request.
|
8
|
-
#
|
9
|
-
# See RFC2616 section 5: <http://tools.ietf.org/html/rfc2616#section-5>
|
10
|
-
class Net::FTW::HTTP::Request < Net::FTW::HTTP::Message
|
11
|
-
include Net::FTW::CRLF
|
12
|
-
|
13
|
-
# The http method. Like GET, PUT, POST, etc..
|
14
|
-
# RFC2616 5.1.1 - <http://tools.ietf.org/html/rfc2616#section-5.1.1>
|
15
|
-
#
|
16
|
-
# Warning: this accessor obscures the ruby Kernel#method() method.
|
17
|
-
# I would like to call this 'verb', but my preference is first to adhere to
|
18
|
-
# RFC terminology. Further, ruby's stdlib Net::HTTP calls this 'method' as
|
19
|
-
# well (See Net::HTTPGenericRequest).
|
20
|
-
attr_accessor :method
|
21
|
-
|
22
|
-
# This is the Request-URI. Many people call this the 'path' of the request.
|
23
|
-
# RFC2616 5.1.2 - <http://tools.ietf.org/html/rfc2616#section-5.1.2>
|
24
|
-
attr_accessor :request_uri
|
25
|
-
|
26
|
-
# Lemmings. Everyone else calls Request-URI the 'path' - so I should too.
|
27
|
-
alias_method :path, :request_uri
|
28
|
-
|
29
|
-
public
|
30
|
-
def initialize(uri=nil)
|
31
|
-
super()
|
32
|
-
use_uri(uri) if !uri.nil?
|
33
|
-
@version = 1.1
|
34
|
-
end # def initialize
|
35
|
-
|
36
|
-
public
|
37
|
-
def use_uri(uri)
|
38
|
-
# Convert URI objects to Addressable::URI
|
39
|
-
uri = Addressable::URI.parse(uri.to_s) if uri.is_a?(URI)
|
40
|
-
|
41
|
-
# TODO(sissel): Use normalized versions of these fields?
|
42
|
-
# uri.host
|
43
|
-
# uri.port
|
44
|
-
# uri.scheme
|
45
|
-
# uri.path
|
46
|
-
# uri.password
|
47
|
-
# uri.user
|
48
|
-
@request_uri = uri.path
|
49
|
-
@headers.set("Host", uri.host)
|
50
|
-
|
51
|
-
# TODO(sissel): support authentication
|
52
|
-
end # def use_uri
|
53
|
-
|
54
|
-
# Set the method for this request. Usually something like "GET" or "PUT"
|
55
|
-
# etc. See <http://tools.ietf.org/html/rfc2616#section-5.1.1>
|
56
|
-
public
|
57
|
-
def method=(method)
|
58
|
-
# RFC2616 5.1.1 doesn't say the method has to be uppercase.
|
59
|
-
# It can be any 'token' besides the ones defined in section 5.1.1:
|
60
|
-
# The grammar for 'token' is:
|
61
|
-
# token = 1*<any CHAR except CTLs or separators>
|
62
|
-
# TODO(sissel): support section 5.1.1 properly. Don't upcase, but
|
63
|
-
# maybe upcase things that are defined in 5.1.1 like GET, etc.
|
64
|
-
@method = method.upcase
|
65
|
-
end # def method=
|
66
|
-
|
67
|
-
# Get the request line (first line of the http request)
|
68
|
-
# From the RFC: Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
69
|
-
#
|
70
|
-
# Note: I skip the trailing CRLF. See the to_s method where it is provided.
|
71
|
-
def request_line
|
72
|
-
return "#{method} #{request_uri} HTTP/#{version}"
|
73
|
-
end # def request_line
|
74
|
-
|
75
|
-
# Define the Message's start_line as request_line
|
76
|
-
alias_method :start_line, :request_line
|
77
|
-
# TODO(sissel): Methods to write:
|
78
|
-
# 1. Parsing a request, use HTTP::Parser from http_parser.rb
|
79
|
-
# 2. Building a request from a URI or Addressable::URI
|
80
|
-
end # class Net::FTW::HTTP::Request < Message
|
@@ -1,80 +0,0 @@
|
|
1
|
-
require "net/ftw/namespace"
|
2
|
-
require "net/ftw/http/message"
|
3
|
-
require "http/parser" # gem http_parser.rb
|
4
|
-
|
5
|
-
class Net::FTW::HTTP::Response < Net::FTW::HTTP::Message
|
6
|
-
# The HTTP version number
|
7
|
-
# See RFC2616 section 6.1: <http://tools.ietf.org/html/rfc2616#section-6.1>
|
8
|
-
attr_reader :version
|
9
|
-
|
10
|
-
# The http status code (RFC2616 6.1.1)
|
11
|
-
# See RFC2616 section 6.1.1: <http://tools.ietf.org/html/rfc2616#section-6.1.1>
|
12
|
-
attr_reader :status
|
13
|
-
|
14
|
-
# The reason phrase (RFC2616 6.1.1)
|
15
|
-
# See RFC2616 section 6.1.1: <http://tools.ietf.org/html/rfc2616#section-6.1.1>
|
16
|
-
attr_reader :reason
|
17
|
-
|
18
|
-
# Translated from the recommendations listed in RFC2616 section 6.1.1
|
19
|
-
# See RFC2616 section 6.1.1: <http://tools.ietf.org/html/rfc2616#section-6.1.1>
|
20
|
-
STATUS_REASON_MAP = {
|
21
|
-
100 => "Continue",
|
22
|
-
101 => "Switching Protocols",
|
23
|
-
200 => "OK",
|
24
|
-
201 => "Created",
|
25
|
-
202 => "Accepted",
|
26
|
-
203 => "Non-Authoritative Information",
|
27
|
-
204 => "No Content",
|
28
|
-
205 => "Reset Content",
|
29
|
-
206 => "Partial Content",
|
30
|
-
300 => "Multiple Choices",
|
31
|
-
301 => "Moved Permanently",
|
32
|
-
302 => "Found",
|
33
|
-
303 => "See Other",
|
34
|
-
304 => "Not Modified",
|
35
|
-
305 => "Use Proxy",
|
36
|
-
307 => "Temporary Redirect",
|
37
|
-
400 => "Bad Request",
|
38
|
-
401 => "Unauthorized",
|
39
|
-
402 => "Payment Required",
|
40
|
-
403 => "Forbidden",
|
41
|
-
404 => "Not Found",
|
42
|
-
405 => "Method Not Allowed",
|
43
|
-
406 => "Not Acceptable"
|
44
|
-
} # STATUS_REASON_MAP
|
45
|
-
|
46
|
-
public
|
47
|
-
def initialize
|
48
|
-
super
|
49
|
-
@reason = "" # Empty reason string by default. It is not required.
|
50
|
-
end # def initialize
|
51
|
-
|
52
|
-
# Set the status code
|
53
|
-
public
|
54
|
-
def status=(code)
|
55
|
-
code = code.to_i if !code.is_a?(Fixnum)
|
56
|
-
# TODO(sissel): Validate that 'code' is a 3 digit number
|
57
|
-
@status = code
|
58
|
-
|
59
|
-
# Attempt to set the reason if the status code has a known reason
|
60
|
-
# recommendation. If one is not found, default to the current reason.
|
61
|
-
@reason = STATUS_REASON_MAP.fetch(@status, @reason)
|
62
|
-
end # def status=
|
63
|
-
|
64
|
-
# Get the status-line string, like "HTTP/1.0 200 OK"
|
65
|
-
public
|
66
|
-
def status_line
|
67
|
-
# First line is 'Status-Line' from RFC2616 section 6.1
|
68
|
-
# Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
|
69
|
-
# etc...
|
70
|
-
return "HTTP-#{version} #{status} #{reason}"
|
71
|
-
end # def status_line
|
72
|
-
|
73
|
-
# Define the Message's start_line as status_line
|
74
|
-
alias_method :start_line, :status_line
|
75
|
-
|
76
|
-
# TODO(sissel): Methods to write:
|
77
|
-
# 1. Parsing a request, use HTTP::Parser from http_parser.rb
|
78
|
-
# 2. Building a request from a URI or Addressable::URI
|
79
|
-
end # class Net::FTW::HTTP::Response
|
80
|
-
|
data/lib/net/ftw/http/server.rb
DELETED
data/lib/net/ftw/machine.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
require "net/ftw/namespace"
|
2
|
-
|
3
|
-
# Protocol state machine
|
4
|
-
class Net::FTW::Machine
|
5
|
-
class InvalidTransition < StandardError
|
6
|
-
public
|
7
|
-
def initialize(instance, current_state, next_state)
|
8
|
-
@instance = instance
|
9
|
-
@current_state = current_state
|
10
|
-
@next_state = next_state
|
11
|
-
end
|
12
|
-
|
13
|
-
public
|
14
|
-
def to_s
|
15
|
-
return "Invalid transition: #{@current_state} => #{@next_state} on object: #{instance}"
|
16
|
-
end
|
17
|
-
end # class InvalidTransition
|
18
|
-
|
19
|
-
# Always the first state.
|
20
|
-
START = :start
|
21
|
-
ERROR = :error
|
22
|
-
|
23
|
-
public
|
24
|
-
def initialize
|
25
|
-
@state = START
|
26
|
-
end # def initialize
|
27
|
-
|
28
|
-
# Feed data input into this machine
|
29
|
-
public
|
30
|
-
def feed(input)
|
31
|
-
# Invoke whatever method of state we are in when we have data.
|
32
|
-
# like state_headers(input), etc
|
33
|
-
method("state_#{@state}")(input)
|
34
|
-
end # def feed
|
35
|
-
|
36
|
-
public
|
37
|
-
def state?(state)
|
38
|
-
return @state == state
|
39
|
-
end # def state?
|
40
|
-
|
41
|
-
public
|
42
|
-
def transition(new_state)
|
43
|
-
if valid_transition?(new_state)
|
44
|
-
@state = new_state
|
45
|
-
else
|
46
|
-
raise InvalidTransition.new(@state, new_state, self.class)
|
47
|
-
end
|
48
|
-
end # def transition
|
49
|
-
|
50
|
-
public
|
51
|
-
def valid_transition?(new_state)
|
52
|
-
allowed = TRANSITIONS[@state]
|
53
|
-
if allowed.is_a?(Array)
|
54
|
-
return allowed.include?(new_state)
|
55
|
-
else
|
56
|
-
return allowed == new_state
|
57
|
-
end
|
58
|
-
end # def valid_transition
|
59
|
-
end # class Net:FTW::Machine
|
data/lib/net/ftw/namespace.rb
DELETED
data/lib/net/ftw/protocol/tls.rb
DELETED
data/lib/net/ftw/websocket.rb
DELETED
@@ -1,139 +0,0 @@
|
|
1
|
-
require "net/ftw/namespace"
|
2
|
-
require "net/ftw/http/request"
|
3
|
-
require "net/ftw/http/response"
|
4
|
-
require "openssl"
|
5
|
-
require "base64" # stdlib
|
6
|
-
require "digest/sha1" # stdlib
|
7
|
-
|
8
|
-
# WebSockets, RFC6455.
|
9
|
-
#
|
10
|
-
# TODO(sissel): Find a comfortable way to make this websocket stuff
|
11
|
-
# both use HTTP::Connection for the HTTP handshake and also be usable
|
12
|
-
# from HTTP::Client
|
13
|
-
# TODO(sissel): Also consider SPDY and the kittens.
|
14
|
-
class Net::FTW::WebSocket
|
15
|
-
include Net::FTW::CRLF
|
16
|
-
|
17
|
-
WEBSOCKET_ACCEPT_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
18
|
-
|
19
|
-
# Protocol phases
|
20
|
-
# 1. tcp connect
|
21
|
-
# 2. http handshake (RFC6455 section 4)
|
22
|
-
# 3. websocket protocol
|
23
|
-
|
24
|
-
def initialize(uri)
|
25
|
-
uri = Addressable::URI.parse(uri.to_s) if [URI, String].include?(uri.class)
|
26
|
-
uri.port ||= 80
|
27
|
-
@uri = uri
|
28
|
-
|
29
|
-
@connection = Net::FTW::HTTP::Connection.new("#{@uri.host}:#{@uri.port}")
|
30
|
-
@key_nonce = generate_key_nonce
|
31
|
-
prepare
|
32
|
-
end # def initialize
|
33
|
-
|
34
|
-
private
|
35
|
-
def prepare
|
36
|
-
request = Net::FTW::HTTP::Request.new(@uri)
|
37
|
-
response = Net::FTW::HTTP::Response.new
|
38
|
-
|
39
|
-
# RFC6455 section 4.1:
|
40
|
-
# "2. The method of the request MUST be GET, and the HTTP version MUST
|
41
|
-
# be at least 1.1."
|
42
|
-
request.method = "GET"
|
43
|
-
request.version = 1.1
|
44
|
-
|
45
|
-
# RFC6455 section 4.2.1 bullet 3
|
46
|
-
request.headers.set("Upgrade", "websocket")
|
47
|
-
# RFC6455 section 4.2.1 bullet 4
|
48
|
-
request.headers.set("Connection", "Upgrade")
|
49
|
-
# RFC6455 section 4.2.1 bullet 5
|
50
|
-
request.headers.set("Sec-WebSocket-Key", @key_nonce)
|
51
|
-
# RFC6455 section 4.2.1 bullet 6
|
52
|
-
request.headers.set("Sec-WebSocket-Version", 13)
|
53
|
-
# RFC6455 section 4.2.1 bullet 7 (optional)
|
54
|
-
# The Origin header is optional for non-browser clients.
|
55
|
-
#request.headers.set("Origin", ...)
|
56
|
-
# RFC6455 section 4.2.1 bullet 8 (optional)
|
57
|
-
#request.headers.set("Sec-Websocket-Protocol", ...)
|
58
|
-
# RFC6455 section 4.2.1 bullet 9 (optional)
|
59
|
-
#request.headers.set("Sec-Websocket-Extensions", ...)
|
60
|
-
# RFC6455 section 4.2.1 bullet 10 (optional)
|
61
|
-
# TODO(sissel): Any other headers like cookies, auth headers, are allowed.
|
62
|
-
|
63
|
-
# TODO(sissel): This is starting to feel like not the best way to implement
|
64
|
-
# protocols.
|
65
|
-
@connection.on(@connection.class::CONNECTED) do |address|
|
66
|
-
@connection.write(request.to_s)
|
67
|
-
@connection.write(CRLF)
|
68
|
-
end
|
69
|
-
@connection.on(@connection.class::HEADERS_COMPLETE) do |version, status, headers|
|
70
|
-
puts :HEADERS
|
71
|
-
response.status = status
|
72
|
-
response.version = version
|
73
|
-
headers.each { |field, value| response.headers.add(field, value) }
|
74
|
-
|
75
|
-
# TODO(sissel): Respect redirects
|
76
|
-
|
77
|
-
if websocket_handshake_ok?(request, response)
|
78
|
-
@connection.on(@connection.class::MESSAGE_BODY) do |data|
|
79
|
-
websocket_read(data)
|
80
|
-
end
|
81
|
-
elsif response.status == 101
|
82
|
-
# WebSocket handshake failed. Bad headers or bad hash?
|
83
|
-
@connection.disconnect("Invalid WebSocket handshake response")
|
84
|
-
else
|
85
|
-
# Handle this http response normally, don't switch protocols
|
86
|
-
# Maybe this is a 302 redirect or something else
|
87
|
-
# TODO(sissel): handle the response normally
|
88
|
-
puts "Non-websocket response"
|
89
|
-
puts response.to_s
|
90
|
-
@connection.on(@connection.class::MESSAGE_BODY) do |data|
|
91
|
-
puts data
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end # @connection.on HEADERS_COMPLETE
|
95
|
-
@connection.run
|
96
|
-
end # def prepare
|
97
|
-
|
98
|
-
def websocket_read(data)
|
99
|
-
p :data => data
|
100
|
-
end # def websocket_read
|
101
|
-
|
102
|
-
private
|
103
|
-
def generate_key_nonce
|
104
|
-
# RFC6455 section 4.1 says:
|
105
|
-
# ---
|
106
|
-
# 7. The request MUST include a header field with the name
|
107
|
-
# |Sec-WebSocket-Key|. The value of this header field MUST be a
|
108
|
-
# nonce consisting of a randomly selected 16-byte value that has
|
109
|
-
# been base64-encoded (see Section 4 of [RFC4648]). The nonce
|
110
|
-
# MUST be selected randomly for each connection.
|
111
|
-
# ---
|
112
|
-
#
|
113
|
-
# It's not totally clear to me how cryptographically strong this random
|
114
|
-
# nonce needs to be, and if it does not need to be strong and it would
|
115
|
-
# benefit users who do not have ruby with openssl enabled, maybe just use
|
116
|
-
# rand() to generate this string.
|
117
|
-
#
|
118
|
-
# Thus, generate a random 16 byte string and encode i with base64.
|
119
|
-
# Array#pack("m") packs with base64 encoding.
|
120
|
-
return Base64.strict_encode64(OpenSSL::Random.random_bytes(16))
|
121
|
-
end # def generate_key_nonce
|
122
|
-
|
123
|
-
private
|
124
|
-
def websocket_handshake_ok?(request, response)
|
125
|
-
# See RFC6455 section 4.2.2
|
126
|
-
return false unless response.status == 101 # "Switching Protocols"
|
127
|
-
return false unless response.headers.get("upgrade") == "websocket"
|
128
|
-
return false unless response.headers.get("connection") == "Upgrade"
|
129
|
-
|
130
|
-
# Now verify Sec-WebSocket-Accept. It should be the SHA-1 of the
|
131
|
-
# Sec-WebSocket-Key (in base64) + WEBSOCKET_ACCEPT_UUID
|
132
|
-
expected = request.headers.get("Sec-WebSocket-Key") + WEBSOCKET_ACCEPT_UUID
|
133
|
-
expected_hash = Digest::SHA1.base64digest(expected)
|
134
|
-
return false unless response.headers.get("Sec-WebSocket-Accept") == expected_hash
|
135
|
-
|
136
|
-
return true
|
137
|
-
end # def websocket_handshake_ok
|
138
|
-
|
139
|
-
end # class Net::FTW::WebSocket
|