nitro 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +8 -0
- data/ChangeLog +1546 -0
- data/LICENCE +32 -0
- data/README +278 -0
- data/RELEASES +7 -0
- data/Rakefile +79 -0
- data/bin/cluster.rb +219 -0
- data/doc/architecture.txt +28 -0
- data/doc/bugs.txt +7 -0
- data/doc/css.txt +20 -0
- data/doc/ideas.txt +120 -0
- data/doc/pg.txt +47 -0
- data/doc/svn.txt +82 -0
- data/doc/todo.txt +30 -0
- data/etc/new-project.rb +18 -0
- data/examples/simple/README +15 -0
- data/examples/simple/app.rb +31 -0
- data/examples/simple/conf/apache.conf +100 -0
- data/examples/simple/conf/config.rb +89 -0
- data/examples/simple/conf/debug-config.rb +53 -0
- data/examples/simple/conf/live-config.rb +48 -0
- data/examples/simple/conf/overrides.rb +9 -0
- data/examples/simple/conf/requires.rb +51 -0
- data/examples/simple/ctl +32 -0
- data/examples/simple/env.rb +33 -0
- data/examples/simple/install.rb +12 -0
- data/examples/simple/lib/articles/entities.rb +35 -0
- data/examples/simple/lib/articles/lc-en.rb +36 -0
- data/examples/simple/lib/articles/methods.rb +55 -0
- data/examples/simple/lib/articles/part.rb +58 -0
- data/examples/simple/logs/access_log +2 -0
- data/examples/simple/logs/apache.log +3 -0
- data/examples/simple/logs/app.log +1 -0
- data/examples/simple/logs/events.log +1 -0
- data/examples/simple/root/add-article.sx +15 -0
- data/examples/simple/root/article-form.ss +20 -0
- data/examples/simple/root/comments-form.ss +16 -0
- data/examples/simple/root/comments.si +30 -0
- data/examples/simple/root/index.sx +44 -0
- data/examples/simple/root/shader/shader.xsl +100 -0
- data/examples/simple/root/shader/style.css +9 -0
- data/examples/simple/root/view-article.sx +30 -0
- data/examples/tiny/app.rb +30 -0
- data/examples/tiny/conf/apache.conf +100 -0
- data/examples/tiny/conf/config.rb +67 -0
- data/examples/tiny/conf/requires.rb +40 -0
- data/examples/tiny/ctl +31 -0
- data/examples/tiny/logs/access_log +9 -0
- data/examples/tiny/logs/apache.log +9 -0
- data/examples/tiny/root/index.sx +35 -0
- data/lib/n/app/cluster.rb +219 -0
- data/lib/n/app/cookie.rb +86 -0
- data/lib/n/app/filters/autologin.rb +50 -0
- data/lib/n/app/fragment.rb +67 -0
- data/lib/n/app/handlers.rb +120 -0
- data/lib/n/app/handlers/code-handler.rb +184 -0
- data/lib/n/app/handlers/page-handler.rb +612 -0
- data/lib/n/app/request-part.rb +59 -0
- data/lib/n/app/request.rb +653 -0
- data/lib/n/app/script.rb +398 -0
- data/lib/n/app/server.rb +53 -0
- data/lib/n/app/session.rb +224 -0
- data/lib/n/app/user.rb +47 -0
- data/lib/n/app/webrick-servlet.rb +213 -0
- data/lib/n/app/webrick.rb +70 -0
- data/lib/n/application.rb +187 -0
- data/lib/n/config.rb +31 -0
- data/lib/n/db.rb +217 -0
- data/lib/n/db/README +232 -0
- data/lib/n/db/connection.rb +369 -0
- data/lib/n/db/make-release.sh +26 -0
- data/lib/n/db/managed.rb +235 -0
- data/lib/n/db/mixins.rb +282 -0
- data/lib/n/db/mysql.rb +342 -0
- data/lib/n/db/psql.rb +378 -0
- data/lib/n/db/tools.rb +110 -0
- data/lib/n/db/utils.rb +99 -0
- data/lib/n/events.rb +118 -0
- data/lib/n/l10n.rb +22 -0
- data/lib/n/logger.rb +33 -0
- data/lib/n/macros.rb +53 -0
- data/lib/n/mixins.rb +46 -0
- data/lib/n/parts.rb +154 -0
- data/lib/n/properties.rb +194 -0
- data/lib/n/server.rb +61 -0
- data/lib/n/server/PLAYBACK.txt +8 -0
- data/lib/n/server/RESEARCH.txt +13 -0
- data/lib/n/server/filter.rb +77 -0
- data/lib/n/shaders.rb +167 -0
- data/lib/n/sitemap.rb +188 -0
- data/lib/n/std.rb +69 -0
- data/lib/n/sync/clc.rb +108 -0
- data/lib/n/sync/handler.rb +221 -0
- data/lib/n/sync/server.rb +170 -0
- data/lib/n/tools/README +11 -0
- data/lib/n/ui/date-select.rb +74 -0
- data/lib/n/ui/pager.rb +187 -0
- data/lib/n/ui/popup.rb +45 -0
- data/lib/n/ui/select.rb +41 -0
- data/lib/n/ui/tabs.rb +34 -0
- data/lib/n/utils/array.rb +92 -0
- data/lib/n/utils/cache.rb +144 -0
- data/lib/n/utils/gfx.rb +108 -0
- data/lib/n/utils/hash.rb +148 -0
- data/lib/n/utils/html.rb +147 -0
- data/lib/n/utils/http.rb +98 -0
- data/lib/n/utils/mail.rb +28 -0
- data/lib/n/utils/number.rb +31 -0
- data/lib/n/utils/pool.rb +66 -0
- data/lib/n/utils/string.rb +297 -0
- data/lib/n/utils/template.rb +38 -0
- data/lib/n/utils/time.rb +91 -0
- data/lib/n/utils/uri.rb +193 -0
- data/lib/xsl/base.xsl +205 -0
- data/lib/xsl/ce.xsl +30 -0
- data/lib/xsl/localization.xsl +23 -0
- data/lib/xsl/xforms.xsl +26 -0
- data/test/run.rb +95 -0
- metadata +187 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
# = AutoLogin filter
|
2
|
+
#
|
3
|
+
# code:: gmosx
|
4
|
+
#
|
5
|
+
# (c) 2004 Navel, all rights reserved.
|
6
|
+
# $Id: autologin.rb 79 2004-10-18 16:38:19Z gmosx $
|
7
|
+
|
8
|
+
require "n/utils/string"
|
9
|
+
require "n/server/filter"
|
10
|
+
|
11
|
+
require_part "users"
|
12
|
+
|
13
|
+
module N; module App
|
14
|
+
|
15
|
+
# = AutoLoginFilter
|
16
|
+
#
|
17
|
+
# Automatically login a user with valid authentication cookie.
|
18
|
+
# Uses some n1 code at the moment.
|
19
|
+
#
|
20
|
+
class AutoLoginFilter < N::ServerFilter
|
21
|
+
|
22
|
+
def process(request)
|
23
|
+
if request.is_top?
|
24
|
+
# only try to autologin for top level requests
|
25
|
+
# there is NO need for the older SKIP_AUTOLOGIN session flag.
|
26
|
+
if (request.session.user.anonymous?) and
|
27
|
+
cookie = request.get_cookie($users_auth_cookie)
|
28
|
+
|
29
|
+
username, password_crypted = cookie.split(",")
|
30
|
+
|
31
|
+
if user = $db.get_by_name(username, N::User) and
|
32
|
+
password_crypted == user.password
|
33
|
+
if request.session.login(request, user)
|
34
|
+
# user logged in
|
35
|
+
else
|
36
|
+
# stale cookie, remove it
|
37
|
+
request.del_cookie($users_auth_cookie)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
$log.warn "Unknown user or wrong password in auth-cookie: #{cookie} from IP: #{request.remote_addr}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
return @next_filter.process(request) if @next_filter
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end; end # module
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# = Fragment
|
2
|
+
#
|
3
|
+
# A Fragment is the output of a rendered script. Additional metadata
|
4
|
+
# such as lastmodified and expire times are stored to facilitate
|
5
|
+
# fragment processing (for example caching).
|
6
|
+
#
|
7
|
+
# === Design:
|
8
|
+
#
|
9
|
+
# Fragments are cached in the filesystem to allow for a cluster of
|
10
|
+
# server to access them. Benefits over the older memory cache scheme:
|
11
|
+
#
|
12
|
+
# - each fragment is processed once by one server
|
13
|
+
# - easier visualisation of the fragments.
|
14
|
+
# - can clear the cache for all server
|
15
|
+
# - can selectively invalidate one fragment!
|
16
|
+
# - less memory per server
|
17
|
+
# - can run background cron scripts over the fragments (compression)
|
18
|
+
#
|
19
|
+
# code:: gmosx
|
20
|
+
#
|
21
|
+
# (c) 2004 Navel, all rights reserved.
|
22
|
+
# $Id: fragment.rb 71 2004-10-18 10:50:22Z gmosx $
|
23
|
+
|
24
|
+
require "n/utils/cache"
|
25
|
+
require "n/mixins"
|
26
|
+
|
27
|
+
module N; module App
|
28
|
+
|
29
|
+
class Fragment
|
30
|
+
include N::Expirable
|
31
|
+
include N::LRUCache::Item
|
32
|
+
|
33
|
+
# precompiled flags for fragment key "customization"
|
34
|
+
|
35
|
+
ADMIN_FLAG = "-a"
|
36
|
+
OWNER_FLAG = "-o"
|
37
|
+
EDITOR_FLAG = "-e"
|
38
|
+
VIEWER_FLAG = "-f"
|
39
|
+
MY_FLAG = "-m"
|
40
|
+
ANONYMOUS_FLAG = "-n"
|
41
|
+
|
42
|
+
attr_accessor :body, :lm
|
43
|
+
|
44
|
+
# another method for invalidation, allows for more flexible
|
45
|
+
# invalidation strategies. also used by the legacy autoinvalidate
|
46
|
+
# code.
|
47
|
+
attr_accessor :expires
|
48
|
+
|
49
|
+
def initialize(body = "", lm = Time.now)
|
50
|
+
@body = body
|
51
|
+
@lm = lm
|
52
|
+
end
|
53
|
+
|
54
|
+
def expires_after!(ea = (60*60*24))
|
55
|
+
@expires = @lm + ea
|
56
|
+
end
|
57
|
+
|
58
|
+
def expired?
|
59
|
+
return true if @expires.nil? || (Time.now > @expires)
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_str
|
63
|
+
return @body
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end; end # module
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# = Handlers
|
2
|
+
#
|
3
|
+
# code: gmosx
|
4
|
+
#
|
5
|
+
# (c) 2002-2003 Navel, all rights reserved.
|
6
|
+
# $Id: handlers.rb 71 2004-10-18 10:50:22Z gmosx $
|
7
|
+
|
8
|
+
require "n/utils/cache"
|
9
|
+
require "n/server/filter"
|
10
|
+
|
11
|
+
module N; module App
|
12
|
+
|
13
|
+
# = App server handlers handle requests to specific resources.
|
14
|
+
#
|
15
|
+
# === Design:
|
16
|
+
#
|
17
|
+
# Handlers are NOT singleton classes. This way we can assign one handler
|
18
|
+
# class to multiple resources, and keep statistics and metrics for
|
19
|
+
# each resource.
|
20
|
+
#
|
21
|
+
class Handler < N::ServerFilter
|
22
|
+
def process(request)
|
23
|
+
# nop
|
24
|
+
|
25
|
+
# walk the filter pipeline
|
26
|
+
@next_filter.process(request) if @next_filter
|
27
|
+
end
|
28
|
+
|
29
|
+
#---------------------------------------------------------------------
|
30
|
+
# Testing/Metrics support methods
|
31
|
+
|
32
|
+
|
33
|
+
end # class
|
34
|
+
|
35
|
+
# Handler Error.
|
36
|
+
# raise this if an error happens when handling a request
|
37
|
+
|
38
|
+
class HandlerError < StandardError; end
|
39
|
+
|
40
|
+
# = Script Handler
|
41
|
+
#
|
42
|
+
# Base handler for scripts.
|
43
|
+
|
44
|
+
class ScriptHandler < Handler
|
45
|
+
|
46
|
+
# cache the compiled page scripts to optimize future references.
|
47
|
+
# use a thread safe cache.
|
48
|
+
@@compiled_script_cache = N::SafeHash.new()
|
49
|
+
|
50
|
+
# dont allow 2 threads to compile the same script. In fact dont allow
|
51
|
+
# two threads to compile in parallel.
|
52
|
+
@@compile_sync = Sync.new
|
53
|
+
|
54
|
+
# Overload the path.
|
55
|
+
# FIXME: better name and much better documentation.
|
56
|
+
#
|
57
|
+
def overload_path(path)
|
58
|
+
path.gsub!(/\/\//, '/')
|
59
|
+
|
60
|
+
if ::File.exists?("#$root_dir/#{path}")
|
61
|
+
return path
|
62
|
+
else
|
63
|
+
$log.debug "OVERLOAD: '#{path}' -> 'p/#{path}'" if $DBG
|
64
|
+
return "p/#{path}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# the compiler returns a singleton class customized for rendering the
|
69
|
+
# input script. The original idea was to define render() as a static
|
70
|
+
# method and return the class, but we will use a singleton object to
|
71
|
+
# keep custom data structures (sub-page-graph, metrics, etc)
|
72
|
+
#
|
73
|
+
# script_path is used as key in the Dynamic class creation and for
|
74
|
+
# caching
|
75
|
+
#
|
76
|
+
# === Design:
|
77
|
+
# we use __ for out too to avoid nasty collisions.
|
78
|
+
#
|
79
|
+
def compile_script(script)
|
80
|
+
compiled_script = nil
|
81
|
+
|
82
|
+
# dont allow 2 threads to compile the same script. In fact dont
|
83
|
+
# allow two threads to compile in parallel.
|
84
|
+
# gmosx: SOS this eval assigns the variable compiled_script!
|
85
|
+
@@compile_sync.synchronize {
|
86
|
+
eval(script)
|
87
|
+
}
|
88
|
+
|
89
|
+
return compiled_script
|
90
|
+
end
|
91
|
+
|
92
|
+
def compiled_script_cache
|
93
|
+
return @@compiled_script_cache
|
94
|
+
end
|
95
|
+
|
96
|
+
# Log a rendering error
|
97
|
+
#
|
98
|
+
def log_error(request, ex)
|
99
|
+
request.log_error "--------"
|
100
|
+
request.log_error "#{request.path}:"
|
101
|
+
request.log_error "#{ex.class}: #{ex}"
|
102
|
+
request.log_error ex.backtrace()
|
103
|
+
raise ScriptHandlerError.new(0, "error")
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
# Handler Error.
|
109
|
+
# raise this if an error happens when handling a request
|
110
|
+
|
111
|
+
class ScriptHandlerError < HandlerError
|
112
|
+
attr_reader :error_line
|
113
|
+
|
114
|
+
def initialize(error_line, message = nil)
|
115
|
+
@error_line, @message = error_line, message
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end; end # module
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# = Code handler (.sx scripts)
|
2
|
+
#
|
3
|
+
# code:
|
4
|
+
# George Moschovitis <gm@navel.gr>
|
5
|
+
#
|
6
|
+
# (c) 2004 Navel, all rights reserved.
|
7
|
+
# $Id: code-handler.rb 71 2004-10-18 10:50:22Z gmosx $
|
8
|
+
|
9
|
+
require "cgi"
|
10
|
+
require "singleton"
|
11
|
+
require "sync"
|
12
|
+
|
13
|
+
require "n/utils/cache"
|
14
|
+
require "n/utils/uri"
|
15
|
+
require "n/app/script"
|
16
|
+
require "n/app/fragment"
|
17
|
+
require "n/app/handlers"
|
18
|
+
|
19
|
+
module N; module App
|
20
|
+
|
21
|
+
# = CodeHandler
|
22
|
+
#
|
23
|
+
# web server handler that executes ruby code (logic)
|
24
|
+
# Caching is NOT SUPPORTED (and not needed anyway)
|
25
|
+
#
|
26
|
+
# TODO:
|
27
|
+
#
|
28
|
+
# probably extend PageHandler from CodeHandler and not vice versa.
|
29
|
+
#
|
30
|
+
class CodeHandler < ScriptHandler
|
31
|
+
|
32
|
+
# process is called ONLY for top level scripts.
|
33
|
+
#
|
34
|
+
# no need for synchronization, the page-script is thread safe.
|
35
|
+
# if you use thread-unsafe code in your script you are responsible
|
36
|
+
# for synchronization.
|
37
|
+
#
|
38
|
+
# TODO:
|
39
|
+
# add timing code here
|
40
|
+
#
|
41
|
+
def process(request)
|
42
|
+
# gmosx, FIXME: temporarily here, move somewhere ELSE!
|
43
|
+
|
44
|
+
request.locale = $lc_map[request.user.locale] || $lc_en
|
45
|
+
script = get_compiled_script(request.path)
|
46
|
+
fragment = eval_script(script, request)
|
47
|
+
|
48
|
+
# walk the filter pipeline
|
49
|
+
super
|
50
|
+
|
51
|
+
return fragment, script
|
52
|
+
end
|
53
|
+
|
54
|
+
# evaluate the request script in the context of the current request.
|
55
|
+
# returns the output of the script, ie the fragment body.
|
56
|
+
#
|
57
|
+
# === Design:
|
58
|
+
#
|
59
|
+
# I think that the script caching logic should be here, because it nicelly
|
60
|
+
# encapsulates transform and compile.
|
61
|
+
#
|
62
|
+
def eval_script(script, request)
|
63
|
+
|
64
|
+
$log.debug "Evaluating, #{request.path}" if $DBG
|
65
|
+
|
66
|
+
fragment = Fragment.new(:key)
|
67
|
+
|
68
|
+
# try to render the script, report errors.
|
69
|
+
begin
|
70
|
+
fragment.body = script.__render(request)
|
71
|
+
rescue N::ScriptExitException => see
|
72
|
+
# the script raised a ScripteExitException to force a premature
|
73
|
+
# end. Surpress this error!
|
74
|
+
rescue => ex
|
75
|
+
log_error(request, ex)
|
76
|
+
end
|
77
|
+
|
78
|
+
return fragment
|
79
|
+
end
|
80
|
+
|
81
|
+
# === Output:
|
82
|
+
# defcode: the code that customizes the base script class.
|
83
|
+
# pagecode: the code that renders the fragment.
|
84
|
+
#
|
85
|
+
# === REMARKS:
|
86
|
+
# - this method is evaluated in compile time, so we cannot pass
|
87
|
+
# or use the request/request pair. gmosx: NO, we do pass request,
|
88
|
+
# some data can and SHOULD be used to prepare the transform.
|
89
|
+
#
|
90
|
+
def transform_script(path, hash, shader = nil)
|
91
|
+
|
92
|
+
path = overload_path(path)
|
93
|
+
|
94
|
+
# load the page text from the script
|
95
|
+
pagecode = ""
|
96
|
+
|
97
|
+
pagecode = File.read("#$root_dir/#{path}")
|
98
|
+
|
99
|
+
# convert to ruby code
|
100
|
+
pagecode.gsub!(/<<</, "__out << %{")
|
101
|
+
pagecode.gsub!(/>>>/, "}")
|
102
|
+
|
103
|
+
script = %{
|
104
|
+
class CodeScript_#{hash} < CodeScript
|
105
|
+
def initialize(path)
|
106
|
+
super
|
107
|
+
@pagecode = <<-'PAGECODE'
|
108
|
+
#{pagecode}
|
109
|
+
PAGECODE
|
110
|
+
@defcode = nil
|
111
|
+
end
|
112
|
+
def __render(request)
|
113
|
+
__out = ""
|
114
|
+
lc = request.locale
|
115
|
+
#{pagecode}
|
116
|
+
return __out
|
117
|
+
end
|
118
|
+
end
|
119
|
+
return CodeScript_#{hash}.new("#{path}")
|
120
|
+
}
|
121
|
+
|
122
|
+
return script
|
123
|
+
end
|
124
|
+
|
125
|
+
# try to get the script from the cache. invalidates the compiled version and return nil
|
126
|
+
# if the script is modified since compile time. Also takes active shader and localization
|
127
|
+
# into account. If script is not compiled, transform and compile it.
|
128
|
+
#
|
129
|
+
# Output:
|
130
|
+
# the compiled script. Throws exception if the script does not exist.
|
131
|
+
#
|
132
|
+
def get_compiled_script(path, hash = nil)
|
133
|
+
compiled_script = nil
|
134
|
+
key = calc_script_key(path, hash)
|
135
|
+
|
136
|
+
# gmosx: $reload_scripts is typically set to true when debugging
|
137
|
+
# statically included files (.ss)
|
138
|
+
unless $reload_scripts
|
139
|
+
|
140
|
+
compiled_script = @@compiled_script_cache[key]
|
141
|
+
|
142
|
+
# gmosx: monitor scripts should be explicit!
|
143
|
+
if $srv_monitor_scripts and compiled_script and (File.mtime(compiled_script.path) > compiled_script.__create_time)
|
144
|
+
$log.debug "Script '#{path}' externaly modified, recompiling" if $DBG
|
145
|
+
compiled_script = nil
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
unless compiled_script
|
151
|
+
# the script is not cached, so load, transform and compile it!
|
152
|
+
$log.debug "Compiling script '#{key}'" if $DBG
|
153
|
+
|
154
|
+
script = transform_script(path, key)
|
155
|
+
compiled_script = compile_script(script)
|
156
|
+
|
157
|
+
@@compiled_script_cache[key] = compiled_script
|
158
|
+
end
|
159
|
+
|
160
|
+
return compiled_script
|
161
|
+
end
|
162
|
+
|
163
|
+
def calc_script_key(path, hash)
|
164
|
+
return "#{path}#{hash}".gsub(/[^\w]/, "")
|
165
|
+
end
|
166
|
+
|
167
|
+
end # CodeHandler
|
168
|
+
|
169
|
+
# = Codescript
|
170
|
+
#
|
171
|
+
# encapsulates the script defining Ruby Code to execute.
|
172
|
+
# effectively acts as a Generator.
|
173
|
+
#
|
174
|
+
# === Requirements:
|
175
|
+
#
|
176
|
+
# - the generated class should be a singleton, no need to stress
|
177
|
+
# the garbage collector.
|
178
|
+
# - the render method should be thread safe.
|
179
|
+
#
|
180
|
+
class CodeScript < Script
|
181
|
+
|
182
|
+
end # CodeScript
|
183
|
+
|
184
|
+
end; end # module
|
@@ -0,0 +1,612 @@
|
|
1
|
+
# = XML Page handler (.sx scripts)
|
2
|
+
#
|
3
|
+
# code:
|
4
|
+
# George Moschovitis <gm@navel.gr>
|
5
|
+
# Anastasios Koutoumanos <ak@navel.gr>
|
6
|
+
#
|
7
|
+
# (c) 2004 Navel, all rights reserved.
|
8
|
+
# $Id: page-handler.rb 89 2004-10-20 12:55:58Z gmosx $
|
9
|
+
|
10
|
+
require "cgi"
|
11
|
+
require "singleton"
|
12
|
+
require "sync"
|
13
|
+
|
14
|
+
require "n/utils/cache"
|
15
|
+
require "n/utils/uri"
|
16
|
+
require "n/app/script"
|
17
|
+
require "n/app/fragment"
|
18
|
+
require "n/app/handlers"
|
19
|
+
|
20
|
+
module N; module App
|
21
|
+
|
22
|
+
# = PageHandler
|
23
|
+
#
|
24
|
+
# web server handler that render xml pages (.sx scripts).
|
25
|
+
# This is the main handler of the Nitro Application Server.
|
26
|
+
#
|
27
|
+
# The handler evaluates the given script. The result of
|
28
|
+
# this evaluation is called a Fragment. The result of a top
|
29
|
+
# level script, ie a top level fragments is called a page.
|
30
|
+
#
|
31
|
+
# == Advantages over the original .rx format:
|
32
|
+
#
|
33
|
+
# - xml based
|
34
|
+
# - strips <!-- comments
|
35
|
+
# - allows multiline comments
|
36
|
+
# - allows pre-transformation with xlst (free transformation)
|
37
|
+
# - allows validation of xhtml
|
38
|
+
# - allows syntax highlighting
|
39
|
+
#
|
40
|
+
# == TODO:
|
41
|
+
#
|
42
|
+
# - move shader selection in a script method, that can
|
43
|
+
# be overriden (user specific shaders).
|
44
|
+
# - compress the output some more, every byte
|
45
|
+
# counts (bandwidth == money)
|
46
|
+
# - use something like FormValidator to validate parameters
|
47
|
+
# for sub-fragments.
|
48
|
+
#
|
49
|
+
# === FIXME:
|
50
|
+
# - correct encoding of fragment hash
|
51
|
+
# (query string, shader, user etc)
|
52
|
+
#
|
53
|
+
# === Design:
|
54
|
+
#
|
55
|
+
# break the rendering process in sub methods to make testable
|
56
|
+
# and verifiable (and easier to read).
|
57
|
+
#
|
58
|
+
# Statically included scripts (.ss) SHOULD be valid xml files (even
|
59
|
+
# though they are not required) to be automatically verifiable and
|
60
|
+
# compatible with editors. The xml prologue (<?xml / <root>) is removed.
|
61
|
+
#
|
62
|
+
# Be carefull about statically included files scope issues. This is the
|
63
|
+
# reason why we do not use statically included files everywhere.
|
64
|
+
#
|
65
|
+
# === WARNING:
|
66
|
+
#
|
67
|
+
# no need to lock cache io, we use a safe cache!
|
68
|
+
#
|
69
|
+
class PageHandler < ScriptHandler
|
70
|
+
|
71
|
+
# process is called ONLY for top level scripts.
|
72
|
+
#
|
73
|
+
# no need for synchronization, the page-script is thread safe.
|
74
|
+
# if you use thread-unsafe code in your script you are responsible
|
75
|
+
# for synchronization.
|
76
|
+
#
|
77
|
+
# TODO:
|
78
|
+
# add timing code here
|
79
|
+
#
|
80
|
+
def process(request)
|
81
|
+
# gmosx, FIXME: temporarily here, move somewhere ELSE!
|
82
|
+
|
83
|
+
request.locale = $lc_map[request.user.locale] || $lc_en
|
84
|
+
request.shader = calc_shader(request)
|
85
|
+
request.tag = calc_tag(request)
|
86
|
+
request.top_script = script = get_compiled_script(request.path, request.tag, request.shader)
|
87
|
+
|
88
|
+
# Action requests should be uncacheable, to be safe.
|
89
|
+
# No need for a check here! all actions should use consume which
|
90
|
+
# sets the uncacheable flag.
|
91
|
+
#
|
92
|
+
# request.uncacheable = true if request.delete("*act*")
|
93
|
+
# request.uncacheable = true if request.query_string =~ /\$.*\$/
|
94
|
+
|
95
|
+
script.__init_render(request)
|
96
|
+
|
97
|
+
# dont cache admin pages, FIXME: use less checks here!!
|
98
|
+
|
99
|
+
if script.__cache?(request) and (not request.uncacheable) and
|
100
|
+
(not request.session["ADMIN_MODE"]) and (not $reload_scripts)
|
101
|
+
if etag = script.__etag(request)
|
102
|
+
request.headers["cache-control"] = "pubic, must-revalidate"
|
103
|
+
request.headers["etag"] = etag
|
104
|
+
end
|
105
|
+
else
|
106
|
+
# gmosx, FIXME: add the correct cache control header here!!
|
107
|
+
etag = nil
|
108
|
+
end
|
109
|
+
|
110
|
+
if etag && etag == request.headers["IF-NONE-MATCH"]
|
111
|
+
request.set_not_modified!
|
112
|
+
else
|
113
|
+
fragment = eval_script(script, request)
|
114
|
+
end
|
115
|
+
|
116
|
+
# walk the filter pipeline
|
117
|
+
super
|
118
|
+
|
119
|
+
return fragment, script
|
120
|
+
end
|
121
|
+
|
122
|
+
# Process sub level scripts.
|
123
|
+
#
|
124
|
+
def sub_process(request)
|
125
|
+
script = get_compiled_script(request.path, request.tag, request.shader)
|
126
|
+
script.__init_render(request)
|
127
|
+
fragment = eval_script(script, request)
|
128
|
+
|
129
|
+
return fragment, script
|
130
|
+
end
|
131
|
+
|
132
|
+
# evaluate the request script in the context of the current request.
|
133
|
+
# returns the output of the script, ie the fragment body.
|
134
|
+
#
|
135
|
+
# === Design:
|
136
|
+
#
|
137
|
+
# I think that the script caching logic should be here, because it nicelly
|
138
|
+
# encapsulates transform and compile.
|
139
|
+
#
|
140
|
+
def eval_script(script, request)
|
141
|
+
|
142
|
+
etag = script.__etag(request)
|
143
|
+
|
144
|
+
unless fragment = script.__cache_get(etag)
|
145
|
+
|
146
|
+
# no suitable fragment exists in the cache, so render the script.
|
147
|
+
$log.debug "Rendering, #{request.path}" if $DBG
|
148
|
+
|
149
|
+
fragment = Fragment.new
|
150
|
+
|
151
|
+
# try to render the script, report errors.
|
152
|
+
begin
|
153
|
+
fragment.body = script.__render(request)
|
154
|
+
|
155
|
+
if script.__cache?(request)
|
156
|
+
script.__cache_put(etag, fragment) unless request.uncacheable
|
157
|
+
end
|
158
|
+
rescue N::ScriptExitException => see
|
159
|
+
# the script raised a ScripteExitException to force a premature
|
160
|
+
# end. Surpress this error!
|
161
|
+
rescue => ex
|
162
|
+
log_error(request, ex)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
return fragment
|
168
|
+
end
|
169
|
+
|
170
|
+
# === Output:
|
171
|
+
# defcode: the code that customizes the base script class.
|
172
|
+
# pagecode: the code that renders the fragment.
|
173
|
+
#
|
174
|
+
# === TODO:
|
175
|
+
# - we have to keep the original script code to apply multiple shaders
|
176
|
+
#
|
177
|
+
# === REMARKS:
|
178
|
+
# - this method is evaluated in compile time, so we cannot pass
|
179
|
+
# or use the request/request pair. gmosx: NO, we do pass request,
|
180
|
+
# some data can and SHOULD be used to prepare the transform.
|
181
|
+
#
|
182
|
+
def transform_script(path, hash, shader = nil)
|
183
|
+
|
184
|
+
path = overload_path(path)
|
185
|
+
|
186
|
+
initcode = ""
|
187
|
+
defcode = ""
|
188
|
+
sub_scripts = []
|
189
|
+
filename = "#$root_dir/#{path}"
|
190
|
+
cached_filename = ".cache/#{shader}#{path}.rb"
|
191
|
+
|
192
|
+
# load the page text from the script.
|
193
|
+
# check if a cached transformed script exists.
|
194
|
+
# also staticaly includes all <?include xxx ?> files.
|
195
|
+
# this also validates the xhtml (cool side-effect).
|
196
|
+
begin
|
197
|
+
document = ::File.read(filename)
|
198
|
+
|
199
|
+
# calculate mtime
|
200
|
+
mtime = ::File.stat(filename).mtime
|
201
|
+
document.scan(/<\?include xl:href="(.*?)"(.*)\?>/) { |match|
|
202
|
+
match = overload_path(match[0])
|
203
|
+
imtime = ::File.stat("#$root_dir/#{match}").mtime
|
204
|
+
mtime = imtime if imtime > mtime
|
205
|
+
}
|
206
|
+
# also check the shader mtime
|
207
|
+
# FIXME: even better should check mtime now!
|
208
|
+
mtime = shader.mtime if shader.mtime > mtime
|
209
|
+
|
210
|
+
# calculate subscripts
|
211
|
+
document.scan(/<x:include xl:href="(.*?)"(.*)>/) { |match|
|
212
|
+
match = overload_path(match[0])
|
213
|
+
sub_path = "#{match.split("?")[0]}"
|
214
|
+
sub_scripts << get_compiled_script(sub_path, hash, shader)
|
215
|
+
}
|
216
|
+
|
217
|
+
# check if a cached transformed script exists
|
218
|
+
# FIXME: encode shader and shader mtime.
|
219
|
+
if false # ::File.exists?(cached_filename) and ::File.stat(cached_filename).mtime > mtime
|
220
|
+
return ::File.read(cached_filename), sub_scripts
|
221
|
+
end
|
222
|
+
|
223
|
+
$log.debug "Transforming '#{path}'" if $DBG
|
224
|
+
|
225
|
+
# static includes
|
226
|
+
# the target file is included at compile time.
|
227
|
+
#
|
228
|
+
# gmosx: must be xformed before the <?r pi.
|
229
|
+
# ex:
|
230
|
+
# <?include xl:href="root/myfile.sx" ?>
|
231
|
+
#
|
232
|
+
document.gsub!(/<\?include xl:href="(.*?)"(.*)\?>/) { |match|
|
233
|
+
# gmosx: xmm match matches the whole string.
|
234
|
+
match = overload_path($1)
|
235
|
+
load_statically_included("#$root_dir/#{match}")
|
236
|
+
}
|
237
|
+
|
238
|
+
# dynamic includes
|
239
|
+
# the target file is included at run time
|
240
|
+
#
|
241
|
+
document.gsub!(/<x:include xl:href="(.*?)"(.*)(.?)\/>/) { |match|
|
242
|
+
"<?r __out << __include('#$1', request) ?>"
|
243
|
+
}
|
244
|
+
|
245
|
+
# expand method macro
|
246
|
+
#
|
247
|
+
document.gsub!(/\@\?(.*?)(['|"|\s])/, '#{_a(request, %^\1^)}\2')
|
248
|
+
|
249
|
+
# localisation
|
250
|
+
# gmosx, OPTIMIZE in the future i could pre translate the strings!
|
251
|
+
document.gsub!(/\|:(.*?)\|/, '#{lc[:\1]}')
|
252
|
+
document.gsub!(/\!:(.*?)\|(.*?)\!/, '#{lc[:\1].call(\2)}')
|
253
|
+
|
254
|
+
# extract the definition code. The <?def .. ?> will
|
255
|
+
# be ignored afterwards.
|
256
|
+
#
|
257
|
+
document.scan(/<\?def(.*?)\?>/m) { |match|
|
258
|
+
defcode << $1 << "\n"
|
259
|
+
}
|
260
|
+
|
261
|
+
# extract the initialization phase code. The <?i .. ?> will
|
262
|
+
# be ignored afterwards.
|
263
|
+
#
|
264
|
+
document.scan(/<\?i(.*?)\?>/m) { |match|
|
265
|
+
initcode << $1 << "\n"
|
266
|
+
}
|
267
|
+
|
268
|
+
rescue Exception, StandardError => e
|
269
|
+
$log.error pp_exception(e)
|
270
|
+
raise RuntimeError.new
|
271
|
+
end
|
272
|
+
|
273
|
+
# pre-transform the script.
|
274
|
+
pagecode = shader.transform(document)
|
275
|
+
|
276
|
+
# preprocess the script to convert to valid ruby code.
|
277
|
+
|
278
|
+
# strip the xml header! (interracts with the following gsub!)
|
279
|
+
# FIXME: perhaps the xslt could strip this?
|
280
|
+
pagecode.gsub!(/<\?xml.*\?>/, "")
|
281
|
+
|
282
|
+
# xform the processing instructions
|
283
|
+
pagecode.gsub!(/\?>/, "\n__out << %{")
|
284
|
+
pagecode.gsub!(/<\?ruby /, "}\n")
|
285
|
+
pagecode.gsub!(/<\?r /, "}\n")
|
286
|
+
|
287
|
+
# tml, TODO: resolve static injects! scan injects and update the metadata
|
288
|
+
# in the page-graph.
|
289
|
+
|
290
|
+
pagecode = %@__out << %{#{pagecode}}@
|
291
|
+
|
292
|
+
# gmosx: unescape quotes etc, that are escaped by the xslt processor.
|
293
|
+
# can we avoid this ???
|
294
|
+
# yes: the xslt processors escapes code in #{ } brackets, we should
|
295
|
+
# use <?= ?> brackets.
|
296
|
+
#
|
297
|
+
# The new version of render supports output buffering ala php.
|
298
|
+
# __out_buffers keeps a stack of buffers.
|
299
|
+
|
300
|
+
pagecode = CGI.unescapeHTML(pagecode)
|
301
|
+
key = calc_script_key(path, hash)
|
302
|
+
|
303
|
+
script = %{
|
304
|
+
class PageScript#{key} < PageScript
|
305
|
+
def initialize(path)
|
306
|
+
super
|
307
|
+
@key = "#{key}"
|
308
|
+
end
|
309
|
+
#{defcode}
|
310
|
+
def __init_render(request)
|
311
|
+
#{initcode}
|
312
|
+
end
|
313
|
+
def __render(request)
|
314
|
+
lc = request.locale
|
315
|
+
|
316
|
+
__out_buffers = nil
|
317
|
+
|
318
|
+
__out = ""
|
319
|
+
if request.is_top?
|
320
|
+
__out = %|<?xml version="1.0"?>
|
321
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">|
|
322
|
+
end
|
323
|
+
|
324
|
+
#{pagecode}
|
325
|
+
return __out
|
326
|
+
end
|
327
|
+
end
|
328
|
+
compiled_script = PageScript#{key}.new("#{path}")
|
329
|
+
}
|
330
|
+
|
331
|
+
# remove excessive white space
|
332
|
+
# gmosx: FIXME: i can do better here: remove leading space per
|
333
|
+
# line for example.
|
334
|
+
script.squeeze!(" \t")
|
335
|
+
|
336
|
+
# cache the transformed script.
|
337
|
+
#
|
338
|
+
dir = N::StringUtils.directory_from_path(cached_filename)
|
339
|
+
FileUtils.mkdir_p(dir)
|
340
|
+
File.open(cached_filename, "w") { |f|
|
341
|
+
f << script
|
342
|
+
}
|
343
|
+
|
344
|
+
return script, sub_scripts
|
345
|
+
end
|
346
|
+
|
347
|
+
# try to get the script from the cache. invalidates the compiled version and return nil
|
348
|
+
# if the script is modified since compile time. Also takes active shader and localization
|
349
|
+
# into account. If script is not compiled, transform and compile it.
|
350
|
+
#
|
351
|
+
# Output:
|
352
|
+
# the compiled script. Throws exception if the script does not exist.
|
353
|
+
#
|
354
|
+
def get_compiled_script(path, hash, shader)
|
355
|
+
compiled_script = nil
|
356
|
+
key = calc_script_key(path, hash)
|
357
|
+
|
358
|
+
# gmosx: $reload_scripts is typically set to true when debugging
|
359
|
+
# statically included files (.ss)
|
360
|
+
unless $reload_scripts
|
361
|
+
|
362
|
+
compiled_script = @@compiled_script_cache[key]
|
363
|
+
|
364
|
+
# gmosx: monitor scripts should be explicit!
|
365
|
+
if $srv_monitor_scripts and compiled_script and (File.mtime(compiled_script.path) > compiled_script.__create_time)
|
366
|
+
$log.debug "Script '#{path}' externaly modified, recompiling" if $DBG
|
367
|
+
compiled_script = nil
|
368
|
+
end
|
369
|
+
|
370
|
+
end
|
371
|
+
|
372
|
+
unless compiled_script
|
373
|
+
# the script is not cached, so load, transform and compile it!
|
374
|
+
$log.debug "Compiling script '#{key}'" if $DBG
|
375
|
+
|
376
|
+
script, sub_scripts = transform_script(path, hash, shader)
|
377
|
+
compiled_script = compile_script(script)
|
378
|
+
compiled_script.sub_scripts = sub_scripts unless sub_scripts.empty?
|
379
|
+
|
380
|
+
@@compiled_script_cache[key] = compiled_script
|
381
|
+
end
|
382
|
+
|
383
|
+
return compiled_script
|
384
|
+
end
|
385
|
+
|
386
|
+
#-----------------------------------------------------------------------------
|
387
|
+
# Hooks
|
388
|
+
|
389
|
+
# Calculate shader for this request.
|
390
|
+
#
|
391
|
+
# TODO: allow per include shader!
|
392
|
+
#
|
393
|
+
def calc_shader(request)
|
394
|
+
return $default_shader
|
395
|
+
# return $shaders[request.user.shader]
|
396
|
+
end
|
397
|
+
|
398
|
+
# Standard encoding/modification/customization of the fragment_hash.
|
399
|
+
# Override this in your application to provide customized encoding.
|
400
|
+
# For example encode the shader id.
|
401
|
+
#
|
402
|
+
def calc_tag(request)
|
403
|
+
return "#{request.locale[:locale]}#{request.shader.name}"
|
404
|
+
end
|
405
|
+
|
406
|
+
def calc_script_key(path, hash)
|
407
|
+
return "#{path}#{hash}".gsub(/[^\w]/, "")
|
408
|
+
end
|
409
|
+
|
410
|
+
#-----------------------------------------------------------------------------
|
411
|
+
# Utils
|
412
|
+
|
413
|
+
# loads a script and prepares it for statically inclusion by removing
|
414
|
+
# the optional xml prologue/epilogue.
|
415
|
+
#
|
416
|
+
def load_statically_included(filename)
|
417
|
+
$log.debug "Statically including '#{filename}'" if $DBG
|
418
|
+
|
419
|
+
code = File.read(filename)
|
420
|
+
code.gsub!(/<\?xml.*\?>/, "")
|
421
|
+
code.gsub!(/<\/?root(.*?)>/m, " ");
|
422
|
+
|
423
|
+
return code
|
424
|
+
end
|
425
|
+
|
426
|
+
end # PageHandler
|
427
|
+
|
428
|
+
# = Pagescript
|
429
|
+
#
|
430
|
+
# encapsulates the script defining a specific xml page.
|
431
|
+
# effectively acts as a Generator.
|
432
|
+
#
|
433
|
+
# === Requirements:
|
434
|
+
#
|
435
|
+
# - the generated class should be a singleton, no need to stress
|
436
|
+
# the garbage collector.
|
437
|
+
# - the render method should be thread safe.
|
438
|
+
#
|
439
|
+
# == Design:
|
440
|
+
#
|
441
|
+
# - only pass request and request for simplicity
|
442
|
+
# __session can be deducted from request, and __user/__errors from
|
443
|
+
# __session.
|
444
|
+
#
|
445
|
+
# === Injection:
|
446
|
+
#
|
447
|
+
# Injection is the inclusion of subscripts in a super (parent) script.
|
448
|
+
# There are two types of inclusion, dynamic and static. In contrary to what
|
449
|
+
# you may believe, dynamix inclusion IS needed in cases where you decide
|
450
|
+
# what tou include at runtime. A synthesized "my" page is a good example.
|
451
|
+
# However, n1 overused dynamic inclusion. In most cases static inclusion
|
452
|
+
# works just fine. Another interesting optimization is when you dynamically
|
453
|
+
# include but not recursivelly. The full, recursive and dynamic include is
|
454
|
+
# really seldomly needed. However in our implementation there is no difference
|
455
|
+
# between inject_once / inject_recursive.
|
456
|
+
#
|
457
|
+
# === Class generation example:
|
458
|
+
#
|
459
|
+
# WARNING: the example may be deprecated!
|
460
|
+
#
|
461
|
+
# source script:
|
462
|
+
#
|
463
|
+
# <?xml version='1.0'?>
|
464
|
+
# <?ruby
|
465
|
+
# a = 5
|
466
|
+
# b = request["bval"]
|
467
|
+
# ?>
|
468
|
+
# <html>
|
469
|
+
# <body>
|
470
|
+
# this is it a = #{a}, b = #{b} and c = #{request["cval"]}<br/>
|
471
|
+
# cool huh?
|
472
|
+
# </body>
|
473
|
+
# </html>
|
474
|
+
#
|
475
|
+
# generated class:
|
476
|
+
#
|
477
|
+
# class PageScript__index
|
478
|
+
# def render(request)
|
479
|
+
# out = ""
|
480
|
+
# a = 5
|
481
|
+
# b = request["bval"]
|
482
|
+
# __out << %{
|
483
|
+
# <html>
|
484
|
+
# <body>
|
485
|
+
# this is it a = #{a}, b = #{b} and c = #{request["cval"]}<br/>
|
486
|
+
# cool huh?
|
487
|
+
# </body>
|
488
|
+
# </html>
|
489
|
+
# }
|
490
|
+
# return __out
|
491
|
+
# end
|
492
|
+
# end
|
493
|
+
#
|
494
|
+
# === Future:
|
495
|
+
#
|
496
|
+
# - use catbuffer for optimized appends.
|
497
|
+
#
|
498
|
+
class PageScript < Script
|
499
|
+
# <x:include xl:href='...'/> implementation
|
500
|
+
# Dynamically include ("inject") a subpage (fragment) in this page.
|
501
|
+
#
|
502
|
+
# gmosx, SOS: This is a new version for dynamic inclusion that doesnt
|
503
|
+
# generate subrequest objects. For use with new (v3) code.
|
504
|
+
#
|
505
|
+
# === Design:
|
506
|
+
#
|
507
|
+
# To keep this method simple (and as a small optimization) we used
|
508
|
+
# to not allow a query string when including. Pass the parameters
|
509
|
+
# as request arguments or parameters. Here is an example:
|
510
|
+
#
|
511
|
+
# <?r request_set_arg("max_message", 5); request["admin"] = true ?>
|
512
|
+
# <x:include xl:href='*parts/messages/view-messages.xi'/>
|
513
|
+
#
|
514
|
+
# However to support legacy code, to follow the REST design guidelines,
|
515
|
+
# and because it turned out to be easy thanks to the refactoring, the
|
516
|
+
# query string is supported. Passing params through the request object
|
517
|
+
# is suggested though (and essential to pass ruby objects). The query
|
518
|
+
# string of the included string is converted to arguments to avoid
|
519
|
+
# poluting the parent request query string.
|
520
|
+
#
|
521
|
+
# Still, the preferred way is to use the args hash to pass special
|
522
|
+
# arguments to included scripts.
|
523
|
+
# <?r request.set_arg("maxitems", 5) ?>
|
524
|
+
# <x:include xl:href="p/list.si" />
|
525
|
+
#
|
526
|
+
# If you want to include the parents query string for caching purposes
|
527
|
+
# call with parent = true!
|
528
|
+
#
|
529
|
+
# TODO: add unit test for this.
|
530
|
+
#
|
531
|
+
def __include(uri, request, parent = false)
|
532
|
+
begin
|
533
|
+
$log.devel "Including #{uri}"
|
534
|
+
|
535
|
+
# get script real path and type
|
536
|
+
script_path, type, args, qs = N::UriUtils.decode(uri)
|
537
|
+
|
538
|
+
# update the dependencies set, we only need the key as flag,
|
539
|
+
# so just insert true. Calculating the dependencies this
|
540
|
+
# way is simple and not too inefficient.
|
541
|
+
#
|
542
|
+
# @dependencies[script_path] = true
|
543
|
+
|
544
|
+
request.level += 1
|
545
|
+
request.path = script_path
|
546
|
+
|
547
|
+
# Build the cache key for this request.
|
548
|
+
# attach the query string of the parent. Needed for
|
549
|
+
# example in fragments with pagers. Do NOT do this
|
550
|
+
# by default, or excessive numbers of fragments will
|
551
|
+
# be generated.
|
552
|
+
if parent
|
553
|
+
request.fragment_hash = "#{qs}#{request.query_string}"
|
554
|
+
else
|
555
|
+
request.fragment_hash = qs
|
556
|
+
end
|
557
|
+
|
558
|
+
# add arguments passes through the query string.
|
559
|
+
request.parameters.update(args)
|
560
|
+
|
561
|
+
# TODO: select by regexp
|
562
|
+
# FIXME: hacky implementation, NO Need for this test, always
|
563
|
+
# PageHandler.
|
564
|
+
if handler = $srv_extension_map[type]
|
565
|
+
handler = handler[1]
|
566
|
+
else
|
567
|
+
$log.error "No handler for extension '#{type}'"
|
568
|
+
unless N::StringUtils.valid?(type)
|
569
|
+
$log.error "Perhaps you have a syntax error in your include statement or you didnt pass the uri"
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
fragment, script = handler.sub_process(request)
|
574
|
+
|
575
|
+
# restore parent request
|
576
|
+
request.level -= 1
|
577
|
+
|
578
|
+
# gmosx: one idea was to clear the args here to avoid a class of bugs.
|
579
|
+
# But i think it is better to trust the developer to do this, thus
|
580
|
+
# giving him greater flexibility.
|
581
|
+
# gmosx: NO !!! practice shows that it is NOT GOOD to trust the
|
582
|
+
# developer :)
|
583
|
+
# Hmm clearing the args also poses problems though :(
|
584
|
+
#
|
585
|
+
# request.args.clear()
|
586
|
+
|
587
|
+
return fragment.body
|
588
|
+
|
589
|
+
rescue ScriptHandlerError => e
|
590
|
+
# allready handled!
|
591
|
+
rescue Exception, StandardError => e
|
592
|
+
# gmosx: this block is used to catch possible errors in the inject
|
593
|
+
# implementation if we do not catch these errors here, they
|
594
|
+
# propagate to the caller that prints a NON usefull message, that
|
595
|
+
# can waste a developers time.
|
596
|
+
#
|
597
|
+
$log.error "error in INCLUDE IMPLEMENTATION while including: #{uri}"
|
598
|
+
# gmosx: too noisy, only uncomment if you get the above error!!
|
599
|
+
$log.error pp_exception(e)
|
600
|
+
|
601
|
+
# Following our design goal of chaos reduction (ie small changes
|
602
|
+
# should result in small results) we drink the exception and
|
603
|
+
# output an error flag. So the rest of the page is rendered and
|
604
|
+
# the server error handler is NOT triggered.
|
605
|
+
end
|
606
|
+
|
607
|
+
return "(error)"
|
608
|
+
end
|
609
|
+
|
610
|
+
end # PageScript
|
611
|
+
|
612
|
+
end; end # module
|