This commit is contained in:
pjht 2023-10-06 17:02:48 -05:00
parent 0224abf215
commit 1b1598e668
Signed by: pjht
GPG Key ID: CA239FC6934E6F3A
13 changed files with 466 additions and 431 deletions

12
Gemfile
View File

@ -1,6 +1,8 @@
source "https://rubygems.org"
# frozen_string_literal: true
gem "pry"
gem "web_gui", :path => "."
gem "dry-struct"
gem "dry-events"
source 'https://rubygems.org'
gem 'dry-events'
gem 'dry-struct'
gem 'pry'
gem 'web_gui', path: '.'

View File

@ -1,5 +1,7 @@
require "bundler/gem_tasks"
task :default => :build
# frozen_string_literal: true
require 'bundler/gem_tasks'
task default: :build
task :geminabox do
`gem inabox -o pkg/web_gui-*.gem`
`rm pkg/web_gui-*.gem`

View File

@ -1,11 +1,12 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require "bundler/setup"
require "web_gui"
require 'bundler/setup'
require 'web_gui'
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
require "pry"
require 'pry'
Pry.start

40
calc.rb
View File

@ -1,28 +1,30 @@
require "bundler/setup"
# frozen_string_literal: true
require 'bundler/setup'
Bundler.setup
require "web_gui"
$op=nil
$num1=nil
$num2=nil
app=WebGui::App.new("Calculator") {
opthash={:add=>"Add",:sub=>"Subtract",:mult=>"Multiply",:div=>"Divide"}
add_element(WebGui::TextField.new {|val| $num1=val.to_f})
add_element(WebGui::Menu.new(opthash) {|val| $op=val})
add_element(WebGui::TextField.new {|val| $num2=val.to_f})
res=nil
add_element(WebGui::ActionButton.new("Calculate") {
require 'web_gui'
$op = nil
$num1 = nil
$num2 = nil
app = WebGui::App.new('Calculator') do
opthash = { add: 'Add', sub: 'Subtract', mult: 'Multiply', div: 'Divide' }
add_element(WebGui::TextField.new { |val| $num1 = val.to_f })
add_element(WebGui::Menu.new(opthash) { |val| $op = val })
add_element(WebGui::TextField.new { |val| $num2 = val.to_f })
res = nil
add_element(WebGui::ActionButton.new('Calculate') do
case $op
when :add
result=$num1+$num2
result = $num1 + $num2
when :sub
result=$num1-$num2
result = $num1 - $num2
when :mult
result=$num1*$num2
result = $num1 * $num2
when :div
result=$num1.to_f/$num2
result = $num1.to_f / $num2
end
res.settext("Result:#{result}")
})
res=add_element(WebGui::Text.new("Result:"))
}
end)
res = add_element(WebGui::Text.new('Result:'))
end
app.run

View File

