oaf 0.3.0 → 0.3.1

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MTc3ZjQwMGU4N2M5MDkyZGNhN2JkY2YxNWYwN2Q3MWUyNzRkZWI4OA==
4
+ Y2M2YmMyNGY2Zjg4MmU2MWJjN2Y3ZDc0MzhjMDMxZmEyZjRkY2RkNw==
5
5
  data.tar.gz: !binary |-
6
- NTg3YWZjMTYwYjcyMmI1NDU3M2QxOGI5YmM0MjliOTcwNDk3Nzg0NQ==
6
+ ZjY4YTM5NjdiYzg3YzA0NTk4NmFjMzdiOGMzYzYwYmZjZWM0NWJkMA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MGM5M2IzOTMwZDhkNTUyZDBiMTE1YjFiYjM2Y2ExNmVhZTQwNDBjZjQxNGU5
10
- NDUzNWNmZmQwMzFmZTI0NDc3NjdlY2FhNDNlNWFhNzFhZmI5Zjc5NzkxZmE5
11
- M2U3YmE5ZWZkMjNmYTBiZThkMjRkMDE2OWVjMWU0OWVkNDk3MzY=
9
+ OWQ0NzRhMGJiYjYxNGQxNmJmNjFmZTBiMDRhODc4NmU0ZDFjNGQyZTExZDI2
10
+ ZjJhMDkyM2NkMGRhZmU0NzZmYWY2M2IwYjM1NzI2YmQ3NWJkMzc1MzJiMjk5
11
+ NzQwYmJiMjMyN2I0NzJlOWRkMDU3ZjRiYmRhZDA4MDk3NGNjYWE=
12
12
  data.tar.gz: !binary |-
13
- MGY5ODIwOGIzM2I4MGM0NDkwZTBiZWVmNjU3NTFjNjg2ZDRkYTU1MGM1ZWMy
14
- ODE3ZDA3YmY5ZGQwMmY2ODAwMTQzNWFiYWQwMTVmM2ZjNDkyYjQ0NGI5MmU4
15
- ZDZkZWFmMTRkOGEwNDY5NmYyNjVjODk3NWIwYzFlNmVhODE3ZTk=
13
+ YWVmMzQ5NWE1M2FiYzQ4YmQyM2E3YjdjMjkyOWZiMDkxMDJiMDA1NGI2YWU1
14
+ NTg0ODdlZGU0NTQ1NTljNjU5MGE2NjMwZGY0MTNjOTY4NDdkZmZiYjJmYjYx
15
+ MGVkMDZkMTg0MTlhMWYxZTUzMWI3YjMxMDJhY2M1ODM0MTdjZjM=
data/bin/oaf CHANGED
@@ -61,4 +61,4 @@ else
61
61
  exit 1
62
62
  end
63
63
 
64
- Oaf::HTTP::Server.serve options[:path], options[:port]
64
+ Oaf::HTTPServer.serve options[:path], options[:port]
data/lib/oaf.rb CHANGED
@@ -22,5 +22,5 @@
22
22
 
23
23
  require 'oaf/version'
24
24
  require 'oaf/util'
25
- require 'oaf/http/handler'
26
- require 'oaf/http/server'
25
+ require 'oaf/httphandler'
26
+ require 'oaf/httpserver'
@@ -21,16 +21,16 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
 
23
23
  require 'oaf/util'
24
- require 'oaf/http/server'
24
+ require 'oaf/httpserver'
25
25
  require 'webrick'
26
26
 
27
- module Oaf::HTTP
27
+ module Oaf
28
28
 
29
29
  # Provides all required handlers to WEBrick for serving all basic HTTP
30
30
  # methods. WEBrick handles GET, POST, HEAD, and OPTIONS out of the box,
31
31
  # but to mock most RESTful applications we are going to want PUT and
32
32
  # DELETE undoubtedly.
33
- class Handler < WEBrick::HTTPServlet::AbstractServlet
33
+ class HTTPHandler < WEBrick::HTTPServlet::AbstractServlet
34
34
 
35
35
  # Remove the predefined WEBrick methods. WEBrick comes with some defaults
36
36
  # for GET, POST, OPTIONS, and HEAD, but let's use our own instead.
@@ -64,12 +64,12 @@ module Oaf::HTTP
64
64
  def process_request req, res
65
65
  req_headers = req.header
66
66
  req_query = req.query
67
- req_body = Oaf::HTTP::Server.get_request_body req
67
+ req_body = Oaf::HTTPServer.get_request_body req
68
68
  file = Oaf::Util.get_request_file @path, req.path, req.request_method
69
69
  out = Oaf::Util.get_output(file, req.path, req_headers, req_body,
70
70
  req_query)
71
- res_headers, res_status, res_body = Oaf::HTTP::Server.parse_response out
72
- Oaf::HTTP::Server.set_response! res, res_headers, res_body, res_status
71
+ res_headers, res_status, res_body = Oaf::HTTPServer.parse_response out
72
+ Oaf::HTTPServer.set_response! res, res_headers, res_body, res_status
73
73
  end
74
74
 
75
75
  # A magic respond_to? implementation to trick WEBrick into thinking that any
