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