tap-server 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|