@@ -0,0 +1,106 @@
1
+ # oaf - Care-free web app prototyping using files and scripts
2
+ # Copyright 2013 Ryan Uber <ru@ryanuber.com>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'oaf/util'
24
+ require 'oaf/httphandler'
25
+ require 'webrick'
26
+
27
+ module Oaf::HTTPServer
28
+ extend self
29
+
30
+ # Given output from a script, parse HTTP response details and return them
31
+ # as a hash, including body, status, and headers.
32
+ #
33
+ # == Parameters:
34
+ # output::
35
+ # The output text data returned by a script.
36
+ #
37
+ # == Returns:
38
+ # A hash containing the HTTP response details (body, status, and headers).
39
+ #
40
+ def parse_response output
41
+ has_meta = false
42
+ headers = {'content-type' => 'text/plain'}
43
+ status = 200
44
+ headers, status, meta_size = Oaf::Util.parse_http_meta output
45
+ lines = output.split("\n")
46
+ body = lines.take(lines.length - meta_size).join("\n")+"\n"
47
+ [headers, status, body]
48
+ end
49
+
50
+ # Safely retrieves the request body, and assumes an empty string if it
51
+ # cannot be retrieved. This helps get around a nasty exception in WEBrick.
52
+ #
53
+ # == Parameters:
54
+ # req::
55
+ # A WEBrick::HTTPRequest object
56
+ #
57
+ # == Returns:
58
+ # A string containing the request body
59
+ #
60
+ def get_request_body req
61
+ if ['POST', 'PUT'].member? req.request_method
62
+ begin
63
+ result = req.body
64
+ rescue WEBrick::HTTPStatus::LengthRequired
65
+ result = '' # needs to be in rescue for coverage
66
+ end
67
+ end
68
+ result
69
+ end
70
+
71
+ # Consume HTTP response details and set them into a response object.
72
+ #
73
+ # == Parameters:
74
+ # res::
75
+ # A WEBrick::HTTPResponse object
76
+ # headers::
77
+ # A hash containing HTTP response headers
78
+ # body::
79
+ # A string containing the HTTP response body
80
+ # status::
81
+ # An integer indicating the response status
82
+ #
83
+ def set_response! res, headers, body, status
84
+ headers.each do |name, value|
85
+ res.header[name] = value
86
+ end
87
+ res.body = body
88
+ res.status = status
89
+ end
90
+
91
+ # Invokes the Webrick web server library to handle incoming requests, and
92
+ # routes them to the appropriate scripts if they exist on the filesystem.
93
+ #
94
+ # == Parameters:
95
+ # path::
96
+ # The path in which to search for files
97
+ # port::
98
+ # The TCP port to listen on
99
+ #
100
+ def serve path, port
101
+ server = WEBrick::HTTPServer.new :Port => port
102
+ server.mount '/', Oaf::HTTPHandler, path
103
+ trap 'INT' do server.shutdown end
104
+ server.start
105
+ end
106
+ end
@@ -20,285 +20,281 @@
20
20
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
 
23
- module Oaf
23
+ module Oaf::Util
24
+ extend self
24
25
 
25
- module Util
26
- extend Oaf
27
- extend self
28
-
29
- # Determines if a line of output looks anything like an HTTP header
30
- # declaration.
31
- #
32
- # == Parameters:
33
- # line::
34
- # A line of text to examine
35
- #
36
- # == Returns:
37
- # A boolean, true if it can be read as a header string, else false.
38
- #
39
- def is_http_header? line
40
- line.split(':').length == 2
41
- end
26
+ # Determines if a line of output looks anything like an HTTP header
27
+ # declaration.
28
+ #
29
+ # == Parameters:
30
+ # line::
31
+ # A line of text to examine
32
+ #
33
+ # == Returns:
34
+ # A boolean, true if it can be read as a header string, else false.
35
+ #
36
+ def is_http_header? line
37
+ line.split(':').length == 2
38
+ end
42
39
 
43
- # Retrieves a hash in the form `name` => `value` from a string that
44
- # describes an HTTP response header.
45
- #
46
- # == Parameters:
47
- # line::
48
- # A line of text to parse
49
- #
50
- # == Returns
51
- # A hash in the form `name` => `value`, or `nil`
52
- #
53
- def get_http_header line
54
- return nil if not is_http_header? line
55
- parts = line.split(':')
56
- {parts[0].strip => parts[1].strip}
57
- end
40
+ # Retrieves a hash in the form `name` => `value` from a string that
41
+ # describes an HTTP response header.
42
+ #
43
+ # == Parameters:
44
+ # line::
45
+ # A line of text to parse
46
+ #
47
+ # == Returns
48
+ # A hash in the form `name` => `value`, or `nil`
49
+ #
50
+ def get_http_header line
51
+ return nil if not is_http_header? line
52
+ parts = line.split(':')
53
+ {parts[0].strip => parts[1].strip}
54
+ end
58
55
 
59
- # Retrieves the numeric value from a line of text as an HTTP status code.
60
- #
61
- # == Parameters:
62
- # line::
63
- # A line of text to parse
64
- #
65
- # == Returns:
66
- # An integer if valid, else `nil`.
67
- #
68
- def get_http_status line
69
- is_http_status?(line) ? line.to_i : nil
70
- end
56
+ # Retrieves the numeric value from a line of text as an HTTP status code.
57
+ #
58
+ # == Parameters:
59
+ # line::
60
+ # A line of text to parse
61
+ #
62
+ # == Returns:
63
+ # An integer if valid, else `nil`.
64
+ #
65
+ def get_http_status line
66
+ is_http_status?(line) ? line.to_i : nil
67
+ end
71
68
 
