Class: MainPlugin

Inherits:
Plugin
  • Object
show all
Defined in:
plugins/main/main.rb

Overview

The MainPlugin is accessible as @plugins.main and just main from other plugins.

MainPlugin provides mainly client setup and the following services:

  • The root html page, which includes the scripts and sets up client startup variables.

  • The url of the client as a HValue, including the anchor.

    • Accessible via msg.session[:main][:location_href]

  • The local time of the client’s web browser as a HValue, as seconds since epoch.

    • Accessible via msg.session[:main][:client_time]

  • Sequential loading. See #delayed_call

  • Provides the #init_ui event for plugins that respond to it.

Instance Method Summary collapse

Instance Method Details

#closeObject

Frees the ticket resource id of the “loading” gif image.



112
113
114
115
# File 'plugins/main/main.rb', line 112

def close
  super
  @plugins[:ticket].del_rsrc( @loading_gif_id )
end

#delayed_call(msg, params) ⇒ nil

Interface for adding delayed calls

When adding a delayed call, use an Array to define a plugin/method with optional arguments that will be called on the next request. The client will call back immediately when a delayed call is pending. The first param of the method is a msg. Don’t include the msg of the current request in params, it will be inserted automatically for the delayed call.

It can also be used for loading sequences to the client, when using a String as the params.

Format of params for plugin callback:

Array
plugin_name, method_name, *args

Format of params for javascript sequences:

String

Javascript to send

Calls will be flushed per request with the following conditions:

  • At most four (4) delayed calls will be processed at a time

  • If the calls use more than 200ms combined, even less will be processed at a time

Parameters:

  • msg (Message)

    The message instance.

  • params (Array, String)

    The params of the delayed call.

Returns:

  • (nil)


314
315
316
# File 'plugins/main/main.rb', line 314

def delayed_call( msg, params )
  get_ses( msg )[:delayed_calls].push( params )
end

#do_init_ui(msg) ⇒ Object

Enables the init_ui event.



356
357
358
# File 'plugins/main/main.rb', line 356

def do_init_ui( msg )
  get_ses( msg )[:dont_init_ui] = false
end

#dont_init_ui(msg) ⇒ Object

Disables the init_ui event.



350
351
352
# File 'plugins/main/main.rb', line 350

def dont_init_ui( msg )
  get_ses( msg )[:dont_init_ui] = true
end

#end_polling(msg, ses) ⇒ Object

When nothing is delayed and the second poll has been made (init_ui called), sets the client to non-polling-mode, having only value synchronization trigger new requests. On the client, SesWatcher forces the change by sending the client time periodically.



419
420
421
422
423
424
# File 'plugins/main/main.rb', line 419

def end_polling( msg, ses )
  if ses[:poll_mode] == true
    msg.reply "COMM.Transporter.poll(0);"
    ses[:poll_mode] = false
  end
end

#flush_delayed(msg, ses) ⇒ Object

Flushes commands in the :delayed_calls array



362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'plugins/main/main.rb', line 362

def flush_delayed( msg, ses )
  ## Limits the amount of delayed calls to process to 4.
  ## Prevents the client from choking even when the server
  ## load is light.
  if ses[:delayed_calls].size < 4
    call_count = ses[:delayed_calls].size
  else
    call_count = 4
  end
  
  time_start = Time.now.to_f
  time_taken = 0.0
  
  ## process delayed calls, until:
  ## - over 200ms of cpu time has been spent
  ## - the :delayed_calls -array is empty
  ## - call_count limit is reached
  until time_taken > 0.2 or ses[:delayed_calls].size == 0 or call_count == 0
    # gets the next call
    delayed_call = ses[:delayed_calls].shift
    if RSence.args[:debug]
      puts "delayed_call: #{delayed_call.inspect}"
    end
    # strings are always javascript, used for segmenting client load
    if delayed_call.class == String
      msg.reply delayed_call
    # arrays are plugin calls
    elsif delayed_call.class == Array
      # ['plugin_name', 'method_name'] pairs call the named plugin:method with just msg
      if delayed_call.size == 2
        (plugin_name,method_name) = delayed_call
        msg.run(plugin_name,method_name,msg)
      # if the array contains more items, they are used as additional method params
      else
        (plugin_name,method_name) = delayed_call[0..1]
        method_params = delayed_call[2..-1]
        msg.run(plugin_name,method_name,msg,*method_params)
      end
    end
    ## calculates time taken
    time_taken = Time.now.to_f - time_start
    call_count -= 1
  end
  ## Sets the client into poll mode, unless the :delayed_calls -array is empty
  if ses[:boot] > 1
    if ses[:delayed_calls].empty?
      end_polling( msg, ses )
    else
      start_polling( msg, ses )
    end
  end
