Initial commit
This commit is contained in:
commit
15b17d5338
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.gem
|
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# wsserver
|
||||
This gem is a WebSocket server for Ruby.
|
||||
|
||||
## Installation
|
||||
Download the latest release and run `gem install wsserver.gem` in the directory where you put it.
|
||||
|
||||
## Usage
|
||||
Put this code in a file:
|
||||
|
||||
```ruby
|
||||
server = WebsocketServer.new
|
||||
|
||||
while true
|
||||
Thread.new(server.accept) do |connection|
|
||||
# Your code here
|
||||
end
|
||||
end
|
||||
```
|
||||
To receive a message from the client do `connection.recv`.
|
||||
Note: connection.recv will return false if the connection was closed.
|
||||
To send a message to the client do `connection.send`.
|
||||
To close the connection do `connection.close`
|
||||
The WebSocketServer class accepts two named arguments as well:
|
||||
host: The host this server will run on. Defaults to localhost.
|
||||
port: The port the server will run on. Defaults to 4567.
|
3
lib/wsserver.rb
Normal file
3
lib/wsserver.rb
Normal file
@ -0,0 +1,3 @@
|
||||
require_relative "wsserver/version"
|
||||
require_relative "wsserver/websocket_connection"
|
||||
require_relative "wsserver/websocket_server"
|
86
lib/wsserver/websocket_connection.rb
Normal file
86
lib/wsserver/websocket_connection.rb
Normal file
@ -0,0 +1,86 @@
|
||||
class WebSocketConnection
|
||||
|
||||
def initialize(socket)
|
||||
@socket = socket
|
||||
end
|
||||
|
||||
# Recives a frame from the client.
|
||||
def recv
|
||||
puts @socket.inspect
|
||||
fin_and_opcode = @socket.read(1).bytes[0]
|
||||
fin = fin_and_opcode & 0b10000000
|
||||
opcode = fin_and_opcode & 0b00001111
|
||||
|
||||
case opcode
|
||||
when 1,0
|
||||
mask_and_length_indicator = @socket.read(1).bytes[0]
|
||||
length_indicator = mask_and_length_indicator & 0x7f
|
||||
|
||||
if length_indicator <= 125
|
||||
length = length_indicator
|
||||
elsif length_indicator == 126
|
||||
length = @socket.read(2).unpack("n")[0]
|
||||
else
|
||||
length = @socket.read(8).unpack("Q>")[0]
|
||||
end
|
||||
|
||||
mask = @socket.read(4).bytes
|
||||
|
||||
masked = @socket.read(length).bytes
|
||||
|
||||
i=0
|
||||
$data=[]
|
||||
masked.each do |byte|
|
||||
$data[i]=byte ^ mask[i % 4]
|
||||
i+=1
|
||||
end
|
||||
data=$data
|
||||
string=data.pack("c*")
|
||||
|
||||
if not fin
|
||||
string += parse_frame(@socket)
|
||||
end
|
||||
|
||||
puts "Got frame: #{string}"
|
||||
|
||||
return string
|
||||
when 8
|
||||
close
|
||||
return false
|
||||
else
|
||||
send("This server ony supports text data and the close frame")
|
||||
close
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def close()
|
||||
bytes = [0b10001000,0]
|
||||
data = bytes.pack("C*")
|
||||
puts data.inspect
|
||||
socket << data
|
||||
@socket.close
|
||||
end
|
||||
|
||||
# Sends a frame to the client.
|
||||
def send(string)
|
||||
return if string == false
|
||||
|
||||
puts "Sending frame: #{string}"
|
||||
|
||||
bytes = [0b10000001]
|
||||
size = string.bytesize
|
||||
|
||||
if size <= 125
|
||||
bytes += [size]
|
||||
elsif size < 2**16
|
||||
bytes += [126] + [size].pack("n").bytes
|
||||
else
|
||||
bytes += [127] + [size].pack("Q>").bytes
|
||||
end
|
||||
|
||||
bytes += string.bytes
|
||||
data = bytes.pack("C*")
|
||||
@socket << data
|
||||
end
|
||||
end
|
54
lib/wsserver/websocket_server.rb
Normal file
54
lib/wsserver/websocket_server.rb
Normal file
@ -0,0 +1,54 @@
|
||||
require 'socket'
|
||||
require 'digest/sha1'
|
||||
require 'base64'
|
||||
require_relative "websocket_connection.rb"
|
||||
|
||||
class WebSocketServer
|
||||
# Initalize a new WebSocketServer.
|
||||
def initialize(path: '/', port: 4567, host: 'localhost')
|
||||
@path=path
|
||||
@tcp_server = TCPServer.new(host, port)
|
||||
end
|
||||
|
||||
# Accept a WebSocket connection. Returns a new WebSocketConnection bound to the connection.
|
||||
def accept
|
||||
socket = @tcp_server.accept
|
||||
success=send_handshake(socket)
|
||||
return WebSocketConnection.new(socket) if success
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_handshake(socket)
|
||||
http_request = {}
|
||||
while (line = socket.gets) && (line != "\r\n")
|
||||
key, value = line.split(": ")
|
||||
value=value.chomp if value != nil
|
||||
http_request[key] = value
|
||||
end
|
||||
|
||||
if http_request.has_key? "Sec-WebSocket-Key"
|
||||
websocket_key = http_request["Sec-WebSocket-Key"]
|
||||
puts "Websocket handshake detected with key: #{ websocket_key.inspect }"
|
||||
else
|
||||
puts "Aborting non-websocket connection"
|
||||
socket << "HTTP/1.1 400 Bad Request\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n"
|
||||
socket.close
|
||||
return false
|
||||
end
|
||||
|
||||
response_key = Digest::SHA1.base64digest(websocket_key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
||||
puts "Responding to handshake with key: #{ response_key }"
|
||||
|
||||
socket << "HTTP/1.1 101 Switching Protocols\r\n" +
|
||||
"Upgrade: websocket\r\n" +
|
||||
"Connection: Upgrade\r\n"+
|
||||
"Sec-WebSocket-Accept: #{ response_key }\r\n" +
|
||||
"\r\n"
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
17
wsserver.gemspec
Normal file
17
wsserver.gemspec
Normal file
@ -0,0 +1,17 @@
|
||||
VERSION="1.0"
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "wsserver"
|
||||
spec.version=VERSION
|
||||
spec.authors = ["pjht"]
|
||||
spec.summary = "This gem is a WebSocket server for Ruby"
|
||||
|
||||
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
||||
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
||||
if spec.respond_to?(:metadata)
|
||||
spec.metadata["allowed_push_host"] = ""
|
||||
else
|
||||
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
||||
end
|
||||
|
||||
spec.files = ["lib/wsserver.rb","lib/wsserver/websocket_connection.rb","lib/wsserver/websocket_server.rb"]
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user