72
- # Determines if an HTTP status code is valid per RFC2616
73
- #
74
- # == Parameters:
75
- # code::
76
- # A number to validate
77
- #
78
- # == Returns
79
- # A boolean, true if valid, else false.
80
- #
81
- def is_http_status? code
82
- (200..206).to_a.concat((300..307).to_a).concat((400..417).to_a) \
83
- .concat((500..505).to_a).include? code.to_i
84
- end
69
+ # Determines if an HTTP status code is valid per RFC2616
70
+ #
71
+ # == Parameters:
72
+ # code::
73
+ # A number to validate
74
+ #
75
+ # == Returns
76
+ # A boolean, true if valid, else false.
77
+ #
78
+ def is_http_status? code
79
+ (200..206).to_a.concat((300..307).to_a).concat((400..417).to_a) \
80
+ .concat((500..505).to_a).include? code.to_i
81
+ end
85
82
 
86
- # Convert various pieces of data about the request into a single hash, which
87
- # can then be passed on as environment data to a script.
88
- #
89
- # == Parameters:
90
- # req_path::
91
- # A string with the HTTP request path
92
- # headers::
93
- # A hash of request headers
94
- # params::
95
- # A hash of request parameters
96
- # body::
97
- # A string containing the request body
98
- #
99
- # == Returns:
100
- # A flat hash containing namespaced environment parameters
101
- #
102
- def prepare_environment req_path, headers, params, body
103
- result = Hash.new
104
- {'header' => headers, 'param' => params}.each do |prefix, data|
105
- data.each do |name, value|
106
- result.merge! Oaf::Util.environment_item prefix, name, value
107
- end
83
+ # Convert various pieces of data about the request into a single hash, which
84
+ # can then be passed on as environment data to a script.
85
+ #
86
+ # == Parameters:
87
+ # req_path::
88
+ # A string with the HTTP request path
89
+ # headers::
90
+ # A hash of request headers
91
+ # params::
92
+ # A hash of request parameters
93
+ # body::
94
+ # A string containing the request body
95
+ #
96
+ # == Returns:
97
+ # A flat hash containing namespaced environment parameters
98
+ #
99
+ def prepare_environment req_path, headers, params, body
100
+ result = Hash.new
101
+ {'header' => headers, 'param' => params}.each do |prefix, data|
102
+ data.each do |name, value|
103
+ result.merge! Oaf::Util.environment_item prefix, name, value
108
104
  end
109
- result.merge! Oaf::Util.environment_item 'request', 'path', req_path
110
- result.merge Oaf::Util.environment_item 'request', 'body', body
111
105
  end
106
+ result.merge! Oaf::Util.environment_item 'request', 'path', req_path
107
+ result.merge Oaf::Util.environment_item 'request', 'body', body
108
+ end
112
109
 
113
- # Prepares a key for placement in the execution environment. This includes
114
- # namespacing variables and converting characters to predictable and
115
- # easy-to-use names.
116
- #
117
- # == Parameters:
118
- # prefix::
119
- # A prefix for the key. This helps with separation.
120
- # key::
121
- # The key to sanitize
122
- #
123
- # == Returns:
124
- # A string with the prepared value
125
- #
126
- def prepare_key prefix, key
127
- "oaf_#{prefix}_#{key.gsub('-', '_').downcase}"
128
- end
110
+ # Prepares a key for placement in the execution environment. This includes
111
+ # namespacing variables and converting characters to predictable and
112
+ # easy-to-use names.
113
+ #
114
+ # == Parameters:
115
+ # prefix::
116
+ # A prefix for the key. This helps with separation.
117
+ # key::
118
+ # The key to sanitize
119
+ #
120
+ # == Returns:
121
+ # A string with the prepared value
122
+ #
123
+ def prepare_key prefix, key
124
+ "oaf_#{prefix}_#{key.gsub('-', '_').downcase}"
125
+ end
129
126
 
130
- # Formats a single environment item into a hash, which can be merged into a
131
- # collective environment mapping later on.
132
- #
133
- # == Parameters:
134
- # prefix::
135
- # The prefix for the type of item being added.
136
- # key::
137
- # The key name of the environment property
138
- # value::
139
- # The value for the environment property
140
- #
141
- # == Returns:
142
- # A hash with prepared values ready to merge into an environment hash
143
- #
144
- def environment_item prefix, key, value
145
- {Oaf::Util.prepare_key(prefix, key) => Oaf::Util.flatten(value)}
146
- end
127
+ # Formats a single environment item into a hash, which can be merged into a
128
+ # collective environment mapping later on.
129
+ #
130
+ # == Parameters:
131
+ # prefix::
132
+ # The prefix for the type of item being added.
133
+ # key::
134
+ # The key name of the environment property
135
+ # value::
136
+ # The value for the environment property
137
+ #
138
+ # == Returns:
139
+ # A hash with prepared values ready to merge into an environment hash
140
+ #
141
+ def environment_item prefix, key, value
142
+ {Oaf::Util.prepare_key(prefix, key) => Oaf::Util.flatten(value)}
143
+ end
147
144
 
