downspout 0.2.2

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.
@@ -0,0 +1,344 @@
1
+ module Downspout
2
+ # The object returned by a call to fetch_url() or download_url_to_disk().
3
+ class Downloader < Base
4
+
5
+ # returns the path to the downloaded file
6
+ attr_accessor :path
7
+
8
+ # returns the remote response as the appropriate Net::HTTPResponse
9
+ attr_reader :response
10
+
11
+ # returns the headers parsed from the remote response
12
+ attr_reader :response_headers
13
+
14
+ # returns the URI parsed from the URL
15
+ attr_reader :uri
16
+
17
+ # returns the URL initially given
18
+ attr_accessor :url
19
+
20
+ def initialize( options=nil ) #:nodoc:
21
+ @basename = nil
22
+ @curb_enabled = Downspout::Config.use_curb?
23
+ @response_headers = {}
24
+ @started_at = nil
25
+ @finished_at = nil
26
+
27
+ if options.respond_to?(:keys) then
28
+ options.each do |key, value|
29
+ self.send("#{key}=", value)
30
+ end
31
+ end
32
+
33
+ @uri = URI.parse( @url ) unless @url.nil?
34
+ end
35
+
36
+ def to_s #:nodoc:
37
+ return @path
38
+ end
39
+
40
+ # returns the protocol or 'scheme' of the URL
41
+ def scheme
42
+ return @uri.scheme unless @uri.nil?
43
+ return nil
44
+ end
45
+
46
+ # returns the time taken to download the file
47
+ def duration
48
+ return nil unless @started_at
49
+ return nil unless @finished_at
50
+
51
+ return @finished_at - @started_at
52
+ end
53
+
54
+ # Extracts the file name from the URL or uses a default name based on the content-type header
55
+ def basename
56
+ return @basename unless @basename.nil?
57
+
58
+ if !(@path.nil?) then
59
+ @basename = File.basename( @path )
60
+ else
61
+ if !(@uri.path.nil? || @uri.path.empty? || uri.path == '/')
62
+ @basename = File.basename( @uri.path )
63
+ else
64
+ $logger.debug("downspout | downloader | basename | Bad URI path")
65
+ @basename = 'file.downspout'
66
+ end
67
+ end
68
+
69
+ $logger.debug("downspout | downloader | basename | #{@basename} ")
70
+
71
+ return @basename
72
+ end
73
+
74
+ # will this download use the Curb library?
75
+ def use_curb?
76
+ return @curb_enabled
77
+ end
78
+
79
+ # will this download use the default Net/HTTP library?
80
+ def use_net_http?
81
+ return false if use_curb?
82
+ return true
83
+ end
84
+
85
+ # configure this download to use the Curb library (will fail if Curb is unavailable.)
86
+ def enable_curb!
87
+ @curb_enabled = true if Downspout::Config.curb_available?
88
+
89
+ return @curb_enabled
90
+ end
91
+
92
+ # configure this download NOT to use the Curb library
93
+ def disable_curb!
94
+ @curb_enabled = false
95
+ end
96
+
97
+ def download! #:nodoc:
98
+ $logger.debug("downspout | downloader | download! | URL : #{@url} ")
99
+ @started_at = Time.now
100
+
101
+ raise UnsupportedScheme if @uri.nil?
102
+ raise UnsupportedScheme unless Downspout.supported_protocol?( @uri.scheme )
103
+
104
+ if @path.nil? then
105
+ tf = Downspout::Tmpfile.new( :name => self.basename )
106
+ @path = tf.path
107
+ end
108
+ $logger.debug("downspout | downloader | download! | Path : #{@path} ")
109
+
110
+ remove_file_at_target_path
111
+
112
+ if Downspout::Config.network_enabled? then
113
+ case self.scheme
114
+ when /ftp/
115
+ net_ftp_download
116
+ when /http[s]?/
117
+ if use_curb? then
118
+ curb_http_download
119
+ else
120
+ net_http_download
121
+ end
122
+ else
123
+ $logger.error("downspout | downloader | download! | Unknown URL Scheme : '#{self.scheme}'")
124
+ raise UnsupportedScheme
125
+ end
126
+ else
127
+ $logger.warn("downspout | downloader | download! | >>>>> Networking Disabled <<<<<")
128
+ end
129
+
130
+ downloaded = File.exist?( @path )
131
+
132
+ $logger.debug("downspout | downloader | download! | #{self.basename} downloaded? : #{downloaded} ")
133
+ @finished_at = Time.now
134
+
135
+ if (tf && @basename == 'file.downspout') then
136
+ # rename file more appropriately
137
+ new_name = generate_file_name
138
+ if !(new_name.nil?) then
139
+ $logger.debug("downspout | downloader | download! | Renaming #{@basename} to #{new_name} ...")
140
+ new_path = File.join( File.dirname( tf.path ), new_name)
141
+ FileUtils.mv( tf.path, new_path )
142
+ @path = new_path
143
+ end
144
+ end
145
+
146
+ $logger.debug("downspout | downloader | download! | Started: #{@started_at.utc}, Finished: #{@finished_at.utc}, Duration: #{duration}")
147
+
148
+ return downloaded
149
+ end
150
+
151
+ private
152
+
153
+ def remove_file_at_target_path
154
+ if File.exist?( @path ) then
155
+ $logger.debug("downspout | downloader | remove_file_at_target_path | Removing #{@path} ... ")
156
+ FileUtils.rm( @path )
157
+ end
158
+ end
159
+
160
+ def net_ftp_download
161
+ $logger.debug("downspout | downloader | net_ftp_download | Downloading #{@url} ...")
162
+
163
+ # look up the credentials for this host
164
+ cred = Downspout::Config.credentials.select{|c| c.scheme == 'ftp' }.select{ |c| c.host == @uri.host }.first
165
+ if cred.nil? then
166
+ $logger.warn("downspout | downloader | net_ftp_download | No credentials found for '#{@uri.host}'.")
167
+ # proceed anyway - slight possibility it's an un-authorized FTP account...
168
+ else
169
+ $logger.debug("downspout | downloader | net_ftp_download | Loaded credentials for #{cred.host} ...")
170
+ end
171
+
172
+ begin
173
+ ftp = Net::FTP.open( @uri.host ) do |ftp|
174
+ ftp.login( cred.user_name, cred.pass_word ) unless cred.nil?
175
+ ftp.passive
176
+ ftp.chdir( File.dirname( @uri.path ) )
177
+
178
+ $logger.debug("downspout | downloader | net_ftp_download | Local Path : #{@path} ...")
179
+ ftp.getbinaryfile( self.basename, @path )
180
+ end
181
+ rescue Exception => e
182
+ $logger.error("downspout | downloader | net_ftp_download | Exception : #{e}")
183
+ raise e
184
+ end
185
+
186
+ done = File.exist?( @path )
187
+ $logger.debug("downspout | downloader | net_ftp_download | #{basename} downloaded? : #{done}.")
188
+
189
+ return done
190
+ end
191
+
192
+ def net_http_download
193
+ $logger.debug("downspout | downloader | net_http_download | Downloading #{@url} ...")
194
+
195
+ begin
196
+ response = net_http_fetch( @url , 1)
197
+ open( @path, "wb" ) do |file|
198
+
199
+ file.write(response.body)
200
+ end
201
+
202
+ $logger.debug("downspout | downloader | net_http_download | Response Body : #{response.body[0..5].strip}")
203
+
204
+ rescue SocketError => dns_err
205
+ $logger.error("downspout | downloader | net_http_download | Net/HTTP DNS Error | #{@uri.host} | #{dns_err.inspect}")
206
+ remove_file_at_target_path
207
+ raise dns_err
208
+ end
209
+
210
+ $logger.debug("downspout | downloader | net_http_download | Response Code : #{response.code}")
211
+
212
+ # populate the response headers from net/http headers...
213
+ new_header_str = "HTTP/1.1 #{@response.code} #{@response.message}\r\n"
214
+ @response.each_header do |k,v|
215
+ new_header_str += "#{k}: #{v}\r\n"
216
+ end
217
+ @response_headers = parse_headers_from_string( new_header_str )
218
+
219
+
220
+ if ((response.code.to_i != 200) and (response.code.to_i != 202)) then
221
+ # missing file, failed download - delete the response body [if downloaded]
222
+ remove_file_at_target_path
223
+ return false
224
+ end
225
+
226
+ $logger.debug("downspout | downloader | net_http_download | Headers : #{response.header}")
227
+
228
+ if !( File.exist?( @path ) ) then
229
+ $logger.error("downspout | downloader | net_http_download | Missing File at download path : #{@path}")
230
+ return false
231
+ end
232
+
233
+ $logger.debug("downspout | downloader | net_http_download | Successful.")
234
+ return true
235
+ end
236
+
237
+ def net_http_fetch( url_str, limit = 10 )
238
+ $logger.debug("downspout | downloader | net_http_fetch | URL: #{url_str}, Redirects: #{limit}.")
239
+ raise Downspout::BadURL, 'URL is missing' if url_str.nil?
240
+ raise Downspout::ExcessiveRedirects, 'HTTP redirect too deep' if limit == 0
241
+
242
+ u = URI.parse( url_str )
243
+
244
+ my_request = Net::HTTP::Get.new( "#{u.path}?#{u.query}" )
245
+
246
+ # TODO : implement credentials for downloads via net_http_fetch
247
+ my_request.basic_auth 'account', 'p4ssw0rd'
248
+
249
+ $logger.debug("downspout | downloader | net_http_fetch | Firing...")
250
+ @response = Net::HTTP.start( u.host, u.port ) do |http|
251
+ http.request( my_request )
252
+ end
253
+
254
+ $logger.debug("downspout | downloader | net_http_fetch | Response : #{@response}")
255
+
256
+ case @response
257
+ when Net::HTTPSuccess
258
+ @response
259
+ when Net::HTTPRedirection
260
+ net_http_fetch( @response['location'], limit - 1 )
261
+ else
262
+ @response.error!
263
+ end
264
+ end
265
+
266
+ def curb_http_download
267
+ $logger.debug("downspout | downloader | curb_http_download | Downloading #{@url} ...")
268
+
269
+ begin
270
+ curb = Curl::Easy.download( @url, @path) {|c| c.follow_location=true; c.max_redirects=1;}
271
+ rescue Curl::Err::HostResolutionError
272
+ $logger.error("downspout | downloader | curb_http_download | Curb/Curl DNS Error | #{@uri.host}")
273
+ return false
274
+ end
275
+
276
+ $logger.debug("downspout | downloader | curb_http_download | Response Code : #{curb.response_code}")
277
+
278
+ if ((curb.response_code != 200) and (curb.response_code != 202)) then
279
+ # missing file, failed download - delete the response body [if downloaded]
280
+ remove_file_at_target_path
281
+ end
282
+
283
+ $logger.debug("downspout | downloader | curb_http_download | Headers : #{curb.header_str}")
284
+
285
+ # populate the response headers from curb header string
286
+ @response_headers = parse_headers_from_string( curb.header_str )
287
+
288
+ # populate a 'proxy' HTTPResponse object with the Curb data...
289
+ hr_klass = Net::HTTPResponse.send('response_class', curb.response_code.to_s)
290
+ $logger.debug("downspout | downloader | curb_http_download | Response Type : #{hr_klass.name}")
291
+
292
+ @response = hr_klass.new( @response_headers["HTTP"][:version],
293
+ curb.response_code,
294
+ @response_headers["HTTP"][:message] )
295
+
296
+ $logger.debug("downspout | downloader | curb_http_download | Response : #{@response.inspect}")
297
+
298
+ if !( File.exist?( @path ) ) then
299
+ $logger.error("downspout | downloader | curb_http_download | Missing File at download path : #{@path}")
300
+ return false
301
+ end
302
+
303
+ $logger.debug("downspout | downloader | curb_http_download | Successful.")
304
+ return true
305
+ end
306
+
307
+ def parse_headers_from_string( header_str )
308
+ $logger.debug("downspout | downloader | parse_headers_from_string | String : #{header_str}")
309
+ header_hash = {}
310
+ http_hash = {}
311
+ headers = header_str.split("\r\n")
312
+
313
+ http_info = headers[0]
314
+ http_hash[:header] = http_info
315
+ http_hash[:version] = http_info.split(" ")[0].match("HTTP/([0-9\.]+)")[1]
316
+ http_hash[:code] = (http_info.split("\r\n")[0].split(" ")[1]).to_i
317
+ http_hash[:message] = http_info.split("\r\n")[0].split(" ")[2]
318
+
319
+ $logger.debug("downspout | downloader | parse_headers_from_string | Response : #{http_hash[:version]}, #{http_hash[:code]}, #{http_hash[:message]}")
320
+ header_hash["HTTP"] = http_hash
321
+
322
+ headers[1..-1].each do |line|
323
+ header_name, header_value = line.match(/([\w\-\s]+)\:\s?(.*)/)[1..2]
324
+ header_hash[header_name] = header_value
325
+ end
326
+
327
+ return header_hash
328
+ end
329
+
330
+ def generate_file_name
331
+ file_type = @response_headers['Content-Type'] if use_curb?
332
+ file_type = @response_headers['Content-Type'] if use_net_http?
333
+ return nil unless file_type
334
+
335
+ return "default.html" if file_type =~ /text\/html/
336
+ # TODO : smarter file name generation
337
+
338
+ return nil
339
+ end
340
+
341
+ end
342
+
343
+
344
+ end
@@ -0,0 +1,11 @@
1
+ if !(defined?( $logger )) then
2
+ if defined?( RAILS_DEFAULT_LOGGER ) then
3
+ $logger = RAILS_DEFAULT_LOGGER
4
+ else
5
+ require 'logger'
6
+ $logger = Logger.new( STDERR )
7
+ $logger.level = Logger::INFO
8
+ end
9
+ end
10
+
11
+ $logger.debug "initialized logging facility..."
@@ -0,0 +1,101 @@
1
+ require 'fileutils'
2
+ require 'tempfile'
3
+
4
+ module Downspout
5
+
6
+ class Tmpfile < File
7
+
8
+ # acepts an options hash which can include either or both of :name and :prefix
9
+ # then creates a Tempfile with the optionally given name in a unique sub-folder
10
+ # of the configured directory, optionally named with the prefix string.
11
+ # The unique folder name includes the prefix, a sortable date, the PID of
12
+ # the download process, and a randomly generated sequence of characters.
13
+ #
14
+ # => "/tmp/downloads/my-app-20110203-59488-1run8k2-0/desired-file-name.txt"
15
+ #
16
+ # call-seq:
17
+ # Downspout::Tmpfile.new( :name => 'desired-file-name.txt', :prefix => 'my-app' )
18
+ #
19
+ def initialize( options = nil )
20
+ # make sure the configured directory exists
21
+ FileUtils.mkdir_p( Downspout::Config.tmp_dir )
22
+
23
+ defaults = {:prefix => 'downspout', :name => "downloaded_file.tmp"}
24
+
25
+ # overwrite defaults with given options
26
+ defaults.merge!( options ) unless options.nil?
27
+
28
+ # create a unique file path from the given options
29
+ unique_path = File.join( Downspout::Config.tmp_dir, tmp_dir_name( defaults[:prefix] ), defaults[:name] )
30
+
31
+ # make sure the unique directory exists
32
+ $logger.debug("downspout | tmpfile | initialize | Creating unique directory : #{File.dirname(unique_path)}")
33
+ FileUtils.mkdir_p( File.dirname( unique_path ) )
34
+ raise "MakeDir Error" unless File.exist?( File.dirname( unique_path ) )
35
+
36
+ super( unique_path, File::CREAT, 0644 )
37
+
38
+ end
39
+
40
+ def self.clean_dir( dir_path, delay=30 ) #:nodoc:
41
+ # remove files older than DELAY (in minutes) from configured folder
42
+ delay = 30 unless (delay.class == Fixnum)
43
+ t0 = Time.now - ( delay * 60 )
44
+
45
+ return false unless File.exist?( dir_path )
46
+ the_dir = Dir.new( dir_path )
47
+
48
+ $logger.debug( "downspout | tmpfile | clean_dir | start | Entries : #{the_dir.entries.size}" )
49
+
50
+ the_dir.entries.each do |item|
51
+ next if item == "."
52
+ next if item == ".."
53
+
54
+ $logger.debug( "downspout | tmpfile | clean_dir | sub item : #{item}" )
55
+
56
+ item_path = File.join( dir_path, item )
57
+
58
+ tx = File.mtime( item_path ).utc
59
+
60
+ # skip files with modtime changed less than sixty minutes ago
61
+ next unless (tx < t0)
62
+
63
+ if File.directory?( item_path ) then
64
+ $logger.debug( "downspout | tmpfile | clean_dir | Removing Directory : #{item}/*" )
65
+
66
+ clean_dir( item_path, delay )
67
+
68
+ begin
69
+ FileUtils.rmdir( item_path )
70
+ rescue Exception => e
71
+ $logger.debug( "downspout | tmpfile | clean_dir | Exception : #{e}" )
72
+ return false
73
+ end
74
+ else
75
+ $logger.debug( "downspout | tmpfile | clean_dir | Removing Item : #{item}" )
76
+
77
+ FileUtils.rm( item_path )
78
+ end
79
+ end
80
+
81
+ $logger.debug( "downspout | tmpfile | clean_download_dir | finish | Entries : #{the_dir.entries.size}" )
82
+ return true
83
+ end
84
+
85
+ private
86
+
87
+ def tmp_dir_name( prefix, n=rand(9) )
88
+ t = Time.now.strftime("%Y%m%d")
89
+ path = "#{prefix}-#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}"
90
+ end
91
+
92
+ end
93
+
94
+
95
+ # Utility method for periodically removing download files from the configured directory.
96
+ # Expects an integer as the number of minutes 'old' a file should be before removal.
97
+ def self.clean_download_dir( delay=30 )
98
+ Tmpfile.clean_dir( Downspout::Config.tmp_dir, delay )
99
+ end
100
+
101
+ end
data/lib/downspout.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+
3
+ # common dependencies
4
+ require 'fileutils'
5
+ require 'uri'
6
+ require 'net/http'
7
+ require 'net/ftp'
8
+
9
+ # unusual dependencies
10
+ require 'curb'
11
+
12
+ # add this directory to the path...
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+
15
+ # customized logger
16
+ require 'downspout/logger'
17
+
18
+ # required components
19
+ require 'downspout/base'
20
+ require 'downspout/config'
21
+ require 'downspout/credential'
22
+ require 'downspout/tmp_file'
23
+ require 'downspout/downloader'
@@ -0,0 +1,127 @@
1
+ require 'test_helper'
2
+ require 'test_servlet'
3
+
4
+ class DownspoutTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ $test_ws_root ||= Test::App.root
8
+ $test_read_me_url = "http://127.0.0.1:8899/READ_ME.rdoc"
9
+ $test_image_url = "http://127.0.0.1:8899/images/ruby.png"
10
+
11
+ $test_ws ||= WEBrick::HTTPServer.new(:Port => 8899,
12
+ :DocumentRoot => @test_ws_root,
13
+ # :Logger => Log.new(nil, BasicLog::WARN), # TODO : Use Log/BasicLog from WEBrick to reduce spam in tests
14
+ :Logger => $logger,
15
+ :AccessLog => [])
16
+
17
+
18
+ $test_ws.mount(TestServlet.path, TestServlet)
19
+
20
+ two_deep_proc = Proc.new { |req, resp|
21
+ resp.body = '2-deep redirector proc mounted on #{req.script_name}'
22
+ resp.set_redirect( HTTPStatus::MovedPermanently, '/one/deep')
23
+ }
24
+
25
+ $test_ws.mount('/two/deep/', HTTPServlet::ProcHandler.new(two_deep_proc) )
26
+
27
+ redir_proc = Proc.new { |req, resp|
28
+ resp.body = 'redirector proc mounted on #{req.script_name}'
29
+ resp.set_redirect( HTTPStatus::MovedPermanently, '/READ_ME.rdoc')
30
+ }
31
+
32
+ $test_ws.mount('/one/deep/', HTTPServlet::ProcHandler.new(redir_proc) )
33
+
34
+ $test_ws.mount("/images", HTTPServlet::FileHandler,
35
+ File.join( Test::App.root, "test", "fixtures"), {:FancyIndexing => true} )
36
+
37
+ $test_ws_thread = Thread.new { $test_ws.start }
38
+ end
39
+
40
+ def test_download_rdoc_from_servlet
41
+ some_url = $test_read_me_url
42
+
43
+ dl = Downspout.fetch_url( some_url )
44
+
45
+ assert_not_nil dl
46
+
47
+ assert File.exist?( dl.path )
48
+ end
49
+
50
+ def test_by_fetching_image_from_w3c
51
+ some_url = "http://www.w3.org/wiki/images/2/2e/Ruby01.png"
52
+
53
+ dl = Downspout.fetch_url( some_url )
54
+
55
+ assert_not_nil dl
56
+
57
+ assert File.exist?( dl.path )
58
+ end
59
+
60
+ context "Downspout" do
61
+ should "define the test URL" do
62
+ assert_not_nil $test_read_me_url
63
+ end
64
+
65
+ context "for HTTP URLs" do
66
+ should "download file via HTTP from local TestServlet" do
67
+ assert Downspout.fetch_url( $test_read_me_url )
68
+ end
69
+ end
70
+
71
+ should "fail with Curb error in case of excessive redirects" do
72
+ two_deep_url = "http://127.0.0.1:8899/two/deep?curby"
73
+
74
+ assert_raise Curl::Err::TooManyRedirectsError do
75
+ dl = Downspout.fetch_url( two_deep_url )
76
+ end
77
+ end
78
+
79
+ context "with Curb disabled" do
80
+ setup do
81
+ Downspout::Config.disable_curb!
82
+ end
83
+
84
+ should "fail with Downspout error in case of excessive redirects" do
85
+ two_deep_url = "http://127.0.0.1:8899/two/deep?no-curb"
86
+
87
+ assert_raise Downspout::ExcessiveRedirects do
88
+ dl = Downspout.fetch_url( two_deep_url )
89
+ end
90
+
91
+ end
92
+
93
+ teardown do
94
+ Downspout::Config.enable_curb!
95
+ end
96
+ end
97
+
98
+ teardown do
99
+ Downspout.clean_download_dir( 0 )
100
+ end
101
+
102
+ end
103
+
104
+ should "fail due to excessive redirects" do
105
+ @obj = Downspout::Downloader.new( :url => "http://127.0.0.1:8899/two/deep?over-draft" )
106
+
107
+ assert_raise Downspout::ExcessiveRedirects do
108
+ @obj.send('net_http_fetch', @obj.url, 1 ) # uses send to bypass Curb and force Net::HTTP
109
+ end
110
+ end
111
+
112
+ should "fail due to DNS error" do
113
+ @obj = Downspout::Downloader.new( :url => "http://fu.man.chu/deep/nested/resource?over-draft" )
114
+
115
+ assert_raise SocketError do
116
+ @obj.send('net_http_fetch', @obj.url) # uses send to bypass Curb and force Net::HTTP
117
+ end
118
+ end
119
+
120
+ should "download to custom path" do
121
+ gfx_path = File.join( Test::App.root, "tmp", "download-test", "image.png" )
122
+ FileUtils.mkdir_p( File.dirname( gfx_path ) )
123
+
124
+ dl = Downspout.download_url_to_path( $test_image_url, gfx_path )
125
+ end
126
+
127
+ end
Binary file
data/test/servlet.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'test_servlet'
2
+
3
+ ws_root = File.expand_path( File.dirname( File.dirname( __FILE__ ) ) )
4
+
5
+ ws_app = WEBrick::HTTPServer.new(:Port => 8899,
6
+ :DocumentRoot => @test_ws_root,
7
+ :Logger => Log.new(nil, BasicLog::WARN), # Log/BasicLog from WEBrick - reduces spam in tests
8
+ # :Logger => $logger,
9
+ :AccessLog => [])
10
+
11
+ ws_app.mount(TestServlet.path, TestServlet)
12
+
13
+ two_deep_proc = Proc.new { |req, resp|
14
+ resp.body = '2-deep redirector proc mounted on #{req.script_name}'
15
+ resp.set_redirect( HTTPStatus::MovedPermanently, '/one/deep')
16
+ }
17
+
18
+ ws_app.mount('/two/deep/', HTTPServlet::ProcHandler.new(two_deep_proc) )
19
+
20
+ redir_proc = Proc.new { |req, resp|
21
+ resp.body = 'redirector proc mounted on #{req.script_name}'
22
+ resp.set_redirect( HTTPStatus::MovedPermanently, '/READ_ME.rdoc')
23
+ }
24
+
25
+ ws_app.mount('/one/deep/', HTTPServlet::ProcHandler.new(redir_proc) )
26
+
27
+ ws_thread = Thread.new { ws_app.start }
28
+
29
+ read_me_url = "#{TestServlet.url}/READ_ME.doc"
30
+
31
+ puts "Request #{read_me_url}"
32
+
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+
6
+ begin
7
+ require 'redgreen'
8
+ rescue LoadError
9
+ # nice to have, aesthetic, not functional
10
+ end
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+
15
+ class Test::Unit::TestCase
16
+ end
17
+
18
+ module Test
19
+ module App
20
+ def self.root
21
+ return File.expand_path( File.dirname( File.dirname( __FILE__ ) ) )
22
+ end
23
+ end
24
+ end
25
+
26
+ # test_logger must be loaded before downspout
27
+ require 'test_logger'
28
+ require 'test_servlet'
29
+
30
+ # The object of our affections
31
+ require 'downspout'
@@ -0,0 +1,28 @@
1
+ require 'logger'
2
+
3
+ if !defined?( RAILS_DEFAULT_LOGGER ) then
4
+ root_dir = File.dirname( File.dirname(__FILE__) )
5
+ test_log_path = File.join( root_dir, 'tmp', 'log', 'test.log')
6
+
7
+ begin
8
+ require 'fileutils'
9
+ # creates the log directory if possible
10
+ FileUtils.mkdir_p( File.dirname( test_log_path ) )
11
+
12
+ # touching the file ensures it is writable
13
+ FileUtils.touch( test_log_path )
14
+
15
+ if File.exist?( File.dirname( test_log_path ) ) then
16
+ $logger = Logger.new( test_log_path )
17
+ else
18
+ $logger = Logger.new( STDERR )
19
+ end
20
+
21
+ rescue Exception => e
22
+ # ignore the error and try to carry on...
23
+ $logger = Logger.new( STDERR )
24
+ $logger.warn "Failed to create log file due to exception : #{e}"
25
+ end
26
+ end
27
+
28
+ $logger.level = Logger::DEBUG