@ -1,69 +1,71 @@
# frozen_string_literal: true
class Object
def descendants
ObjectSpace.each_object(::Class).select {|klass| klass < self }
ObjectSpace.each_object(::Class).select { |klass| klass < self }
end
end
module WebGui; end
require "dry-struct"
require 'dry-struct'
module Types
include Dry::Types.module
end
require "dry-events"
require_relative "web_gui/element"
require_relative "web_gui/event_manager"
require_relative "web_gui/app"
require_relative "web_gui/window"
require_relative "web_gui/serv"
require_relative "web_gui/wsserv"
require 'dry-events'
require_relative 'web_gui/element'
require_relative 'web_gui/event_manager'
require_relative 'web_gui/app'
require_relative 'web_gui/window'
require_relative 'web_gui/serv'
require_relative 'web_gui/wsserv'
module WebGui
class Text < Element
attribute :text, Types::Coercible::String
def render()
return "<p id=#{id}>#{text}</p>"
def render
"<p id=#{id}>#{text}</p>"
end
def settext(text)
$app.update("paragraph",id,text)
$app.update('paragraph', id, text)
end
end
class Link < Element
attribute :page, Types::Symbol
attribute :text, Types::Coercible::String.optional
def render()
def render
if text
return "<a href=\"#{page}\">#{text}</a>"
"<a href=\"#{page}\">#{text}</a>"
else
return "<a href=\"#{page}\">#{page.to_s}</a>"
"<a href=\"#{page}\">#{page}</a>"
end
end
end
class Button < Link
@css="a.button{-webkit-appearance: button;-moz-appearance: button;appearance: button;text-decoration: none;color: initial;padding: 0px 10px;}"
def render()
return "<a href=\"#{page}\" class=\"button\">#{text}</a>"
@css = 'a.button{-webkit-appearance: button;-moz-appearance: button;appearance: button;text-decoration: none;color: initial;padding: 0px 10px;}'
def render
"<a href=\"#{page}\" class=\"button\">#{text}</a>"
end
def self.css()
return @css
class << self
attr_reader :css
end
end
class Image < Element
attribute :name, Types::Coercible::String
def render()
return "<img src=\"#{name}.jpg\"></img>"
def render
"<img src=\"#{name}.jpg\"></img>"
end
end
class Video < Element
attribute :name, Types::Coercible::String
def render()
return "<video controls width=320 height=300><source src=\"#{name}.webm\" type=\"video/webm\"><source src=\"#{name}.mp4\" type=\"video/mp4\"></video>"
def render
"<video controls width=320 height=300><source src=\"#{name}.webm\" type=\"video/webm\"><source src=\"#{name}.mp4\" type=\"video/mp4\"></video>"
end
end
@ -71,53 +73,51 @@ module WebGui
attribute :text, Types::Coercible::String
def initialize(opthash, &block)
super(opthash)
@block=block
@block = block
end
def render()
return "<button id=#{id}>#{text}</button>"
def render
"<button id=#{id}>#{text}</button>"
end
def on_button_pushed(event)
@block.call if event[:id]==id
@block.call if event[:id] == id
end
end
class Menu < Element
attribute :opts, Types::Hash
def initialize(opthash, &block)
super(opthash)
@block=block
@block = block
end
def render()
html="<select id=#{id}>"
opts.each do |val,text|
html+="<option value=\"#{val.to_s}\">#{text}</option>"
def render
html = "<select id=#{id}>"
opts.each do |val, text|
html += "<option value=\"#{val}\">#{text}</option>"
end
html+="</select>"
return html
html += '</select>'
html
end
def on_menu_updated(event)
@block.call(event[:val]) if event[:id]==id
@block.call(event[:val]) if event[:id] == id
end
end
class TextField < Element
def initialize(opthash, &block)
super(opthash)
@block=block
@block = block
end
def render()
html="<input id=#{id}>"
def render
html = "<input id=#{id}>"
end
def on_textfield_updated(event)
@block.call(event[:val]) if event[:id]==id
@block.call(event[:val]) if event[:id] == id
end
end
@ -125,40 +125,39 @@ module WebGui
attribute :buttons, Types::Coercible::Hash
def initialize(opthash, &block)
super(opthash)
@block=block
@block = block
end
def render()
html=""
buttons.each do |value,text|
html+="<input type=\"radio\" id=#{id} name=\"#{id}\" value=\"#{value.to_s}\">#{text}<br>"
def render
html = ''
buttons.each do |value, text|
html += "<input type=\"radio\" id=#{id} name=\"#{id}\" value=\"#{value}\">#{text}<br>"
end
return html
html
end
def on_radiobutton_updated(event)
@block.call(event[:val]) if event[:id]==id
@block.call(event[:val]) if event[:id] == id
end
end
class CheckBox < Element
attribute :boxes, Types::Coercible::Hash
def initialize(opthash, &block)
super(opthash)
@block=block
@block = block
end
def render()
html=""
boxes.each do |value,text|
html+="<input type=\"checkbox\" id=#{id} name=\"#{id}\" value=\"#{value.to_s}\">#{text}<br>"
def render
html = ''
boxes.each do |value, text|
html += "<input type=\"checkbox\" id=#{id} name=\"#{id}\" value=\"#{value}\">#{text}<br>"
end
return html
html
end
def on_checkbox_updated(event)
@block.call(event[:val]) if event[:id]==id
@block.call(event[:val]) if event[:id] == id
end
end
end

