#!/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"] 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) 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 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)] 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 # 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 # #