tap-server 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History +5 -0
- data/cmd/server.rb +36 -8
- data/lib/tap/controller/rest_routes.rb +77 -0
- data/lib/tap/controller/utils.rb +29 -0
- data/lib/tap/controller.rb +244 -145
- data/lib/tap/controllers/app.rb +97 -55
- data/lib/tap/controllers/data.rb +132 -0
- data/lib/tap/controllers/schema.rb +137 -223
- data/lib/tap/controllers/server.rb +70 -27
- data/lib/tap/server/data.rb +178 -0
- data/lib/tap/server/runner.rb +71 -0
- data/lib/tap/server/server_error.rb +35 -0
- data/lib/tap/server.rb +60 -275
- data/lib/tap/tasks/echo.rb +39 -6
- data/lib/tap/tasks/render.rb +65 -0
- data/public/stylesheets/tap.css +15 -2
- data/views/configurable/_config.erb +1 -0
- data/views/configurable/_configs.erb +28 -0
- data/views/configurable/_flag.erb +2 -0
- data/views/configurable/_list_select.erb +8 -0
- data/views/configurable/_select.erb +6 -0
- data/views/configurable/_switch.erb +2 -0
- data/views/layout.erb +1 -1
- data/views/object/obj.erb +1 -0
- data/views/tap/controllers/app/_action.erb +3 -0
- data/views/tap/controllers/app/build.erb +18 -0
- data/views/tap/controllers/app/enque.erb +13 -0
- data/views/tap/controllers/app/info.erb +20 -7
- data/views/tap/controllers/data/_controls.erb +21 -0
- data/views/tap/controllers/data/_index_entry.erb +1 -0
- data/views/tap/controllers/data/_upload.erb +8 -0
- data/views/tap/controllers/data/entry.erb +8 -0
- data/views/tap/controllers/data/index.erb +14 -0
- data/views/tap/controllers/schema/_build.erb +6 -0
- data/views/tap/controllers/schema/_index_entry.erb +6 -0
- data/views/tap/controllers/schema/entry.erb +135 -0
- data/views/tap/controllers/schema/join.erb +6 -4
- data/views/tap/controllers/schema/task.erb +6 -0
- data/views/tap/controllers/server/access.erb +4 -0
- data/views/tap/controllers/server/admin.erb +21 -0
- data/views/tap/controllers/server/help.erb +23 -0
- data/views/tap/controllers/server/index.erb +43 -3
- data/views/tap/task/input.erb +17 -0
- data/views/tap/tasks/load/input.erb +11 -0
- metadata +35 -17
- data/lib/tap/server_error.rb +0 -34
- data/lib/tap/support/persistence.rb +0 -71
- data/lib/tap/tasks/server.rb +0 -30
- data/views/tap/controllers/app/index.erb +0 -44
- data/views/tap/controllers/schema/config/default.erb +0 -4
- data/views/tap/controllers/schema/config/flag.erb +0 -3
- data/views/tap/controllers/schema/config/switch.erb +0 -6
- data/views/tap/controllers/schema/configurations.erb +0 -27
- data/views/tap/controllers/schema/node.erb +0 -47
- data/views/tap/controllers/schema/preview.erb +0 -3
- data/views/tap/controllers/schema/round.erb +0 -30
- data/views/tap/controllers/schema/schema.erb +0 -28
- data/views/tap/tasks/echo/result.html +0 -1
@@ -0,0 +1,178 @@
|
|
1
|
+
module Tap
|
2
|
+
class Server
|
3
|
+
|
4
|
+
# A very simple wrapper for root providing a CRUD interface for reading and
|
5
|
+
# writing files. Data ids may be integers (if you want to pretend Data is
|
6
|
+
# a database), or they can be relative paths.
|
7
|
+
class Data < Tap::Root
|
8
|
+
|
9
|
+
attr_reader :cache
|
10
|
+
|
11
|
+
def initialize(config_or_dir=Dir.pwd)
|
12
|
+
@cache = Hash.new([])
|
13
|
+
|
14
|
+
if config_or_dir.kind_of?(Tap::Root)
|
15
|
+
config_or_dir = config_or_dir.config.to_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
super(config_or_dir)
|
19
|
+
end
|
20
|
+
|
21
|
+
# A restricted version of the original. Path raises an error if the
|
22
|
+
# final path is not relative to als.
|
23
|
+
def entry_path(als, id)
|
24
|
+
id = id.to_s
|
25
|
+
if id.empty?
|
26
|
+
raise "no id specified"
|
27
|
+
end
|
28
|
+
|
29
|
+
path = self.path(als, id)
|
30
|
+
unless relative?(als, path)
|
31
|
+
raise "not a subpath: #{id.inspect} (#{als.inspect})"
|
32
|
+
end
|
33
|
+
|
34
|
+
path
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a list of entry paths.
|
38
|
+
def entries(als)
|
39
|
+
glob(als).select do |path|
|
40
|
+
File.file?(path)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns a list of existing ids.
|
45
|
+
def index(als)
|
46
|
+
entries(als).collect do |path|
|
47
|
+
relative_path(als, path)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns an available integer id, usually the number of entries in self,
|
52
|
+
# but a random integer is generated if that number is taken.
|
53
|
+
def next_id(als)
|
54
|
+
# try the next in the sequence
|
55
|
+
id = entries(als).length
|
56
|
+
|
57
|
+
# if that already exists, go for a random id
|
58
|
+
while find(als, id)
|
59
|
+
id = rand(id * 10000)
|
60
|
+
end
|
61
|
+
|
62
|
+
id
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the path for the specified entry, if it exists. Returns nil
|
66
|
+
# if no such entry can be found.
|
67
|
+
def find(als, id)
|
68
|
+
return nil unless id
|
69
|
+
|
70
|
+
path = entry_path(als, id)
|
71
|
+
File.file?(path) ? path : nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Creates an entry (ie a file) for the specified id. Yields the open
|
75
|
+
# file to the block if given, otherwise the file will be created
|
76
|
+
# without content. Returns the path to the file.
|
77
|
+
#
|
78
|
+
# Raises an error if the file already exists.
|
79
|
+
def create(als, id)
|
80
|
+
path = non_existant_path(als, id)
|
81
|
+
create!(path) {|io| yield(io) if block_given? }
|
82
|
+
end
|
83
|
+
|
84
|
+
# Reads and returns the data for the specified entry, or nil if
|
85
|
+
# the entry doesn't exist.
|
86
|
+
def read(als, id)
|
87
|
+
path = find(als, id)
|
88
|
+
path ? File.read(path) : nil
|
89
|
+
end
|
90
|
+
|
91
|
+
# Overwrites the data for the specified entry. A block must be given to
|
92
|
+
# provide the new content; an error is raised if the entry does not
|
93
|
+
# already exist.
|
94
|
+
def update(als, id)
|
95
|
+
path = existing_path(als, id)
|
96
|
+
create!(path) {|io| yield(io) }
|
97
|
+
end
|
98
|
+
|
99
|
+
# Removes the specified entry (ie file), if it exists. Returns true if
|
100
|
+
# the file was removed and false otherwise.
|
101
|
+
def destroy(als, id)
|
102
|
+
if path = find(als, id)
|
103
|
+
FileUtils.rm(path)
|
104
|
+
cache[als].delete(id)
|
105
|
+
true
|
106
|
+
else
|
107
|
+
false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def create_or_update(als, id)
|
112
|
+
path = entry_path(als, id)
|
113
|
+
create!(path) {|io| yield(io) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def import(als, upload, id=nil)
|
117
|
+
id = upload[:filename] unless id && !id.empty?
|
118
|
+
path = non_existant_path(als, id)
|
119
|
+
|
120
|
+
prepare(path)
|
121
|
+
FileUtils.mv(upload[:tempfile].path, path)
|
122
|
+
path
|
123
|
+
end
|
124
|
+
|
125
|
+
def move(als, id, new_id)
|
126
|
+
path = existing_path(als, id)
|
127
|
+
new_path = non_existant_path(als, new_id)
|
128
|
+
|
129
|
+
prepare(new_path)
|
130
|
+
FileUtils.mv(path, new_path)
|
131
|
+
|
132
|
+
if cache[als].include?(id)
|
133
|
+
cache[als].delete(id)
|
134
|
+
cache[als] << new_id
|
135
|
+
end
|
136
|
+
|
137
|
+
new_id
|
138
|
+
end
|
139
|
+
|
140
|
+
def copy(als, id, new_id)
|
141
|
+
path = existing_path(als, id)
|
142
|
+
new_path = non_existant_path(als, new_id)
|
143
|
+
|
144
|
+
prepare(new_path)
|
145
|
+
FileUtils.copy(path, new_path)
|
146
|
+
new_id
|
147
|
+
end
|
148
|
+
|
149
|
+
protected
|
150
|
+
|
151
|
+
# helper to optimize the creation of entries when path is already
|
152
|
+
# resolved (using the instance prepare requires a second path
|
153
|
+
# resolution)
|
154
|
+
def create!(path) # :nodoc:
|
155
|
+
Utils.prepare(path) {|io| yield(io) }
|
156
|
+
end
|
157
|
+
|
158
|
+
# like find but raises an error if the path doesn't exist
|
159
|
+
def existing_path(als, id)
|
160
|
+
path = entry_path(als, id)
|
161
|
+
unless File.exists?(path)
|
162
|
+
raise "does not exist: #{id.inspect} (#{als.inspect})"
|
163
|
+
end
|
164
|
+
path
|
165
|
+
end
|
166
|
+
|
167
|
+
# like find but raises an error if the path exists
|
168
|
+
def non_existant_path(als, id) # :nodoc:
|
169
|
+
path = entry_path(als, id)
|
170
|
+
if File.exists?(path)
|
171
|
+
raise "already exists: #{id.inspect} (#{als.inspect})"
|
172
|
+
end
|
173
|
+
path
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
class Server
|
5
|
+
module Runner
|
6
|
+
include Rack::Utils
|
7
|
+
include Configurable
|
8
|
+
|
9
|
+
config :servers, %w[thin mongrel webrick], { # the preferred server handlers
|
10
|
+
:long => :server
|
11
|
+
}, &c.list
|
12
|
+
|
13
|
+
config :host, 'localhost', &c.string # the server host
|
14
|
+
config :port, 8080, &c.integer_or_nil # the server port
|
15
|
+
|
16
|
+
attr_reader :handler
|
17
|
+
|
18
|
+
def initialize(config={})
|
19
|
+
@handler = nil
|
20
|
+
initialize_config(config)
|
21
|
+
end
|
22
|
+
|
23
|
+
def running?
|
24
|
+
@handler != nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# Runs self as configured, on the specified server, host, and port. Use an
|
28
|
+
# INT signal to interrupt.
|
29
|
+
def run!(rack_app, handler=rack_handler)
|
30
|
+
return self if running?
|
31
|
+
|
32
|
+
handler.run(rack_app, :Host => host, :Port => port) do |handler_instance|
|
33
|
+
@handler = handler_instance
|
34
|
+
trap(:INT) { stop! }
|
35
|
+
yield if block_given?
|
36
|
+
end
|
37
|
+
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
# Stops the server if running (ie a handler is set).
|
42
|
+
def stop!
|
43
|
+
if running?
|
44
|
+
# Use thins' hard #stop! if available, otherwise just #stop
|
45
|
+
@handler.respond_to?(:stop!) ? @handler.stop! : @handler.stop
|
46
|
+
@handler = nil
|
47
|
+
|
48
|
+
yield if block_given?
|
49
|
+
end
|
50
|
+
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
# Looks up and returns the first available Rack::Handler as listed in the
|
57
|
+
# servers configuration. (Note rack_handler returns a handler class, not
|
58
|
+
# an instance). Adapted from Sinatra.detect_rack_handler
|
59
|
+
def rack_handler # :nodoc:
|
60
|
+
servers.each do |server_name|
|
61
|
+
begin
|
62
|
+
return Rack::Handler.get(server_name)
|
63
|
+
rescue LoadError
|
64
|
+
rescue NameError
|
65
|
+
end
|
66
|
+
end
|
67
|
+
raise "Server handler (#{servers.join(',')}) not found."
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Tap
|
2
|
+
class Server
|
3
|
+
# A special type of error used for specifiying controller errors.
|
4
|
+
class ServerError < RuntimeError
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# A helper to format a non-ServerError into a ServerError response.
|
8
|
+
def response(err)
|
9
|
+
new("500 #{err.class}: #{err.message}\n#{err.backtrace.join("\n")}").response
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# The error status
|
14
|
+
attr_reader :status
|
15
|
+
|
16
|
+
# Headers for the error response
|
17
|
+
attr_reader :headers
|
18
|
+
|
19
|
+
# The error response body
|
20
|
+
attr_reader :body
|
21
|
+
|
22
|
+
def initialize(body="500 Server Error", status=500, headers={'Content-Type' => 'text/plain'})
|
23
|
+
@body = body
|
24
|
+
@status = status
|
25
|
+
@headers = headers
|
26
|
+
super(body)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Formats self as a rack response array (ie [status, headers, body]).
|
30
|
+
def response
|
31
|
+
[status, headers, [body]]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/tap/server.rb
CHANGED
@@ -1,252 +1,76 @@
|
|
1
|
-
require 'rack'
|
2
|
-
require 'rack/mock'
|
3
|
-
|
4
1
|
require 'tap'
|
5
|
-
require 'tap/
|
2
|
+
require 'tap/server/data'
|
3
|
+
require 'tap/server/runner'
|
4
|
+
require 'tap/server/server_error'
|
6
5
|
|
7
6
|
module Tap
|
8
|
-
|
9
|
-
controllers = Support::ConstantManifest.new('controller')
|
10
|
-
env.load_paths.each do |path_root|
|
11
|
-
controllers.register(path_root, '**/*.rb')
|
12
|
-
end
|
13
|
-
controllers
|
14
|
-
end
|
15
|
-
|
16
|
-
# :::-
|
17
|
-
# Server is a Rack application that dispatches calls to other Rack apps, most
|
18
|
-
# commonly a Tap::Controller.
|
19
|
-
#
|
20
|
-
# == Routes
|
21
|
-
#
|
22
|
-
# Routing is fixed and very simple:
|
23
|
-
#
|
24
|
-
# /:controller/path/to/resource
|
25
|
-
#
|
26
|
-
# Server dispatches the request to the controller keyed by :controller after
|
27
|
-
# shifting the key from PATH_INFO to SCRIPT_NAME.
|
28
|
-
#
|
29
|
-
# server = Server.new
|
30
|
-
# server.controllers['sample'] = lambda do |env|
|
31
|
-
# [200, {}, ["Sample got #{env['SCRIPT_NAME']} : #{env['PATH_INFO']}"]]
|
32
|
-
# end
|
33
|
-
#
|
34
|
-
# req = Rack::MockRequest.new(server)
|
35
|
-
# req.get('/sample/path/to/resource').body # => "Sample got /sample : /path/to/resource"
|
36
|
-
#
|
37
|
-
# Server automatically maps unknown keys to controllers discovered via the
|
38
|
-
# env.controllers manifest. The only requirement is that the controller
|
39
|
-
# constant is a Rack application. For instance:
|
40
|
-
#
|
41
|
-
# # [lib/example.rb] => %q{
|
42
|
-
# # ::controller
|
43
|
-
# # class Example
|
44
|
-
# # def self.call(env)
|
45
|
-
# # [200, {}, ["Example got #{env['SCRIPT_NAME']} : #{env['PATH_INFO']}"]]
|
46
|
-
# # end
|
47
|
-
# # end
|
48
|
-
# # }
|
49
|
-
#
|
50
|
-
# req.get('/example/path/to/resource').body # => "Example got /example : /path/to/resource"
|
51
|
-
#
|
52
|
-
# If desired, controllers can be set with aliases to map a path key to a
|
53
|
-
# lookup key.
|
54
|
-
#
|
55
|
-
# server.controllers['sample'] = 'example'
|
56
|
-
# req.get('/sample/path/to/resource').body # => "Example got /sample : /path/to/resource"
|
57
|
-
#
|
58
|
-
# If no controller can be found, the request is routed using the
|
59
|
-
# default_controller_key and the request is NOT adjusted.
|
60
|
-
#
|
61
|
-
# server.default_controller_key = 'app'
|
62
|
-
# server.controllers['app'] = lambda do |env|
|
63
|
-
# [200, {}, ["App got #{env['SCRIPT_NAME']} : #{env['PATH_INFO']}"]]
|
64
|
-
# end
|
65
|
-
#
|
66
|
-
# req.get('/unknown/path/to/resource').body # => "App got : /unknown/path/to/resource"
|
67
|
-
#
|
68
|
-
# In development mode, the controller constant is removed and the constant
|
69
|
-
# require path is reloaded each time it gets called. This system allows many
|
70
|
-
# web frameworks to be hooked into a Tap server.
|
71
|
-
#
|
72
|
-
# :::+
|
7
|
+
# ::configurable
|
73
8
|
class Server
|
74
|
-
|
75
|
-
|
76
|
-
# Instantiates a Server in the specified directory, configured as
|
77
|
-
# specified in root/server.yml. If shutdown_key is specified, a
|
78
|
-
# random shutdown key will be generated and set on the sever.
|
79
|
-
#
|
80
|
-
def instantiate(root, shutdown_key=false)
|
81
|
-
# setup the server directory
|
82
|
-
root = File.expand_path(root)
|
83
|
-
FileUtils.mkdir_p(root) unless File.exists?(root)
|
84
|
-
|
85
|
-
# initialize the server
|
86
|
-
app = Tap::App.instance
|
87
|
-
env = Tap::Exe.instantiate(root)
|
88
|
-
env.activate
|
89
|
-
config = Configurable::Utils.load_file(env.root['server.yml'])
|
90
|
-
|
91
|
-
server = new(env, app, config)
|
92
|
-
server.config[:shutdown_key] = rand(1000000) if shutdown_key
|
93
|
-
server
|
94
|
-
end
|
95
|
-
|
96
|
-
# Runs the server
|
97
|
-
def run(server)
|
98
|
-
cookie_server = Rack::Session::Pool.new(server)
|
99
|
-
server.run!
|
100
|
-
end
|
101
|
-
end
|
9
|
+
include Runner
|
102
10
|
|
103
|
-
|
104
|
-
|
11
|
+
# Server implements a secret for HTTP administration of the server (ex
|
12
|
+
# remote shutdown). Under many circumstances this functionality is
|
13
|
+
# undesirable; specify a nil secret, the default, to prevent remote
|
14
|
+
# administration.
|
15
|
+
config :secret, nil, &c.string_or_nil # the admin secret
|
105
16
|
|
106
|
-
config :
|
107
|
-
config :servers, %w[thin mongrel webrick], &c.list # a list of preferred handlers
|
108
|
-
config :host, 'localhost', &c.string # the server host
|
109
|
-
config :port, 8080, &c.integer # the server port
|
17
|
+
config :development, false, &c.flag
|
110
18
|
|
111
|
-
|
112
|
-
# to a Rack application. Typically controllers is used to specify aliases
|
113
|
-
# when the defaults are not preferable.
|
114
|
-
config :controllers, {}
|
19
|
+
config :router, true, &c.switch
|
115
20
|
|
116
|
-
|
21
|
+
nest :env, Tap::Env, :type => :hidden
|
117
22
|
|
118
|
-
|
119
|
-
# to a controller
|
120
|
-
#--
|
121
|
-
# Set to nil to force controller mapping?
|
122
|
-
config :default_controller_key, 'app'
|
23
|
+
nest :app, Tap::App, :type => :hidden
|
123
24
|
|
124
|
-
|
125
|
-
# via an HTTP request to the app/shutdown method. Remote shutdown is
|
126
|
-
# useful when the user is running a local server (especially from a
|
127
|
-
# background process). Under many circumstances remote shutdown is
|
128
|
-
# undesirable; specify a nil shutdown key, the default, to turn off
|
129
|
-
# shutdown.
|
130
|
-
config :shutdown_key, nil, &c.integer_or_nil # specifies a public shutdown key
|
25
|
+
nest :data, Data, :type => :hidden
|
131
26
|
|
132
|
-
|
133
|
-
attr_reader :handler
|
27
|
+
attr_accessor :controller
|
134
28
|
|
135
|
-
|
136
|
-
@env = env
|
137
|
-
@app = app
|
138
|
-
@cache = {}
|
139
|
-
@handler = nil
|
140
|
-
initialize_config(config)
|
141
|
-
end
|
29
|
+
attr_accessor :thread
|
142
30
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
handler.run self, :Host => host, :Port => port do |handler_instance|
|
148
|
-
@handler = handler_instance
|
149
|
-
trap(:INT) { stop! }
|
150
|
-
end
|
31
|
+
def initialize(controller=nil, config={})
|
32
|
+
@controller = controller
|
33
|
+
@thread = nil
|
34
|
+
super(config)
|
151
35
|
end
|
152
36
|
|
153
|
-
#
|
154
|
-
#
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
handler.respond_to?(:stop!) ? handler.stop! : handler.stop
|
159
|
-
@handler = nil
|
160
|
-
false
|
161
|
-
else
|
162
|
-
true
|
163
|
-
end
|
37
|
+
# Returns true if input is equal to the secret, if a secret is set. Used
|
38
|
+
# to test if a particular request has rights to a remote administrative
|
39
|
+
# action.
|
40
|
+
def admin?(input)
|
41
|
+
secret != nil && input == secret
|
164
42
|
end
|
165
43
|
|
166
|
-
|
167
|
-
|
168
|
-
def initialize_session
|
169
|
-
id = 0
|
170
|
-
session_app = app(id)
|
171
|
-
session_root = root(id)
|
172
|
-
|
173
|
-
# setup expiration information...
|
174
|
-
|
175
|
-
# setup a session log
|
176
|
-
log_path = session_root.prepare(:log, 'session.log')
|
177
|
-
session_app.logger = Logger.new(log_path)
|
178
|
-
session_app.on_complete do |_result|
|
179
|
-
# find the template
|
180
|
-
class_name = _result.key.class.to_s.underscore
|
181
|
-
pattern = "#{class_name}/result\.*"
|
182
|
-
template = nil
|
183
|
-
env.each do |e|
|
184
|
-
templates = e.root.glob(views_dir, pattern)
|
185
|
-
unless templates.empty?
|
186
|
-
template = templates[0]
|
187
|
-
break
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
if template
|
192
|
-
extname = File.extname(template)
|
193
|
-
env.root.prepare(:results, id.to_s, "#{class_name}#{extname}") do |file|
|
194
|
-
file << Support::Templater.new(File.read(template)).build(:_result => _result)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end unless session_app.on_complete_block
|
198
|
-
|
199
|
-
id
|
200
|
-
end
|
201
|
-
|
202
|
-
# Returns the session-specific App, or the server app if id is nil.
|
203
|
-
def app(id=nil)
|
204
|
-
@app
|
205
|
-
end
|
206
|
-
|
207
|
-
# Returns the session-specific Root, or the server env.root if id is nil.
|
208
|
-
def root(id=nil)
|
209
|
-
@env.root
|
210
|
-
end
|
211
|
-
|
212
|
-
# Returns a uri mapping to the specified controller and action. Parameters
|
213
|
-
# may be specified; they are built as a query and attached to the uri as
|
214
|
-
# normal.
|
215
|
-
#
|
216
|
-
# Currenlty uri does not map the controller to a minipath, but in the
|
217
|
-
# future it will.
|
218
|
-
def uri(controller=nil, action=nil, params={})
|
219
|
-
query = build_query(params)
|
220
|
-
uri = ["http://#{host}:#{port}", escape(controller), action].compact.join("/")
|
221
|
-
query.empty? ? uri : "#{uri}?#{query}"
|
222
|
-
end
|
223
|
-
|
224
|
-
# The {Rack}[http://rack.rubyforge.org/doc/] interface method.
|
225
|
-
def call(rack_env)
|
226
|
-
if development?
|
227
|
-
env.reset
|
228
|
-
@cache.clear
|
229
|
-
end
|
44
|
+
def route(rack_env)
|
45
|
+
return controller unless router
|
230
46
|
|
231
47
|
# route to a controller
|
232
|
-
blank,
|
233
|
-
controller =
|
48
|
+
blank, path, path_info = rack_env['PATH_INFO'].split("/", 3)
|
49
|
+
controller = lookup_controller(unescape(path))
|
234
50
|
|
235
51
|
if controller
|
236
|
-
# adjust
|
237
|
-
rack_env['SCRIPT_NAME'] = ["#{rack_env['SCRIPT_NAME'].chomp('/')}/#{
|
52
|
+
# adjust rack_env if route routes to a controller
|
53
|
+
rack_env['SCRIPT_NAME'] = ["#{rack_env['SCRIPT_NAME'].chomp('/')}/#{path}"]
|
238
54
|
rack_env['PATH_INFO'] = ["/#{path_info}"]
|
239
55
|
else
|
240
|
-
# use default controller
|
241
|
-
controller =
|
242
|
-
|
243
|
-
unless controller
|
244
|
-
raise ServerError.new("404 Error: could not route to controller", 404)
|
245
|
-
end
|
56
|
+
# use default controller
|
57
|
+
controller = self.controller
|
58
|
+
path = nil
|
246
59
|
end
|
247
60
|
|
61
|
+
rack_env['tap.controller_path'] = path
|
62
|
+
controller
|
63
|
+
end
|
64
|
+
|
65
|
+
# The {Rack}[http://rack.rubyforge.org/doc/] interface method.
|
66
|
+
def call(rack_env)
|
248
67
|
# handle the request
|
249
68
|
rack_env['tap.server'] = self
|
69
|
+
|
70
|
+
unless controller = route(rack_env)
|
71
|
+
raise ServerError.new("404 Error: could not route to controller", 404)
|
72
|
+
end
|
73
|
+
|
250
74
|
controller.call(rack_env)
|
251
75
|
rescue ServerError
|
252
76
|
$!.response
|
@@ -254,66 +78,27 @@ module Tap
|
|
254
78
|
ServerError.response($!)
|
255
79
|
end
|
256
80
|
|
257
|
-
|
258
|
-
|
259
|
-
env.search(dir, path) {|file| File.file?(file) }
|
81
|
+
def run!
|
82
|
+
super(self)
|
260
83
|
end
|
261
84
|
|
262
85
|
protected
|
263
86
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
servers.each do |server_name|
|
274
|
-
begin
|
275
|
-
return Rack::Handler.get(server_name)
|
276
|
-
rescue LoadError
|
277
|
-
rescue NameError
|
278
|
-
end
|
279
|
-
end
|
280
|
-
raise "Server handler (#{servers.join(',')}) not found."
|
281
|
-
end
|
282
|
-
|
283
|
-
# a helper method for routing a key to a controller
|
284
|
-
def lookup(key) # :nodoc:
|
285
|
-
return @cache[key] if @cache.has_key?(key)
|
286
|
-
minikey = controllers[key] || key
|
287
|
-
|
288
|
-
# return registered controllers
|
289
|
-
if minikey.respond_to?(:call)
|
290
|
-
@cache[key] = minikey
|
291
|
-
return minikey
|
292
|
-
end
|
293
|
-
|
294
|
-
# return if no controller can be found
|
295
|
-
unless const = env.controllers.search(minikey)
|
296
|
-
@cache[key] = nil
|
297
|
-
return nil
|
298
|
-
end
|
299
|
-
|
300
|
-
# load the require_path in dev mode so that
|
301
|
-
# controllers will be reloaded each time
|
302
|
-
if development? && const.require_path
|
303
|
-
parent = if const.nesting.empty?
|
304
|
-
Object
|
87
|
+
def lookup_controller(key) # :nodoc:
|
88
|
+
if development
|
89
|
+
# unload the controller in development mode so that
|
90
|
+
# controllers will be reloaded each request
|
91
|
+
|
92
|
+
env.reset
|
93
|
+
if const = env.seek(:controller, key)
|
94
|
+
const.unload
|
95
|
+
const.constantize
|
305
96
|
else
|
306
|
-
|
97
|
+
nil
|
307
98
|
end
|
308
|
-
|
309
|
-
|
310
|
-
parent.send(:remove_const, const.const_name)
|
311
|
-
end
|
312
|
-
|
313
|
-
load const.require_path
|
99
|
+
else
|
100
|
+
env[:controller][key]
|
314
101
|
end
|
315
|
-
|
316
|
-
@cache[key] = const.constantize
|
317
102
|
end
|
318
103
|
end
|
319
104
|
end
|