end

#get(req, response, ses) ⇒ Object

Outputs the startup web page.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'plugins/main/main.rb', line 146

def get( req, response, ses )
  index_html = render_index_html
  
  response.status = 200
  
  response['Content-Type'] = 'text/html; charset=UTF-8'
  response['Date'] = httime( Time.now )
  response['Server'] = 'RSence'
  response['Cache-Control'] = 'no-cache'
  
  if support_gzip( req.header )
    index_gzip = GZString.new('')
    gzwriter = Zlib::GzipWriter.new( index_gzip, 9 )
    gzwriter.write( index_html )
    gzwriter.close
    response['Content-Length'] = index_gzip.bytesize.to_s
    response['Content-Encoding'] = 'gzip'
    response.body = index_gzip
  else
    response['Content-Length'] = index_html.bytesize.to_s
    response.body = index_html
  end
end

#idle(msg) ⇒ Object

Called on every request of an active, valid session



435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'plugins/main/main.rb', line 435

def idle(msg)
  
  ses = get_ses( msg )
  if ses[:boot] == 0
    boot0( msg, ses )
  elsif ses[:boot] == 1
    boot1( msg, ses )
  elsif not ses[:delayed_calls].empty?
    flush_delayed( msg, ses )
  elsif ses[:boot] > 1
    end_polling( msg, ses )
  end
  ## Increment the counter forever.
  ses[:boot] += 1
end

#index_deps_setup(msg) ⇒ Object



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'plugins/main/main.rb', line 241