View File

@ -1,110 +1,110 @@
$forcenochrome=false
$stdout.sync=true
$eventmanager=EventManager.new
class WebGui::App
attr_reader :windows
def initialize(title,&block)
$app=self
if ARGV.length>0
if ARGV[0]=="--forcenochrome"
$forcenochrome=true
# frozen_string_literal: true
$forcenochrome = false
$stdout.sync = true
$eventmanager = EventManager.new
module WebGui
class App
attr_reader :windows
def initialize(title, &block)
$app = self
$forcenochrome = true if ARGV.length.positive? && (ARGV[0] == '--forcenochrome')
@windows = {}
@windows[:main] = WebGui::Window.new(name: :main, title: title)
@windows[:main].instance_eval(&block)
end
def render_document(wname)
window = windows[wname]
html = window.render
css = window.render_css
<<~ENDDOC
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src=\"main.js\"></script>
<style>
#{css}
</style>
<title>#{window.title}</title>
</head>
<body>
#{html}
</body>
</html>
ENDDOC
end
def add_window(wname, title, &block)
@windows[wname] = WebGui::Window.new(name: wname, title: title)
@windows[wname].instance_eval(&block)
end
def run
servthread = Thread.new do
WebGui::Server.server(self)
end
startwsserv
if File.exist?("/Applications/Google\ Chrome.app") && !$forcenochrome
`"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" --app="http://localhost:2000"`
else
puts 'Chrome is not on your system.'
puts 'Please install Chrome to use this framework properly.'
puts 'If Chrome is installed, please make sure it is called Google Chrome.app and is in the root Applications folder.'
puts 'The app will open in your default browser as a regular webpage instead.'
sleep(5)
`open http://localhost:2000`
servthread.join
end
end
@windows={}
@windows[:main]=WebGui::Window.new(name: :main,title: title)
@windows[:main].instance_eval(&block)
end
def render_document(wname)
window=windows[wname]
html=window.render
css=window.render_css
doc=<<-ENDDOC
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src=\"main.js\"></script>
<style>
#{css}
</style>
<title>#{window.title}</title>
</head>
<body>
#{html}
</body>
</html>
ENDDOC
return doc
end
def add_window(wname,title,&block)
@windows[wname]=WebGui::Window.new(name: wname,title: title)
@windows[wname].instance_eval(&block)
end
def run()
servthread=Thread.new do
WebGui::Server.server(self)
end
startwsserv()
if File.exists? "/Applications/Google\ Chrome.app" and !$forcenochrome
`"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" --app="http://localhost:2000"`
else
puts "Chrome is not on your system."
puts "Please install Chrome to use this framework properly."
puts "If Chrome is installed, please make sure it is called Google Chrome.app and is in the root Applications folder."
puts "The app will open in your default browser as a regular webpage instead."
sleep(5)
`open http://localhost:2000`
servthread.join
end
end
def startwsserv()
@serv=WebGui::WebSocketServer.new
Thread.new(self,@serv) do |parent,server|
server.accept
while true
message=server.recv
if message==false
server.accept
next
def startwsserv
@serv = WebGui::WebSocketServer.new
Thread.new(self, @serv) do |parent, server|
server.accept
loop do
message = server.recv
if message == false
server.accept
next
end
parent.handlemessage(message)
end
parent.handlemessage(message)
end
end
end
def handlemessage(message)
puts "Got message #{message}"
part1,val=message.split("=")
type=part1.match /[a-zA-Z]+/.to_s
type=type[0]
id=part1.match /\d+/
id=id[0].to_i
if val
case type
when "menu"
val=val.to_sym
when "radiobutton"
val=val.to_sym
when "checkbox"
vals=val.split("&")
temp=[]
vals.each do |val|
temp.push(val.to_sym)
def handlemessage(message)
puts "Got message #{message}"
part1, val = message.split('=')
type = part1.match(/[a-zA-Z]+/.to_s)
type = type[0]
id = part1.match(/\d+/)
id = id[0].to_i
if val
case type
when 'menu'
val = val.to_sym
when 'radiobutton'
val = val.to_sym
when 'checkbox'
vals = val.split('&')
temp = []
vals.each do |val|
temp.push(val.to_sym)
end
val = temp
end
val=temp
$eventmanager.publish("#{type}.updated", val: val, id: id)
else
$eventmanager.publish("#{type}.pushed", id: id)
end
$eventmanager.publish("#{type}.updated",val: val,id: id)
else
$eventmanager.publish("#{type}.pushed",id: id)
end
end
def update(type,id,val)
puts "Sent message #{type}#{id}=#{val}"
@serv.send("#{type}#{id}=#{val}")
def update(type, id, val)
puts "Sent message #{type}#{id}=#{val}"
@serv.send("#{type}#{id}=#{val}")
end
end
end

