patchbay 0.0.1.pre
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/lib/patchbay.rb +231 -0
- metadata +46 -0
data/lib/patchbay.rb
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'rack/mime'
|
2
|
+
require 'rack/handler'
|
3
|
+
|
4
|
+
class Patchbay
|
5
|
+
class Route < Struct.new(:handler, :url_parts, :verb)
|
6
|
+
end
|
7
|
+
|
8
|
+
NoRoute_Message = <<EOT
|
9
|
+
<html><body>
|
10
|
+
<h1>404</h1><p>Patchbay: Routing Error</p>
|
11
|
+
</body></html>
|
12
|
+
EOT
|
13
|
+
|
14
|
+
Forbidden_Message = <<EOT
|
15
|
+
<html><body>
|
16
|
+
<h1>403</h1>
|
17
|
+
<p>Forbidden</p>
|
18
|
+
</body></html>
|
19
|
+
EOT
|
20
|
+
|
21
|
+
Exception_Message = <<EOT
|
22
|
+
<html><body>
|
23
|
+
<h1>500</h1>
|
24
|
+
<p>Internal Server Error</p>
|
25
|
+
</body></html>
|
26
|
+
EOT
|
27
|
+
|
28
|
+
def path_is_subdir_of(path_to_test, root_path)
|
29
|
+
File.fnmatch(File.join(root_path, '**'), File.realpath(path_to_test))
|
30
|
+
end
|
31
|
+
|
32
|
+
class Router
|
33
|
+
def initialize
|
34
|
+
@routes = []
|
35
|
+
end
|
36
|
+
|
37
|
+
def matches?(route, url_parts)
|
38
|
+
if route.url_parts.size != url_parts.size
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
|
42
|
+
params = { }
|
43
|
+
|
44
|
+
route.url_parts.zip(url_parts) do |parts|
|
45
|
+
route_url_part = parts[0]
|
46
|
+
url_part = parts[1]
|
47
|
+
|
48
|
+
if route_url_part =~ /:(.+)/
|
49
|
+
params[$1.intern] = url_part
|
50
|
+
else
|
51
|
+
if route_url_part != url_part
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
return params
|
58
|
+
end
|
59
|
+
|
60
|
+
def match(verb, url)
|
61
|
+
parts = url.gsub(/^\/+/,'').split('/')
|
62
|
+
|
63
|
+
@routes.each do |route|
|
64
|
+
if route.verb == verb
|
65
|
+
result = matches?(route, parts)
|
66
|
+
if result
|
67
|
+
return [ route.handler, result ]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
return [ nil, nil ]
|
73
|
+
end
|
74
|
+
|
75
|
+
def add(verb, route, handler)
|
76
|
+
parts = route.gsub(/^\/+/,'').split('/')
|
77
|
+
routeObj = Route.new
|
78
|
+
routeObj.url_parts = parts
|
79
|
+
routeObj.handler = handler
|
80
|
+
routeObj.verb = verb
|
81
|
+
@routes << routeObj
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.router
|
86
|
+
@router ||= Router.new
|
87
|
+
@router
|
88
|
+
end
|
89
|
+
|
90
|
+
def router
|
91
|
+
self.class.router
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.get(route, &action)
|
95
|
+
router.add('GET', route, action)
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.put(route, &action)
|
99
|
+
router.add('PUT', route, action)
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.post(route, &action)
|
103
|
+
router.add('POST', route, action)
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.delete(route, &action)
|
107
|
+
router.add('DELETE', route, action)
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.files_dir
|
111
|
+
@files_dir
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.files_dir=(new_dir)
|
115
|
+
@files_dir = File.realpath(new_dir)
|
116
|
+
end
|
117
|
+
|
118
|
+
def files_dir
|
119
|
+
self.class.files_dir
|
120
|
+
end
|
121
|
+
|
122
|
+
def environment
|
123
|
+
@environment
|
124
|
+
end
|
125
|
+
|
126
|
+
def params
|
127
|
+
@params
|
128
|
+
end
|
129
|
+
|
130
|
+
def handle_file
|
131
|
+
url_parts = environment['PATH_INFO'].split('/')
|
132
|
+
file_path = File.join(files_dir, url_parts)
|
133
|
+
|
134
|
+
if File.exists?(file_path) and path_is_subdir_of(file_path, files_dir)
|
135
|
+
if File.readable?(file_path)
|
136
|
+
send_file(file_path)
|
137
|
+
else
|
138
|
+
handle_forbidden
|
139
|
+
end
|
140
|
+
elsif File.exists?(file_path)
|
141
|
+
handle_no_route
|
142
|
+
else
|
143
|
+
handle_no_route
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def send_file(file_path)
|
148
|
+
mime_type = Rack::Mime.mime_type(File.extname(file_path))
|
149
|
+
@response = [200, { "Content-Type" => mime_type }, File.new(file_path)]
|
150
|
+
end
|
151
|
+
|
152
|
+
def call(env)
|
153
|
+
# find a handler for this request
|
154
|
+
handler, route_params = router.match(env['REQUEST_METHOD'], env['PATH_INFO'])
|
155
|
+
|
156
|
+
@params = route_params
|
157
|
+
|
158
|
+
@environment = env
|
159
|
+
|
160
|
+
@response = nil
|
161
|
+
|
162
|
+
begin
|
163
|
+
if handler
|
164
|
+
self.instance_eval(&handler)
|
165
|
+
else
|
166
|
+
if files_dir
|
167
|
+
handle_file
|
168
|
+
else
|
169
|
+
handle_no_route
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
unless @response
|
174
|
+
fail "No response was generated"
|
175
|
+
end
|
176
|
+
rescue Exception => e
|
177
|
+
handle_exception(e)
|
178
|
+
end
|
179
|
+
|
180
|
+
@response
|
181
|
+
end
|
182
|
+
|
183
|
+
def render(options={})
|
184
|
+
if @response
|
185
|
+
fail "can only render once per request"
|
186
|
+
end
|
187
|
+
|
188
|
+
@response = [200, { "Content-Type" => "text/html" }, []]
|
189
|
+
options.each do |key, value|
|
190
|
+
case key
|
191
|
+
when :error
|
192
|
+
@response[0] = value
|
193
|
+
else
|
194
|
+
@response[1]["Content-Type"] = Rack::Mime.mime_type("."+key.to_s)
|
195
|
+
@response[2] << value
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def handle_no_route
|
201
|
+
@response = [404, { "Content-Type" => "text/html" }, [NoRoute_Message]]
|
202
|
+
end
|
203
|
+
|
204
|
+
def handle_exception(e)
|
205
|
+
@response = [500, { "Content-Type" => "text/html" }, [Exception_Message]]
|
206
|
+
$stderr.puts e.inspect
|
207
|
+
e.backtrace.each { |bt| $stderr.puts bt }
|
208
|
+
end
|
209
|
+
|
210
|
+
def handle_forbidden
|
211
|
+
@response = [403, { "Content-Type" => "text/html" }, [Forbidden_Message]]
|
212
|
+
end
|
213
|
+
|
214
|
+
def run(options={})
|
215
|
+
handler = find_rack_handler
|
216
|
+
handler_name = handler.name.gsub(/.*::/,'')
|
217
|
+
$stderr.puts "Patchbay starting, using #{handler_name}"
|
218
|
+
handler.run(self, options)
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
def find_rack_handler
|
223
|
+
servers = %w/thin mongrel webrick/
|
224
|
+
servers.each do |server|
|
225
|
+
begin
|
226
|
+
return Rack::Handler.get(server)
|
227
|
+
rescue LoadError
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
metadata
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: patchbay
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrew Armenia
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-13 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! "Patchbay is the web framework for non-web apps. \nIt's designed for
|
15
|
+
simplicity, minimalism, and easy integration.\n"
|
16
|
+
email: andrew@asquaredlabs.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/patchbay.rb
|
22
|
+
homepage: http://rubygems.org/gems/patchbay
|
23
|
+
licenses: []
|
24
|
+
post_install_message:
|
25
|
+
rdoc_options: []
|
26
|
+
require_paths:
|
27
|
+
- lib
|
28
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>'
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.3.1
|
40
|
+
requirements: []
|
41
|
+
rubyforge_project:
|
42
|
+
rubygems_version: 1.8.6
|
43
|
+
signing_key:
|
44
|
+
specification_version: 3
|
45
|
+
summary: Embed HTTP APIs in non-web apps easily
|
46
|
+
test_files: []
|