mirror of
https://github.com/mistergibson/gxg-server.git
synced 2026-08-15 10:24:29 -07:00
394 lines
No EOL
16 KiB
Ruby
394 lines
No EOL
16 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()
|
|
# @federation = ::GxG::service_object(:federation)
|
|
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_channel = ::GxG::CHANNELS.create_channel(the_uuid)
|
|
if the_channel
|
|
the_channel.socket = the_socket
|
|
end
|
|
#
|
|
GxGwww::SOCKETS_SAFETY.synchronize {
|
|
GxGwww::SOCKETS[(the_uuid)] = the_socket
|
|
}
|
|
# Un-encrypted Preamble
|
|
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
|
|
# Reference:
|
|
# ::GxGwww.api_login(session_id=nil, credential=:"00000000-0000-4000-0000-000000000000")
|
|
# ::GxGwww.api_logout(session_id=nil)
|
|
#
|
|
true
|
|
end
|
|
#
|
|
def on_message(the_socket, the_data, the_type)
|
|
the_uuid = the_socket.instance_variable_get(:@uuid)
|
|
the_channel = ::GxG::CHANNELS.fetch_channel(the_uuid)
|
|
if the_channel
|
|
the_session = the_channel.socket.instance_variable_get(:@session)
|
|
if the_session
|
|
the_secret = the_session["csrf"]
|
|
the_credential = (the_session['credential'] || :"00000000-0000-4000-0000-000000000000").to_s.to_sym
|
|
if the_secret
|
|
#
|
|
begin
|
|
# ::GxG::Events::Message::import(the_data.decode64.decrypt(the_secret))
|
|
# Send format: channel.socket.send({ :payload => the_message.export.to_s.encrypt(channel.secret).encode64 }.to_json.encode64, :text)
|
|
op_frame = JSON.parse(the_data.to_s.decode64, {:symbolize_names => true})
|
|
if op_frame.is_a?(::Hash)
|
|
unless the_credential == :"00000000-0000-4000-0000-000000000000"
|
|
if op_frame[:payload]
|
|
the_channel.send_message(::GxG::Events::Message::import(JSON.parse(op_frame[:payload].to_s.decode64.decrypt(the_secret), {:symbolize_names => true})))
|
|
end
|
|
if op_frame[:open_channel]
|
|
unless ::GxG::CHANNELS.fetch_channel(op_frame[:open_channel].to_s.to_sym)
|
|
::GxG::CHANNELS.create_channel(op_frame[:open_channel].to_s.to_sym)
|
|
new_channel = ::GxG::CHANNELS.fetch_channel(op_frame[:open_channel].to_s.to_sym)
|
|
if new_channel
|
|
new_channel.socket = the_channel.socket
|
|
new_channel.secret = the_channel.secret
|
|
end
|
|
end
|
|
end
|
|
if op_frame[:close_channel]
|
|
::GxG::CHANNELS.destroy_channel(op_frame[:close_channel].to_s.to_sym)
|
|
end
|
|
end
|
|
else
|
|
end
|
|
# if the_type == :text
|
|
# the_payload = JSON.parse(the_data.to_s.decode64.decrypt(the_secret), {:symbolize_names => true})
|
|
# else
|
|
# the_payload = {:body => ::GxG::ByteArray.new(the_data.to_s.decode64.decrypt(the_secret))}.gxg_export
|
|
# end
|
|
# the_channel.send_message(::GxG::Events::Message::import(the_payload))
|
|
rescue Exception => the_error
|
|
log_error(:error => the_error, :parameters => {:socket => the_socket, :data => the_data, :type => the_type})
|
|
end
|
|
#
|
|
else
|
|
log_error(:error => Exception.new("No session secret found"), :parameters => {:socket => the_socket, :data => the_data, :type => the_type})
|
|
end
|
|
else
|
|
log_error(:error => Exception.new("No session found"), :parameters => {:socket => the_socket, :data => the_data, :type => the_type})
|
|
end
|
|
else
|
|
log_error(:error => Exception.new("No channel found"), :parameters => {:socket => the_socket, :data => the_data, :type => the_type})
|
|
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['session_id'])]
|
|
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)
|
|
}
|
|
prune_list = []
|
|
::GxG::GXG_FEDERATION_SAFETY.synchronize {
|
|
::GxG::GXG_FEDERATION[:connections].each_pair do |uuid, the_channel|
|
|
if the_socket == the_channel.socket
|
|
prune_list << uuid
|
|
end
|
|
end
|
|
}
|
|
prune_list.each do |uuid|
|
|
::GxG::CHANNELS.destroy_channel(uuid)
|
|
end
|
|
puts "Closing Socket #{the_uuid} & Display #{the_display}"
|
|
true
|
|
end
|
|
#
|
|
def on_shutdown()
|
|
puts "Shutdown Received ..."
|
|
end
|
|
#
|
|
end
|
|
# Add socket message pump for ChannelManager
|
|
|
|
# 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
|
|
#
|
|
# |