View File

@ -1,24 +1,31 @@
$id=0
$idtoel={}
def get_id()
id=$id
$id+=1
return id
# frozen_string_literal: true
$id = 0
$idtoel = {}
def get_id
id = $id
$id += 1
id
end
class WebGui::Element < Dry::Struct
constructor_type :strict_with_defaults
attribute :id, Types::Coercible::Int.default { get_id() }
def initialize(opthash=nil)
if opthash
super(opthash)
@block=nil
module WebGui
class Element < Dry::Struct
constructor_type :strict_with_defaults
attribute :id, Types::Coercible::Int.default { get_id }
def initialize(opthash = nil)
if opthash
super(opthash)
@block = nil
end
$idtoel[id] = self
end
def render
''
end
def self.css
@ccs
end
$idtoel[id]=self
end
def render()
return ""
end
def self.css()
return @ccs
end
end

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true
class EventManager
include Dry::Events::Publisher[:event_manager]
register_event("button.pushed")
register_event("menu.updated")
register_event("textfield.updated")
register_event("checkbox.updated")
register_event("radiobutton.updated")
register_event('button.pushed')
register_event('menu.updated')
register_event('textfield.updated')
register_event('checkbox.updated')
register_event('radiobutton.updated')
end

View File

@ -1,136 +1,144 @@
require "socket"
class WebGui::Server
def self.sfile(file,headers,type,client,url)
total=file.length
range=headers["Range"]
if range!=nil
positions=range.split("=")[1].split("-")
start=positions[0].to_i(10)
m_end=positions[1] ? positions[1].to_i(10) : total - 1;
chunksize=(m_end-start)+1
chunk=file[start, m_end+1]
if type=="mp4"
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"video/mp4"}
elsif type=="webm"
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"video/webm"}
elsif type=="mpeg"
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"audio/mpeg"}
elsif type=="ogg"
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"audio/ogg"}
end
header=""
r_headers.each do |key,value|
header+="#{key}: #{value}\n"
end
client.puts "HTTP/1.1 206 Partial Content"
client.print "#{header}"
client.print "\n"
client.print "#{chunk}"
else
if type=="mp4"
r_headers={"Content-Type"=>"video/mp4"}
elsif type=="webm"
r_headers={"Content-Type"=>"video/webm"}
elsif type=="mpeg"
r_headers={"Content-Type"=>"audio/mpeg"}
elsif type=="ogg"
r_headers={"Content-Type"=>"audio/ogg"}
end
header=""
r_headers.each do |key,value|
header+="#{key}: #{value}\n"
end
client.puts "HTTP/1.1 200 OK"
client.print "#{header}"
client.print "\n"
client.print "#{file}"
end
client.close
end
# frozen_string_literal: true
def self.server(app)
Thread::abort_on_exception=true
server = TCPServer.new(2000)
$debug = true
igfiles=["favicon.ico"]
loop do
require 'socket'
module WebGui
class Server
def self.sfile(file, headers, type, client, _url)
total = file.length
range = headers['Range']
if !range.nil?
positions = range.split('=')[1].split('-')
start = positions[0].to_i(10)
m_end = positions[1] ? positions[1].to_i(10) : total - 1
chunksize = (m_end - start) + 1
chunk = file[start, m_end + 1]
case type
when 'mp4'
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
'Content-Length' => chunksize, 'Content-Type' => 'video/mp4' }
when 'webm'
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
'Content-Length' => chunksize, 'Content-Type' => 'video/webm' }
when 'mpeg'
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
'Content-Length' => chunksize, 'Content-Type' => 'audio/mpeg' }
when 'ogg'
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
'Content-Length' => chunksize, 'Content-Type' => 'audio/ogg' }
end
header = ''
r_headers.each do |key, value|
header += "#{key}: #{value}\n"
end
client.puts 'HTTP/1.1 206 Partial Content'
client.print header.to_s
client.print "\n"
client.print chunk.to_s
else
case type
when 'mp4'
r_headers = { 'Content-Type' => 'video/mp4' }
when 'webm'
r_headers = { 'Content-Type' => 'video/webm' }
when 'mpeg'
r_headers = { 'Content-Type' => 'audio/mpeg' }
when 'ogg'
r_headers = { 'Content-Type' => 'audio/ogg' }
end
header = ''
r_headers.each do |key, value|
header += "#{key}: #{value}\n"
end
client.puts 'HTTP/1.1 200 OK'
client.print header.to_s
client.print "\n"
client.print file.to_s
end
client.close
end
def self.server(app)
Thread.abort_on_exception = true
server = TCPServer.new(2000)
$debug = true
igfiles = ['favicon.ico']
loop do
Thread.start(server.accept) do |client|
begin
lines=[]
line=client.gets
while line != "\r\n"
if line==nil
lines=[""]
runreq=false
break
begin
lines = []
line = client.gets
while line != "\r\n"
if line.nil?
lines = ['']
runreq = false
break
end
lines << line
line = client.gets
end
lines<<line
line=client.gets
end
i=0
lines.each do |value|
lines[i]=value.chomp
i=i+1
end
temp=lines.shift
method=temp.split(" ")[0]
url=temp.split(" ")[1]
headers={}
lines.each do |value|
temp=value.split(": ")
headers[temp[0]]=temp[1]
end
wname=url.gsub("/","")
if igfiles.include? wname
client.puts "HTTP/1.1 404 Not Found"
client.puts "Content-Type:text/html"
client.puts
client.print ""
else
if wname.include? ".jpg"
img=File.read(wname)
client.puts "HTTP/1.1 200 OK"
client.puts "Content-Type:image/jpeg"
i = 0
lines.each do |value|
lines[i] = value.chomp
i += 1
end
temp = lines.shift
method = temp.split(' ')[0]
url = temp.split(' ')[1]
headers = {}
lines.each do |value|
temp = value.split(': ')
headers[temp[0]] = temp[1]
end
wname = url.gsub('/', '')
if igfiles.include? wname
client.puts 'HTTP/1.1 404 Not Found'
client.puts 'Content-Type:text/html'
client.puts
client.print ''
elsif wname.include? '.jpg'
img = File.read(wname)
client.puts 'HTTP/1.1 200 OK'
client.puts 'Content-Type:image/jpeg'
client.puts
client.print img
elsif wname.include? ".mp4"
sfile(File.open(wname, "rb") {|io| io.read},headers,"mp4",client,url)
elsif wname.include? ".webm"
sfile(File.open(wname, "rb") {|io| io.read},headers,"webm",client,url)
elsif wname.include? ".js"
if wname=="main.js"
jspath=`gem which web_gui`.split("/")
elsif wname.include? '.mp4'
sfile(File.open(wname, 'rb', &:read), headers, 'mp4', client, url)
elsif wname.include? '.webm'
sfile(File.open(wname, 'rb', &:read), headers, 'webm', client, url)
elsif wname.include? '.js'
if wname == 'main.js'
jspath = `gem which web_gui`.split('/')
jspath.pop
jspath.push "main.js"
jspath=jspath.join("/")
script=File.read(jspath)
jspath.push 'main.js'
jspath = jspath.join('/')
script = File.read(jspath)
else
script=File.read(wname)
script = File.read(wname)
end
client.puts "HTTP/1.1 200 OK"
client.puts "Content-Type:application/javascript"
client.puts 'HTTP/1.1 200 OK'
client.puts 'Content-Type:application/javascript'
client.puts
client.print script
else
wname="main" if wname==""
wname=wname.to_sym
doc=app.render_document(wname)
client.puts "HTTP/1.1 200 OK"
client.puts "Content-Type:text/html"
wname = 'main' if wname == ''
wname = wname.to_sym
doc = app.render_document(wname)
client.puts 'HTTP/1.1 200 OK'
client.puts 'Content-Type:text/html'
client.puts
client.print doc
end
client.close
rescue StandardError => e
puts e
puts e.backtrace
client.puts 'HTTP/1.1 500 Internal Server Error'
client.puts 'Content-Type:text/plain'
client.puts
client.puts "Server error: #{e}"
client.print e.backtrace.join("\n")
client.close
end
client.close
rescue StandardError=>e
puts e
puts e.backtrace
client.puts "HTTP/1.1 500 Internal Server Error"
client.puts "Content-Type:text/plain"
client.puts
client.puts "Server error: #{e}"
client.print e.backtrace.join("\n")
client.close
end
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module WebGui
VERSION = "1.0.0"
VERSION = '1.0.0'
end