def index_deps_setup( msg )
  ses = msg.session
  if not ses.has_key?( :deps )
    # make an array of dependencies for this session, if not already done
    ses[:deps] = []
  end
  compound_pkgs = RSence.config[:client_pkg][:compound_packages]
  boot_dep = @conf[:boot_lib]
  unless ses[:deps].include?( boot_dep )
    if compound_pkgs.include?( boot_dep )
      compound_pkgs[ boot_dep ].each do |pkg_name|
        ses[:deps].push( pkg_name )
        msg.reply(%{jsLoader.loaded("#{pkg_name}");})
      end
    end
    ses[:deps].push( boot_dep )
    begin
      msg.reply(%{jsLoader.loaded("#{boot_dep}");})
    rescue => e
      warn %{ses_id: #{msg.ses_id} failed to load boot_dep: "#{boot_dep}", because: #{e.inspect}}
    end
    if boot_dep == 'rsence'
      ses[:deps].push( 'std_widgets' )
      msg.reply(%{jsLoader.loaded("std_widgets");})
    end
  end
  @conf[:default_libs].each do |dep_lib|
    unless ses[:deps].include?( dep_lib )
      if compound_pkgs.include?( dep_lib )
        compound_pkgs[ dep_lib ].each do |pkg_name|
          ses[:deps].push( pkg_name )
          msg.reply(%{jsLoader.loaded("#{pkg_name}");})
        end
      end
      ses[:deps].push( dep_lib )
      msg.reply(%{jsLoader.loaded("#{dep_lib}");})
    end
  end
end

#initObject

Binds configuration data as instance variables



94
95
96
97
98
99
100
101
102
# File 'plugins/main/main.rb', line 94

def init
  super
  @plugins.register_alias( :main, :index_html )
  @randgen = RandGen.new( 40 )
  ::RSence.config[:index_html][:instance] = self
  @conf  = ::RSence.config[:index_html]
  @bconf = ::RSence.config[:broker_urls]
  @goodbye_uri = File.join(@bconf[:hello],'goodbye')
end

#init_ses(msg) ⇒ Object

New session initialization, called just once per session.



236
237
238
239
# File 'plugins/main/main.rb', line 236

def init_ses( msg )
  super
  restore_ses( msg )
end

#openObject

Opens and renders the index page template



105
106
107
108
109
# File 'plugins/main/main.rb', line 105

def open
  super
  @index_html_src = file_read( ::RSence.config[:index_html][:index_tmpl] )
  render_index_html
end

#post(req, res, ses) ⇒ Object

Returns the “hello/goodbye” session termination request



171
172
173
# File 'plugins/main/main.rb', line 171

def post( req, res, ses )
  @plugins.sessions.expire_ses_by_req( req, res )
end

#pound(msg) ⇒ Object

Returns pound url of browser (after the ‘#’ sign)



226
227
228
# File 'plugins/main/main.rb', line 226

def pound( msg )
  get_ses( msg )[:url][1]
end

#render_index_htmlObject

Index page renderer



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'plugins/main/main.rb', line 63

def render_index_html
  
  index_html = @index_html_src.clone
  
  client_rev = client_pkg.client_cache.client_rev
  deps_src = ''
  @conf[:deps].each do |dep|
    deps_src += %{<script src="#{dep}" type="text/javascript"></script>}
  end
  deps_src += %{<script src="__CLIENT_BASE__/js/#{@conf[:boot_lib]}.js"></script>}
  @conf[:default_libs].each do |dep|
    deps_src += %{<script src="__CLIENT_BASE__/js/#{dep}.js"></script>}
  end
  client_base = File.join(@bconf[:h],client_rev)
  
  index_html.gsub!( '__SCRIPT_DEPS__',   deps_src          )
  index_html.gsub!( '__CLIENT_BASE__',   client_base       )
  index_html.gsub!( '__DEFAULT_TITLE__', @conf[:title]     )
  index_html.gsub!( '__CLIENT_REV__',    client_rev        )
  index_html.gsub!( '__CLIENT_HELLO__',  @bconf[:hello]    )
  index_html.gsub!( '__NOSCRIPT__',      @conf[:noscript]  )
  
  return index_html
end

#restore_ses(msg) ⇒ Object

Called once when a session is restored or cloned using the cookie’s ses_key



282
283
284
285
286
287
288
289
290
291
# File 'plugins/main/main.rb', line 282

def restore_ses( msg )
  super
  index_deps_setup( msg )
  ## Resets session data to defaults
  ses = get_ses( msg )
  ses[:boot] = 0
  ses[:url] = [nil,nil]
  ses[:delayed_calls] = []
  ses[:poll_mode] = true
end

#start_polling(msg, ses) ⇒ Object

Starts polling mode.



427
428
429
430
431
432
# File 'plugins/main/main.rb', line 427

def start_polling( msg, ses )
  if ses[:poll_mode] == false
    msg.reply( "COMM.Transporter.poll(#{::RSence.config[:transporter_conf][:client_poll_priority]});" )
    ses[:poll_mode] = true
  end
end

#support_gzip(header) ⇒ Object

Inspects the http request header to decide if the browser supports gzip compressed responses.



139
140
141
142
143
# File 'plugins/main/main.rb', line 139

def support_gzip( header )
  return false if not ::RSence.config[:no_gzip]
  return false if not header.has_key?('accept-encoding')
  return header['accept-encoding'].include?('gzip')
end

#url(msg) ⇒ Object

Returns base url of browser (before the ‘#’ sign)



220
221
222
# File 'plugins/main/main.rb', line 220

def url( msg )
  get_ses( msg )[:url][0]
end

#url_responder(msg, location_href) ⇒ Object

The #url_responder gets called whenever the anchor (pound) of location.href changes. It enables virtual url events for back/forward buttons and bookmarking in browsers whenever utilized.

Client-side support is included in js/url_responder.js

Also allows virtual-host -like behavior if utilized.



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'plugins/main/main.rb', line 185

def url_responder(msg,location_href)
  
  ses = get_ses( msg )
  
  # Virtual locations:
  if location_href.data.include?('#')
    
    # split 'http://localhost:8001/#/some_uri'
    #   -> ['http://localhost:8001/','/some_uri']
    ses[:url] = location_href.data.split('#')
    
    virtual_uri = ses[:url][1]
    
    # built-in support for signing out, deletes the
    # server-side session and reloads the page
    if virtual_uri == '/sign_out'
      resp_addr = @conf[:respond_address]
      msg.expire_session()
      msg.reply( [
        'COMM.Transporter.stop=true;',
        "location.href=#{resp_addr.to_json};"
      ].join('') )
    end
    
  else
    ses[:url] = [location_href.data,nil]
  end
  
  # url_responder always accepts locations
  return true
  
end