148
- # Flatten a hash or array into a string. This is useful for preparing some
149
- # data for passing in via the environment, because multi-dimension data
150
- # structures are not supported for that.
151
- #
152
- # == Parameters:
153
- # data::
154
- # The data to flatten
155
- #
156
- # == Returns:
157
- # A flattened string. It will be empty if the object passed in was not
158
- # flatten-able.
159
- #
160
- def flatten data
161
- result = ''
162
- if data.kind_of? Hash
163
- data.each do |key, val|
164
- val = Oaf::Util.flatten val if not val.kind_of? String
165
- result += "#{key}#{val}"
166
- end
167
- elsif data.kind_of? Array
168
- data.each do |item|
169
- item = Oaf::Util.flatten item if not item.kind_of? String
170
- result += item
171
- end
172
- elsif data.kind_of? String
173
- result = data
174
- else
175
- result = ''
145
+ # Flatten a hash or array into a string. This is useful for preparing some
146
+ # data for passing in via the environment, because multi-dimension data
147
+ # structures are not supported for that.
148
+ #
149
+ # == Parameters:
150
+ # data::
151
+ # The data to flatten
152
+ #
153
+ # == Returns:
154
+ # A flattened string. It will be empty if the object passed in was not
155
+ # flatten-able.
156
+ #
157
+ def flatten data
158
+ result = ''
159
+ if data.kind_of? Hash
160
+ data.each do |key, val|
161
+ val = Oaf::Util.flatten val if not val.kind_of? String
162
+ result += "#{key}#{val}"
163
+ end
164
+ elsif data.kind_of? Array
165
+ data.each do |item|
166
+ item = Oaf::Util.flatten item if not item.kind_of? String
167
+ result += item
176
168
  end
177
- result
169
+ elsif data.kind_of? String
170
+ result = data
171
+ else
172
+ result = ''
178
173
  end
174
+ result
175
+ end
179
176
 
180
- # Given an array of text lines, iterate over each of them and determine if
181
- # they may be interpreted as headers or status. If they can, add them to
182
- # the result.
183
- #
184
- # == Parameters:
185
- # text::
186
- # Plain text data to examine
187
- #
188
- # == Returns:
189
- # A 3-item structure containing headers, status, and the number of lines
190
- # which the complete metadata (including the "---" delimiter) occupies.
191
- #
192
- def parse_http_meta text
193
- headers = {}
194
- status = 200
195
- size = 0
196
- parts = text.split /^---$/
197
- if parts.length > 1
198
- for part in parts.last.split "\n"
199
- if Oaf::Util.is_http_header? part
200
- headers.merge! Oaf::Util.get_http_header part
201
- elsif Oaf::Util.is_http_status? part
202
- status = Oaf::Util.get_http_status part
203
- else next
204
- end
205
- size += size == 0 ? 2 : 1 # compensate for delimiter
177
+ # Given an array of text lines, iterate over each of them and determine if
178
+ # they may be interpreted as headers or status. If they can, add them to
179
+ # the result.
180
+ #
181
+ # == Parameters:
182
+ # text::
183
+ # Plain text data to examine
184
+ #
185
+ # == Returns:
186
+ # A 3-item structure containing headers, status, and the number of lines
187
+ # which the complete metadata (including the "---" delimiter) occupies.
188
+ #
189
+ def parse_http_meta text
190
+ headers = {}
191
+ status = 200
192
+ size = 0
193
+ parts = text.split /^---$/
194
+ if parts.length > 1
195
+ for part in parts.last.split "\n"
196
+ if Oaf::Util.is_http_header? part
197
+ headers.merge! Oaf::Util.get_http_header part
198
+ elsif Oaf::Util.is_http_status? part
199
+ status = Oaf::Util.get_http_status part
200
+ else next
206
201
  end
202
+ size += size == 0 ? 2 : 1 # compensate for delimiter
207
203
  end
208
- [headers, status, size]
209
204
  end
205
+ [headers, status, size]
206
+ end
210
207
 
211
- # Return a default response string indicating that nothing could be
212
- # done and no response files were found.
213
- #
214
- # == Returns:
215
- # A string with response output for parsing.
216
- #
217
- def get_default_response
218
- "oaf: Not Found\n---\n404"
219
- end
208
+ # Return a default response string indicating that nothing could be
209
+ # done and no response files were found.
210
+ #
211
+ # == Returns:
212
+ # A string with response output for parsing.
213
+ #
214
+ def get_default_response
215
+ "oaf: Not Found\n---\n404"
216
+ end
220
217
 
221
- # Returns the path to the file to use for the request. If the provided
222
- # file path does not exist, this method will search for a file within
223
- # the same directory matching the default convention "_*_".
224
- #
225
- # == Parameters:
226
- # root::
227
- # The root path to search within.
228
- # req_path::
229
- # The HTTP request path
230
- # method::
231
- # The HTTP method of the request
232
- #
233
- # == Returns:
234
- # The path to a file to use, or `nil` if none is found.
235
- #
236
- def get_request_file root, req_path, method
237
- file = File.join root, "#{req_path}.#{method}"
238
- if not File.exist? file
239
- Dir.glob(File.join(File.dirname(file), "_*_.#{method}")).each do |f|
240
- file = f
241
- break
242
- end
218
+ # Returns the path to the file to use for the request. If the provided
219
+ # file path does not exist, this method will search for a file within
220
+ # the same directory matching the default convention "_*_".
221
+ #
222
+ # == Parameters:
223
+ # root::
224
+ # The root path to search within.
225
+ # req_path::
226
+ # The HTTP request path
227
+ # method::
228
+ # The HTTP method of the request
229
+ #
230
+ # == Returns:
231
+ # The path to a file to use, or `nil` if none is found.
232
+ #
233
+ def get_request_file root, req_path, method
234
+ file = File.join root, "#{req_path}.#{method}"
235
+ if not File.exist? file
236
+ Dir.glob(File.join(File.dirname(file), "_*_.#{method}")).each do |f|
237
+ file = f
238
+ break
243
239
  end
