Initial commit

This commit is contained in:
pjht 2017-09-11 07:37:59 -05:00
commit 15b17d5338
6 changed files with 186 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.gem

25
README.md Normal file
View 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
View File

@ -0,0 +1,3 @@
require_relative "wsserver/version"
require_relative "wsserver/websocket_connection"
require_relative "wsserver/websocket_server"

View 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

View 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
View 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