patchbay 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/patchbay.rb +231 -0
  2. 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: []