244
- File.exist?(file) ? file : nil
245
240
  end
241
+ File.exist?(file) ? file : nil
242
+ end
246
243
 
247
- # Fork a new process, in which we can safely modify the running environment
248
- # and run a command, sending data back to the parent process using an IO
249
- # pipe. This can be done in a single line in ruby >= 1.9, but we will do it
250
- # the hard way to maintain compatibility with older rubies.
251
- #
252
- # == Parameters:
253
- # env::
254
- # The environment data to use in the subprocess.
255
- # command::
256
- # The command to execute against the server
257
- #
258
- # == Returns:
259
- # A string of stderr concatenated to stdout.
260
- #
261
- def run_buffered env, command
262
- out, wout = IO.pipe
263
- pid = fork do
264
- out.close
265
- ENV.replace env
266
- wout.write %x(#{command} 2>&1)
267
- at_exit { exit! }
268
- end
269
- wout.close
270
- Process.wait pid
271
- out.read
244
+ # Fork a new process, in which we can safely modify the running environment
245
+ # and run a command, sending data back to the parent process using an IO
246
+ # pipe. This can be done in a single line in ruby >= 1.9, but we will do it
247
+ # the hard way to maintain compatibility with older rubies.
248
+ #
249
+ # == Parameters:
250
+ # env::
251
+ # The environment data to use in the subprocess.
252
+ # command::
253
+ # The command to execute against the server
254
+ #
255
+ # == Returns:
256
+ # A string of stderr concatenated to stdout.
257
+ #
258
+ def run_buffered env, command
259
+ out, wout = IO.pipe
260
+ pid = fork do
261
+ out.close
262
+ ENV.replace env
263
+ wout.write %x(#{command} 2>&1)
264
+ at_exit { exit! }
272
265
  end
266
+ wout.close
267
+ Process.wait pid
268
+ out.read
269
+ end
273
270
 
274
- # Executes a file, or reads its contents if it is not executable, passing
275
- # it the request headers and body as arguments, and returns the result.
276
- #
277
- # == Parameters:
278
- # req_path::
279
- # The HTTP request path
280
- # file::
281
- # The path to the file to use for output
282
- # headers::
283
- # The HTTP request headers to pass
284
- # body::
285
- # The HTTP request body to pass
286
- # params::
287
- # The HTTP request parameters to pass
288
- #
289
- # == Returns:
290
- # The result from the file, or a default result if the file is not found.
291
- #
292
- def get_output file, req_path=nil, headers=[], body=[], params=[]
293
- if file.nil?
294
- out = Oaf::Util.get_default_response
295
- elsif File.executable? file
296
- env = Oaf::Util.prepare_environment req_path, headers, params, body
297
- out = Oaf::Util.run_buffered env, file
298
- else
299
- out = File.open(file).read
300
- end
301
- out
271
+ # Executes a file, or reads its contents if it is not executable, passing
272
+ # it the request headers and body as arguments, and returns the result.
273
+ #
274
+ # == Parameters:
275
+ # req_path::
276
+ # The HTTP request path
277
+ # file::
278
+ # The path to the file to use for output
279
+ # headers::
280
+ # The HTTP request headers to pass
281
+ # body::
282
+ # The HTTP request body to pass
283
+ # params::
284
+ # The HTTP request parameters to pass
285
+ #
286
+ # == Returns:
287
+ # The result from the file, or a default result if the file is not found.
288
+ #
289
+ def get_output file, req_path=nil, headers=[], body=[], params=[]
290
+ if file.nil?
291
+ out = Oaf::Util.get_default_response
292
+ elsif File.executable? file
293
+ env = Oaf::Util.prepare_environment req_path, headers, params, body
294
+ out = Oaf::Util.run_buffered env, file
295
+ else
296
+ out = File.open(file).read
302
297
  end
298
+ out
303
299
  end
304
300
  end
@@ -21,5 +21,5 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
 
23
23
  module Oaf
24
- VERSION = '0.3.0'
24
+ VERSION = '0.3.1'
25
25
  end
@@ -25,7 +25,7 @@ require 'spec_helper'
25
25
  module Oaf
26
26
  describe "Returning HTTP Responses" do
27
27
  it "should return safe defaults if output is empty" do
28
- headers, status, body = Oaf::HTTP::Server.parse_response ''
28
+ headers, status, body = Oaf::HTTPServer.parse_response ''
29
29
  headers.should eq({})
30
30
  status.should eq(200)
31
31
  body.should eq("\n")
@@ -33,7 +33,7 @@ module Oaf
33
33
 
34
34
  it "should return safe defaults when only body is present" do
35
35
  text = "This is a test\n"
36
- headers, status, body = Oaf::HTTP::Server.parse_response text
36
+ headers, status, body = Oaf::HTTPServer.parse_response text
37
37
  headers.should eq({})
38
38
  status.should eq(200)
39
39
  body.should eq("This is a test\n")
@@ -41,25 +41,25 @@ module Oaf
41
41
 
42
42
  it "should return headers correctly" do
43
43
  text = "---\nx-powered-by: oaf"
44
- headers, status, body = Oaf::HTTP::Server.parse_response text
44
+ headers, status, body = Oaf::HTTPServer.parse_response text
45
45
  headers.should eq({'x-powered-by' => 'oaf'})
46
46
  end
47
47
 
48
48
  it "should return status correctly" do
49
49
  text = "---\n201"
50
- headers, status, body = Oaf::HTTP::Server.parse_response text
50
+ headers, status, body = Oaf::HTTPServer.parse_response text
51
51
  status.should eq(201)
52
52
  end
53
53
 
54
54
  it "should return body correctly" do
55
55
  text = "This is a test\n---\n200"
56
- headers, status, body = Oaf::HTTP::Server.parse_response text
56
+ headers, status, body = Oaf::HTTPServer.parse_response text
57
57
  body.should eq("This is a test\n")
58
58
  end
59
59
 
60
60
  it "should return body correctly when no metadata is present" do
61
61
  text = "This is a test"
62
- headers, status, body = Oaf::HTTP::Server.parse_response text
62
+ headers, status, body = Oaf::HTTPServer.parse_response text
63
63
  body.should eq("This is a test\n")
64
64
  end
65
65
  end
@@ -90,15 +90,15 @@ module Oaf
90
90
  @webrick.should_receive(:start).once.and_return(true)
91
91
  WEBrick::HTTPServer.stub(:new).and_return(@webrick)
92
92
  @webrick.should_receive(:mount) \
93
- .with('/', Oaf::HTTP::Handler, '/tmp').once \
93
+ .with('/', Oaf::HTTPHandler, '/tmp').once \
94
94
  .and_return(true)
95
- Oaf::HTTP::Server.serve '/tmp', 9000
95
+ Oaf::HTTPServer.serve '/tmp', 9000
96
96
  end
97
97
 
98
98
  it "should parse the request properly" do
99
99
  req = Oaf::FakeReq.new :path => @f1request
100
100
  res = Oaf::FakeRes.new
101
- handler = Oaf::HTTP::Handler.new Oaf::FakeServlet.new, @tempdir1
101
+ handler = Oaf::HTTPHandler.new Oaf::FakeServlet.new, @tempdir1
102
102
  handler.process_request req, res
103
103
  res.body.should eq("This is a test.\n")
104
104
  res.status.should eq(201)
@@ -108,7 +108,7 @@ module Oaf
108
108
  it "should accept containable methods properly" do
109
109
  req = Oaf::FakeReq.new({:path => @f2request, :method => 'PUT'})
110
110
  res = Oaf::FakeRes.new
111
- handler = Oaf::HTTP::Handler.new Oaf::FakeServlet.new, @tempdir1
111
+ handler = Oaf::HTTPHandler.new Oaf::FakeServlet.new, @tempdir1
112
112
  handler.process_request req, res
113
113
  res.body.should eq("Containable Test\n")
114
114
  res.status.should eq(202)
@@ -118,8 +118,8 @@ module Oaf
118
118
  it "should respond to any HTTP method" do
119
119
  req = Oaf::FakeReq.new :path => @f1request
120
120
  res = Oaf::FakeRes.new
121
- Oaf::HTTP::Handler.any_instance.stub(:process_request).and_return(true)
122
- handler = Oaf::HTTP::Handler.new Oaf::FakeServlet.new, @tempdir1
121
+ Oaf::HTTPHandler.any_instance.stub(:process_request).and_return(true)
122
+ handler = Oaf::HTTPHandler.new Oaf::FakeServlet.new, @tempdir1
123
123
  handler.should_receive(:process_request).with(req, res).once
124
124
  handler.respond_to?(:do_GET).should be_true
125
125
  handler.respond_to?(:do_get).should be_false
@@ -130,8 +130,8 @@ module Oaf
130
130
  it "should call our custom methods for built-ins" do
131
131
  req = Oaf::FakeReq.new :path => @f1request
132
132
  res = Oaf::FakeRes.new
133
- Oaf::HTTP::Handler.any_instance.stub(:process_request).and_return(true)
134
- handler = Oaf::HTTP::Handler.new Oaf::FakeServlet.new, @tempdir1
133
+ Oaf::HTTPHandler.any_instance.stub(:process_request).and_return(true)
134
+ handler = Oaf::HTTPHandler.new Oaf::FakeServlet.new, @tempdir1
135
135
  handler.should_receive(:process_request).with(req, res).exactly(4).times
136
136
  handler.do_GET(req, res)
137
137
  handler.do_POST(req, res)
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oaf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Uber
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-01 00:00:00.000000000 Z
11
+ date: 2013-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ! '>='
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ! '>='
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rake
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -81,25 +67,18 @@ dependencies:
81
67
  - !ruby/object:Gem::Version
82
68
  version: '0'
83
69
  description: Care-free web app prototyping using files and scripts
84
- email:
85
- - ru@ryanuber.com
70
+ email: ru@ryanuber.com
86
71
  executables:
87
72
  - oaf
88
73
  extensions: []
89
74
  extra_rdoc_files: []
90
75
  files:
91
- - .travis.yml
92
- - Gemfile
93
- - LICENSE
94
- - README.md
95
- - Rakefile
96
76
  - bin/oaf
97
- - lib/oaf.rb
98
- - lib/oaf/http/handler.rb
99
- - lib/oaf/http/server.rb
77
+ - lib/oaf/httphandler.rb
78
+ - lib/oaf/httpserver.rb
100
79
  - lib/oaf/util.rb
101
80
  - lib/oaf/version.rb
102
- - oaf.gemspec
81
+ - lib/oaf.rb
103
82
  - spec/oaf/http_spec.rb
104
83
  - spec/oaf/util_spec.rb
105
84
  - spec/spec_helper.rb
@@ -1,8 +0,0 @@
1
- branches:
2
- only:
3
- - master
4
- language: ruby
5
- rvm:
6
- - 1.9.3
7
- - 1.9.2
8
- - 1.8.7
data/Gemfile DELETED
@@ -1,2 +0,0 @@
1
- source "https://rubygems.org"
2
- gemspec
data/LICENSE DELETED
@@ -1,20 +0,0 @@
1
- MIT LICENSE
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md DELETED
@@ -1,150 +0,0 @@
1
- Oaf
2
- ---
3
-
4
- Care-free web app prototyping using files and scripts
5
-
6
- [![Gem Version](https://badge.fury.io/rb/oaf.png)](http://badge.fury.io/rb/oaf)
7
- [![Build Status](https://travis-ci.org/ryanuber/oaf.png)](https://travis-ci.org/ryanuber/oaf)
8
- [![Coverage Status](https://coveralls.io/repos/ryanuber/oaf/badge.png)](https://coveralls.io/r/ryanuber/oaf)
9
- [![Code Climate](https://codeclimate.com/github/ryanuber/oaf.png)](https://codeclimate.com/github/ryanuber/oaf)
10
-
11
- [Documentation](http://rubydoc.info/gems/oaf/frames)
12
-
13
- `Oaf` provides stupid-easy way of creating dynamic web applications by setting
14
- all best practices and security considerations aside until you are sure that you
15
- want to invest your time doing so.
16
-
17
- `Oaf` was created as a weekend project to create a small, simple HTTP server
18
- program that uses script execution as its primary mechanism for generating
19
- responses.
20
-
21
- Example
22
- -------
23
-
24
- Create an executable file named `hello.GET`:
25
-
26
- ```
27
- #!/bin/bash
28
- echo "Hello, ${USER}!"
29
- ```
30
-
31
- Start the server by running the `oaf` command, then make a request:
32
-
33
- ```
34
- $ curl localhost:9000/hello
35
- Hello, ryanuber!
36
- ```
37
-
38
- Installation
39
- ------------
40
-
41
- Oaf is available on [rubygems](http://rubygems.org/gems/oaf), which means you
42
- can do:
43
-
44
- ```
45
- gem install oaf
46
- ```
47
-
48
- ### Accepted files
49
-
50
- `Oaf` will run *ANY* file with the executable bit set, be it shell, Python, Ruby,
51
- compiled binary, or whatever else you might have.
52
-
53
- `Oaf` can also use plain text files.
54
-
55
- ### How file permissions affect output
56
-
57
- * If the file in your request is executable, the output of its execution will
58
- be used as the return data.
59
- * If the file is *NOT* executable, then the contents of the file will be used
60
- as the return data.
61
-
62
- ### Nested methods
63
-
64
- You can create nested methods using simple directories. Example:
65
-
66
- ```
67
- $ ls ./examples/
68
- hello.GET
69
-
70
- $ curl http://localhost:8000/examples/hello
71
- Hello, world!
72
- ```
73
-
74
- ### HTTP Methods
75
- Files must carry the extension of the HTTP method used to invoke them.
76
- Oaf should support any HTTP method, including custom methods.
77
-
78
- ### Headers and Status
79
- You can indicate HTTP headers and status using stdout from your script.
80
-
81
- ```
82
- #!/bin/bash
83
- cat <<EOF
84
- Hello, world!
85
- ---
86
- content-type: text/plain
87
- 200
88
- EOF
89
- ```
90
-
91
- Separated by 3 dashes on a line of their own (`---`), the very last block
92
- of output can contain headers and response status.
93
-
94
- ### Getting request headers, query parameters, and body
95
-
96
- Headers, query parameters, and request body are all passed to executables using
97
- the environment. To defeat overlap in variables, they are namespaced using a
98
- prefix. The environment variables are as follows:
99
-
100
- * Headers: `oaf_header_*`
101
- * Request parameters: `oaf_param_*`
102
- * Request body: `oaf_request_body`
103
- * Request path: `oaf_request_path`
104
-
105
- Below is a quick example of a shell script which makes use of the request data:
106
-
107
- ```
108
- #!/bin/bash
109
- echo "You passed the Accept header: $oaf_header_accept"
110
- echo "You passed the 'message' value: $oaf_param_message"
111
- echo "You passed the request body: $oaf_request_body"
112
- echo "This method is located at: $oaf_request_path"
113
- ```
114
-
115
- Headers query parameter names are converted to all-lowercase, and dashes are
116
- replaced with underscores. This is due to the way the environment works. For
117
- example, if you wanted to get at the `Content-Type` header, you could with the
118
- environment variable `$oaf_header_content_type`.
119
-
120
- ### Catch-all methods
121
-
122
- Catch-all's can be defined by naming a file inside of a directory, beginning and
123
- ending with underscores (`_`). So for example, `test/_default_.GET` will match:
124
- `GET /test/anything`, `GET /test/blah`, etc.
125
-
126
- If you want to define a top-level method for `/test`, you would do so in the
127
- file at `/test.GET`.
128
-
129
- Q&A
130
- ---
131
-
132
- **Why are the headers and status at the bottom of the response?**
133
- Because it is much easier to echo these last. Since Oaf reads response
134
- data directly from stdout/stderr, it is very easy for debug or error messages
135
- to interfere with them. By specifying the headers and status last, we minimize
136
- the chances of unexpected output damaging the response, as all processing is
137
- already complete.
138
-
139
- **Why the name `Oaf`?**
140
- It's a bit of a clumsy and "oafish" approach at web application prototyping. I
141
- constantly find myself trying to write server parts of programs before I have
142
- even completed basic functionality, and sometimes even before I have a clear
143
- idea of what it is I want the program to do.
144
-
145
- Acknowledgements
146
- ----------------
147
-
148
- `Oaf` is inspired by [Stubb](https://github.com/knuton/stubb). A number of ideas
149
- and conventions were borrowed from it. Kudos to
150
- [@knuton](https://github.com/knuton) for having a great idea.
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
3
-
4
- RSpec::Core::RakeTask.new
5
-
6
- task :default => [:spec]
@@ -1,110 +0,0 @@
1
- # oaf - Care-free web app prototyping using files and scripts
2
- # Copyright 2013 Ryan Uber <ru@ryanuber.com>
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
-
23
- require 'oaf/util'
24
- require 'oaf/http/handler'
25
- require 'webrick'
26
-
27
- module Oaf::HTTP
28
-
29
- module Server
30
- extend Oaf
31
- extend self
32
-
33
- # Given output from a script, parse HTTP response details and return them
34
- # as a hash, including body, status, and headers.
35
- #
36
- # == Parameters:
37
- # output::
38
- # The output text data returned by a script.
39
- #
40
- # == Returns:
41
- # A hash containing the HTTP response details (body, status, and headers).
42
- #
43
- def parse_response output
44
- has_meta = false
45
- headers = {'content-type' => 'text/plain'}
46
- status = 200
47
- headers, status, meta_size = Oaf::Util.parse_http_meta output
48
- lines = output.split("\n")
49
- body = lines.take(lines.length - meta_size).join("\n")+"\n"
50
- [headers, status, body]
51
- end
52
-
53
- # Safely retrieves the request body, and assumes an empty string if it
54
- # cannot be retrieved. This helps get around a nasty exception in WEBrick.
55
- #
56
- # == Parameters:
57
- # req::
58
- # A WEBrick::HTTPRequest object
59
- #
60
- # == Returns:
61
- # A string containing the request body
62
- #
63
- def get_request_body req
64
- if ['POST', 'PUT'].member? req.request_method
65
- begin
66
- result = req.body
67
- rescue WEBrick::HTTPStatus::LengthRequired
68
- result = '' # needs to be in rescue for coverage
69
- end
70
- end
71
- result
72
- end
73
-
74
- # Consume HTTP response details and set them into a response object.
75
- #
76
- # == Parameters:
77
- # res::
78
- # A WEBrick::HTTPResponse object
79
- # headers::
80
- # A hash containing HTTP response headers
81
- # body::
82
- # A string containing the HTTP response body
83
- # status::
84
- # An integer indicating the response status
85
- #
86
- def set_response! res, headers, body, status
87
- headers.each do |name, value|
88
- res.header[name] = value
89
- end
90
- res.body = body
91
- res.status = status
92
- end
93
-
94
- # Invokes the Webrick web server library to handle incoming requests, and
95
- # routes them to the appropriate scripts if they exist on the filesystem.
96
- #
97
- # == Parameters:
98
- # path::
99
- # The path in which to search for files
100
- # port::
101
- # The TCP port to listen on
102
- #
103
- def serve path, port
104
- server = WEBrick::HTTPServer.new :Port => port
105
- server.mount '/', Oaf::HTTP::Handler, path
106
- trap 'INT' do server.shutdown end
107
- server.start
108
- end
109
- end
110
- end
@@ -1,27 +0,0 @@
1
- lib = File.expand_path("../lib", __FILE__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'oaf/version'
4
-
5
- Gem::Specification.new do |s|
6
- s.name = 'oaf'
7
- s.version = Oaf::VERSION
8
- s.summary = 'Web app prototyping'
9
- s.description = 'Care-free web app prototyping using files and scripts'
10
- s.authors = ["Ryan Uber"]
11
- s.email = ['ru@ryanuber.com']
12
- s.files = %x(git ls-files).split($/)
13
- s.homepage = 'https://github.com/ryanuber/oaf'
14
- s.license = 'MIT'
15
- s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
- s.test_files = s.files.grep(%r{^spec/})
17
- s.require_paths = ['lib']
18
-
19
- s.required_ruby_version = '>= 1.8'
20
-
21
- s.add_runtime_dependency 'bundler'
22
-
23
- s.add_development_dependency 'rake'
24
- s.add_development_dependency 'rspec'
25
- s.add_development_dependency 'simplecov'
26
- s.add_development_dependency 'coveralls'
27
- end