gxg-server/Services/www/www.rb
2025-03-30 11:26:28 -07:00

333 lines
No EOL
12 KiB
Ruby

#!/usr/bin/env jruby
#
# ### Standard Ruby is fine for small installations and development.
# ### JRuby is best for scaled production as it uses *real* threads for each request.
# ### WEBrick was chosen for its sheer simplicity. When combined with JRuby it performs suprisingly well.
# ### DO NOT use with puma or rainbow or unicorn as this is a unitary NON-forking design. (feature != bug)
#
require "securerandom"
require "logger"
require "webrick"
# module WEBrick
# class Cookie
# def to_s
# ret = ""
# ret << @name << "=" << @value
# ret << "; " << "Version=" << @version.to_s if @version > 0
# ret << "; " << "Domain=" << @domain if @domain
# ret << "; " << "Expires=" << @expires if @expires
# ret << "; " << "Max-Age=" << @max_age.to_s if @max_age
# ret << "; " << "Comment=" << @comment if @comment
# ret << "; " << "Path=" << @path if @path
# ret << "; " << "Secure" if @secure
# # ret << "; " << "SameSite=Lax"
# ret
# end
# end
# end
require "webrocket"
# See: https://github.com/unak/WEBrocket
require "sinatra/base"
require "sinatra/webdav"
require "sinatra/contrib/all"
# See: http://sinatrarb.com/contrib/streaming.html
#
# GxG WWW Initialization
www_service = ::GxG::Services::Service.new(:www)
www_paths = {:www_public => "#{::GxG::SYSTEM_PATHS[:public]}/www"}
www_paths[:www_themes] = "#{www_paths[:www_public]}/themes"
www_paths[:www_javascript] = "#{www_paths[:www_public]}/javascript"
www_paths[:www_images] = "#{www_paths[:www_public]}/images"
www_paths[:www_audio] = "#{www_paths[:www_public]}/audio"
www_paths[:www_video] = "#{www_paths[:www_public]}/video"
# TODO: work out how to publish files to the public (cdn-style) no matter the internal origin.
www_paths[:www_files] = "#{www_paths[:www_public]}/files"
www_paths.values.each do |a_path|
unless Dir.exist?(a_path)
begin
# FileUtils.mkpath(a_path, 0755)
FileUtils.mkpath(a_path)
rescue Exception => error
log_error({:error => error, :parameters => a_path})
end
end
end
www_paths.each_pair do |moniker, path|
::GxG::SYSTEM_PATHS[(moniker)] = path
end
# ### Sinatra Bootstrap
class Node0 < Sinatra::Application
def self.publish_route(the_method, path, options={}, &block)
route(the_method.to_s.upcase, path, options) do
block.call(the_method,request,session)
end
end
enable :sessions
#use Rack::Session::Cookie, {:key => "rack.session", :expire_after => 318513600, :same_site => :strict, :secure => true}
#
helpers Sinatra::Streaming
register Sinatra::WebDAV
configure do
set :backend, WEBrick
set :environment, (::GxG::SERVICES[:www].configuration[:mode] || "production").to_sym
set :bind, ((::GxG::SERVICES[:www].configuration[:listen] || [])[0] || {:address => "127.0.0.1"})[:address]
set :port, ((::GxG::SERVICES[:www].configuration[:listen] || [])[0] || {:port => 32767})[:port].to_i
set :sessions, true
set :root, ::GxG::SYSTEM.gxg_root()
set :public_folder, ::GxG::SYSTEM_PATHS[:www_public]
set :logging, true
set :logger, ::GxG::LOG
secret = (::GxG::uuid_generate.to_s + "-" + ::GxG::uuid_generate.to_s)
set :session_secret, secret
use Rack::Session::Pool, {:expire_after => 318513600, :secure => true}
use Rack::Session::Cookie, {:key => 'rack.session', :path => '/', :expire_after => 318513600, :secret => secret, :secure => true}
end
# ### Extend Sinatra Verbs
# DAV Class 1 Supports
# def self.mkcol(path, options = {}, &block)
# route('MKCOL', path, options, &block)
# end
# #
# def self.copy(path, options = {}, &block)
# route('COPY', path, options, &block)
# end
# #
# def self.move(path, options = {}, &block)
# route('MOVE', path, options, &block)
# end
# #
# def self.propfind(path, options = {}, &block)
# route('PROPFIND', path, options, &block)
# end
# #
# def self.proppatch(path, options = {}, &block)
# route('PROPPATCH', path, options, &block)
# end
# # DAV Class 2 Supports
# def self.enlock(path, options = {}, &block)
# route('LOCK', path, options, &block)
# end
# #
# def self.unlock(path, options = {}, &block)
# route('UNLOCK', path, options, &block)
# end
# # DAV Class 3 Supports
# # TODO: Versioning ??
# # ### Set OPTIONS Header Values
# route('OPTIONS', '*') do
# # FIXME no GET on collections, so make this a resource method
# headers({"DAV" => "1 2 3", "Ms-Author-Via" => "DAV", "Allow" => "OPTIONS, HEAD, GET, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK"})
# ok
# end
# ### Implementation of the verbs:
get '*' do
GxG::SERVICES[:www][:receiver].handle_request(:get, request, session)
end
#
put '*' do
GxG::SERVICES[:www][:receiver].handle_request(:put, request, session)
end
#
post '*' do
GxG::SERVICES[:www][:receiver].handle_request(:post, request, session)
end
#
delete '*' do
GxG::SERVICES[:www][:receiver].handle_request(:delete, request, session)
end
#
mkcol '*' do
GxG::SERVICES[:www][:receiver].handle_request(:mkcol, request, session)
end
#
copy '*' do
GxG::SERVICES[:www][:receiver].handle_request(:copy, request, session)
end
#
move '*' do
GxG::SERVICES[:www][:receiver].handle_request(:move, request, session)
end
#
propfind '*' do
GxG::SERVICES[:www][:receiver].handle_request(:propfind, request, session)
end
#
proppatch '*' do
GxG::SERVICES[:www][:receiver].handle_request(:proppatch, request, session)
end
# lock method collision work-around:
# lock '*' do
# GxG::SERVICES[:www][:receiver].handle_request(:lock, request, session)
# end
route('LOCK', '*') do
GxG::SERVICES[:www][:receiver].handle_request(:lock, request, session)
end
#
unlock '*' do
GxG::SERVICES[:www][:receiver].handle_request(:unlock, request, session)
end
# Note : options is pre-defined.
end
# ### WebSocket Listener
module GxGwww
SOCKETS = {}
SOCKETS_SAFETY = Mutex.new
end
#
class WebSocketListener
def initialize()
self
end
#
def on_open(the_socket)
the_uuid = GxG::uuid_generate.to_sym
the_socket.instance_variable_set(:@uuid, the_uuid)
the_socket.instance_variable_set(:@session, nil)
the_socket.instance_variable_set(:@display, nil)
the_socket.instance_variable_set(:@inbox, [])
GxGwww::SOCKETS_SAFETY.synchronize {
GxGwww::SOCKETS[(the_uuid)] = the_socket
}
#
GxG::SERVICES[:www].dispatcher.post_event(:communications) do
sleep 0.5
the_socket.send({ :attach_socket => the_uuid.to_s }.to_json.encode64, :text)
end
#
true
end
#
def on_message(the_socket, the_data, the_type)
message_queue = the_socket.instance_variable_get(:@inbox)
GxGwww::SOCKETS_SAFETY.synchronize {
if the_type == :text
begin
# Expects: json+base64 of a Hash
message_queue << JSON.parse(the_data.to_s.decode64, {:symbolize_names => true})
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:data => the_data}})
end
else
# Takes in all other data as binary into inbox
message_queue << ::GxG::ByteArray.new(the_data.to_s)
end
}
true
end
#
def on_recv(the_socket, the_data, the_type)
self.on_message(the_socket, the_data, the_type)
end
#
def on_close(the_socket)
the_uuid = the_socket.instance_variable_get(:@uuid)
the_session = the_socket.instance_variable_get(:@session)
the_display = the_socket.instance_variable_get(:@display)
#
if the_display
the_manifest = GxG::SERVICES[:www][:manifests][(the_session)]
if the_manifest
if the_manifest.display_exist?(the_display)
the_manifest.display_set(the_display, :available)
unless the_manifest.connector_get(the_display).remote_closed?
the_manifest.connector_get(the_display).close_remote
end
end
end
end
#
GxGwww::SOCKETS_SAFETY.synchronize {
GxGwww::SOCKETS.delete(the_uuid)
}
puts "Closing Socket #{the_uuid} & Display #{the_display}"
true
end
#
def on_shutdown()
puts "Shutdown Received ..."
end
#
end
# Load GxGwww:
require (File.expand_path("./www/gxg-www.rb",File.dirname(__FILE__)))
www_service[:manifests] = {}
www_service[:receiver] = GxGwww::Responder.new
# ### Define Public Command Interface:
www_service.on(:start, {:description => "WWW Service Layer Start", :usage => "{ :start => nil }"}) do
::GxG::SERVICES[:www].start
end
www_service.on(:stop, {:description => "WWW Service Layer Stop", :usage => "{ :stop => nil }"}) do
::GxG::SERVICES[:www].stop
end
www_service.on(:restart, {:description => "WWW Service Layer Restart", :usage => "{ :restart => nil }"}) do
::GxG::SERVICES[:www].restart
end
www_service.on(:pause, {:description => "WWW Service Pause", :usage => "{ :pause => nil }"}) do
::GxG::SERVICES[:www].pause
end
www_service.on(:resume, {:description => "WWW Service Resume", :usage => "{ :resume => nil }"}) do
::GxG::SERVICES[:www].resume
end
# ### Define Internal Service Control Events:
www_service.on(:at_start, {:description => "WWW Startup", :usage => "{ :at_start => (service-object) }"}) do |service, credential|
#
# TODO : increment number of threads used in GxG::Engine
Thread.new { Node0.run! }
until Node0.running_server do
sleep 1.0
end
(service.configuration[:listen] || []).each_with_index do |the_node, indexer|
if indexer > 0
Node0.running_server().listen(the_node[:address],(the_node[:port] || 32767))
sleep 1.0
log_info("Also listening on #{the_node[:address]}:#{(the_node[:port] || 32767)} ...")
end
end
# Mount WebSocket Listener
Node0::running_server.mount_websocket((GxG::SERVICES[:www].configuration[:relative_url].to_s + "/ws"), WebSocketListener.new, "gxg")
# Load WebDAV Processing:
# Access files and objects via: GxG::SERVICES[:core][:resources]
require (File.expand_path("./www/protocols/dav/dav.rb",File.dirname(__FILE__)))
#
{:result => true}
end
www_service.on(:at_stop, {:description => "WWW Stop", :usage => "{ :at_stop => (service-object) }", :public => false}) do |service|
#
Node0.stop!
#
{:result => true}
end
www_service.on(:publish_route, {:description => "Publish Route", :usage => "{ :publish_route => {:http_method => :get, :path => '/path', :options => {}, :code => proc}}", :public => false}) do |data, credential|
# Code Usage:
# ::GxG::SERVICES[:core].publish_route(:get,"/",{}) do |the_method,the_request,the_session|
# end
Node0.publish_route(data[:http_method], data[:path], data[:options], &data[:code])
#
{:result => true}
end
www_service.on(:publish_api, {:description => "Publish Service API", :usage => "{ :publish_api => {:service => (service-object), :path => '/path'} }", :public => false}) do |data, credential|
#
::GxGwww::publish_api(data[:service], data[:path])
#
{:result => true}
end
www_service.on(:unpublish_api, {:description => "Unpublish Service API", :usage => "{ :unpublish_api => {:path => '/path'} }", :public => false}) do |data, credential|
#
::GxGwww::unpublish_api(data[:path])
#
{:result => true}
end
# ### Service Installation
unless ::GxG::Services::service_available?(:www)
# Set Configuration Defaults
www_service.configuration[:mode] = "production"
www_service.configuration[:listen] = [{:address => "127.0.0.1", :port => 32767}]
www_service.configuration[:relative_url] = ""
www_service.configuration[:cache_quota] = 1073741824
www_service.configuration[:cache_max_item_size] = 1073741824
www_service.save_configuration
::GxG::Services::install_service(:www)
::GxG::Services::enable_service(:www)
end
#
#