View File

@ -1,34 +1,37 @@
class WebGui::Window < Dry::Struct
attribute :title, Types::Coercible::String.default("")
attribute :name, Types::Coercible::String
def initialize(opthash)
super(opthash)
@elements=[]
end
# frozen_string_literal: true
def add_element(element)
raise ArgumentError, "The element must be of type Element." unless element.is_a? WebGui::Element
@elements.push element
$eventmanager.subscribe(element)
return element
end
def render()
html=""
@elements.each do |el|
html+="<p>#{el.render()}</p>"
module WebGui
class Window < Dry::Struct
attribute :title, Types::Coercible::String.default('')
attribute :name, Types::Coercible::String
def initialize(opthash)
super(opthash)
@elements = []
end
return html
end
def render_css()
css=""
WebGui::Element.descendants.each do |el|
elcss=el.css
if elcss!=nil
css+=elcss
def add_element(element)
raise ArgumentError, 'The element must be of type Element.' unless element.is_a? WebGui::Element
@elements.push element
$eventmanager.subscribe(element)
element
end
def render
html = ''
@elements.each do |el|
html += "<p>#{el.render}</p>"
end
html
end
def render_css
css = ''
WebGui::Element.descendants.each do |el|
elcss = el.css
css += elcss unless elcss.nil?
end
css
end
return css
end
end

View File

@ -1,76 +1,82 @@
# frozen_string_literal: true
require 'socket' # Provides TCPServer and TCPSocket classes
require 'digest/sha1'
class WebGui::WebSocketServer
def initialize(host="localhost",port=2345)
@server=TCPServer.new(host,port)
end
def accept()
# Wait for a connection
@socket = @server.accept
# Read the HTTP request. We know it's finished when we see a line with nothing but \r\n
http_request = ""
while (line = @socket.gets) && (line != "\r\n")
http_request += line
module WebGui
class WebSocketServer
def initialize(host = 'localhost', port = 2345)
@server = TCPServer.new(host, port)
end
# Grab the security key from the headers. If one isn't present, close the connection.
if matches = http_request.match(/^Sec-WebSocket-Key: (\S+)/)
websocket_key = matches[1]
else
socket.close
return
def accept
# Wait for a connection
@socket = @server.accept
# Read the HTTP request. We know it's finished when we see a line with nothing but \r\n
http_request = ''
while (line = @socket.gets) && (line != "\r\n")
http_request += line
end
# Grab the security key from the headers. If one isn't present, close the connection.
if matches = http_request.match(/^Sec-WebSocket-Key: (\S+)/)
websocket_key = matches[1]
else
socket.close
return
end
response_key = Digest::SHA1.base64digest([websocket_key, '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'].join)
@socket.write <<~EOS
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: #{response_key}
#{' '}
EOS
end
response_key = Digest::SHA1.base64digest([websocket_key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"].join)
@socket.write <<-eos
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: #{response_key}
eos
end
def recv
first_byte = @socket.getbyte
fin = first_byte & 0b10000000
opcode = first_byte & 0b00001111
fin = fin >> 7
fin = (fin ? true : false)
raise "We don't support continuations" unless fin
def recv()
first_byte = @socket.getbyte
fin = first_byte & 0b10000000
opcode = first_byte & 0b00001111
fin = fin >> 7
fin = (fin ? true : false)
raise "We don't support continuations" unless fin
unless opcode == 1 or opcode == 8
raise "We only support text data and close frame"
send("",3)
unless (opcode == 1) || (opcode == 8)
raise 'We only support text data and close frame'
send('', 3)
end
case opcode
when 1
second_byte = @socket.getbyte
is_masked = second_byte & 0b10000000
payload_size = second_byte & 0b01111111
raise 'All incoming frames should be masked according to the websocket spec' unless is_masked
raise 'We only support payloads < 126 bytes in length' unless payload_size < 126
mask = 4.times.map { @socket.getbyte }
data = payload_size.times.map { @socket.getbyte }
unmasked_data = data.each_with_index.map { |byte, i| byte ^ mask[i % 4] }
unmasked_data.pack('C*').force_encoding('utf-8')
when 8
send('', 0)
false
end
end
if opcode == 1
second_byte = @socket.getbyte
is_masked = second_byte & 0b10000000
payload_size = second_byte & 0b01111111
raise "All incoming frames should be masked according to the websocket spec" unless is_masked
raise "We only support payloads < 126 bytes in length" unless payload_size < 126
mask = 4.times.map {@socket.getbyte}
data = payload_size.times.map {@socket.getbyte}
unmasked_data = data.each_with_index.map { |byte, i| byte ^ mask[i % 4] }
string=unmasked_data.pack('C*').force_encoding('utf-8')
return string
elsif opcode == 8
send("",0)
return false
end
end
def send(message,closetype=false)
if closetype
output=[0x88,2,3,0xe8+closetype]
@socket.write output.pack("C*")
@socket.close
else
output = [0x81, message.size, message]
@socket.write output.pack("CCA#{message.size}")
def send(message, closetype = false)
if closetype
output = [0x88, 2, 3, 0xe8 + closetype]
@socket.write output.pack('C*')
@socket.close
else
output = [0x81, message.size, message]
@socket.write output.pack("CCA#{message.size}")
end
end
end
end

View File

@ -1,17 +1,18 @@
# coding: utf-8
lib = File.expand_path("../lib", __FILE__)
# frozen_string_literal: true
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "web_gui/version"
require 'web_gui/version'
Gem::Specification.new do |spec|
spec.name="web_gui"
spec.version=WebGui::VERSION
spec.authors=["pjht"]
spec.email=["pjht@users.noreply.github.com"]
spec.summary= "A GUI framework for ruby based around the web browser"
spec.homepage="https://github.com/pjht/web_gui"
spec.files=["lib/web_gui.rb","lib/main.js"]+Dir.glob("lib/web_gui/*.rb")
spec.bindir="exe"
spec.executables=spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths=["lib"]
spec.name = 'web_gui'
spec.version = WebGui::VERSION
spec.authors = ['pjht']
spec.email = ['pjht@users.noreply.github.com']
spec.summary = 'A GUI framework for ruby based around the web browser'
spec.homepage = 'https://github.com/pjht/web_gui'
spec.files = ['lib/web_gui.rb', 'lib/main.js'] + Dir.glob('lib/web_gui/*.rb')
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']
end