dav4rack 0.1.1 → 0.1.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.
data/README.rdoc CHANGED
@@ -119,9 +119,8 @@ virtualized resources.
119
119
  There are some helpers worth mentioning that make things a little easier. DAV4Rack::Resource#accept_redirect? method is available to Resources.
120
120
  If true, the currently connected client will accept and properly use a 302 redirect for a GET request. Most clients do not properly
121
121
  support this, which can be a real pain when working with virtualized files that may be located some where else, like S3. To deal with
122
- those clients that don't support redirects, a helper has been provided so resources don't have to deal with proxying themselves. The
123
- DAV4Rack::RemoteFile allows the resource to simply tell Rack to download and send the file from the provided resource and go away, allowing the
124
- process to be freed up to deal with other waiters. A very simple example:
122
+ those clients that don't support redirects, a helper has been provided so resources don't have to deal with proxying themselves. The DAV4Rack::RemoteFile
123
+ is a modified Rack::File that can do some interesting things. First, lets look at its most basic use:
125
124
 
126
125
  class MyResource < DAV4Rack::Resource
127
126
  def setup
@@ -138,6 +137,62 @@ process to be freed up to deal with other waiters. A very simple example:
138
137
  end
139
138
  end
140
139
 
140
+ This is a simple proxy. When Rack receives the RemoteFile, it will pull a chunk of data from object, which in turn pulls it from the socket, and
141
+ sends it to the user over and over again until the EOF is reached. This much the same method that Rack::File uses but instead we are pulling
142
+ from a socket rather than an actual file. Now, instead of proxying these files from a remote server every time, lets cache them:
143
+
144
+ response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :cache_directory => '/tmp')
145
+
146
+ Providing the :cache_directory will let RemoteFile cache the items locally, and then search for them on subsequent requests before heading out
147
+ to the network. The cached file name is based off the SHA1 hash of the file path, size and last modified time. It is important to note that for
148
+ services like S3, the path will often change, making this cache pretty worthless. To combat this, we can provide a reference to use instead:
149
+
150
+ response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :cache_directory => '/tmp', :cache_ref => item[:static_url])
151
+
152
+ These methods will work just fine, but it would be really nice to just let someone else deal with the proxying and let the process get back
153
+ to dealing with actual requests. RemoteFile will happily do that as long as the frontend server is setup correctly. Using the sendfile approach
154
+ will tell the RemoteFile to simply pass the headers on and let the server deal with doing the actual proxying. First, lets look at an implementation
155
+ using all the features, and then degrade that down to the bare minimum. These examples are NGINX specific, but are based off the Rack::Sendfile implementation
156
+ and as such should be applicable to other servers. First, a simplified NGINX server block:
157
+
158
+ server {
159
+ listen 80;
160
+ location / {
161
+ proxy_set_header X-Real-IP $remote_addr;
162
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
163
+ proxy_set_header Host $http_host;
164
+ proxy_set_header X-Sendfile-Type X-Accel-Redirect;
165
+ proxy_set_header X-Accel-Remote-Mapping webdav_redirect
166
+ proxy_pass http://my_app_server;
167
+ }
168
+
169
+ location ~* /webdav_redirect/(.*?)/(.*) {
170
+ internal;
171
+ resolver 127.0.0.1;
172
+ set $re_host $1;
173
+ set $re_path $2;
174
+ set $re_url http://$re_host/$re_path?$args;
175
+ proxy_set_header Authorization '';
176
+ proxy_set_header Host $re_host;
177
+ proxy_max_temp_file_size 0;
178
+ proxy_pass $re_url;
179
+ }
180
+ }
181
+
182
+ With this in place, the parameters for the RemoteFile change slightly:
183
+
184
+ response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => true)
185
+
186
+ The RemoteFile will automatically take care of building out the correct path and sending the proper headers. If the X-Accel-Remote-Mapping header
187
+ is not available, you can simply pass the value:
188
+
189
+ response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => true, :sendfile_prefix => 'webdav_redirect')
190
+
191
+ And if you don't have the X-Sendfile-Type header set, you can fix that by changing the value of :sendfile:
192
+
193
+ response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => 'X-Accel-Redirect', :sendfile_prefix => 'webdav_redirect')
194
+
195
+ And if you have none of the above because your server hasn't been configured for sendfile support, your out of luck until it's configured.
141
196
 
142
197
  == Authentication
143
198
 
@@ -160,7 +215,7 @@ Callbacks can be called before or after a method call. For example:
160
215
 
161
216
  class MyResource < DAV4Rack::Resource
162
217
  before do |resource, method_name|
163
- my_authentication_method
218
+ resource.send(:my_authentication_method)
164
219
  end
165
220
 
166
221
  after do |resource, method_name|
data/dav4rack.gemspec CHANGED
@@ -1,5 +1,5 @@
1
1
  $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
2
- require 'dav4rack'
2
+ require 'dav4rack/version'
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'dav4rack'
5
5
  s.version = DAV4Rack::VERSION
@@ -30,6 +30,7 @@ lib/dav4rack/remote_file.rb
30
30
  lib/dav4rack/lock.rb
31
31
  lib/dav4rack/lock_store.rb
32
32
  lib/dav4rack/logger.rb
33
+ lib/dav4rack/version.rb
33
34
  bin/dav4rack
34
35
  spec/handler_spec.rb
35
36
  README.rdoc
@@ -51,7 +51,7 @@ module DAV4Rack
51
51
  Logger.info "Processing WebDAV request: #{request.path} (for #{request.ip} at #{Time.now}) [#{request.request_method}]"
52
52
  Logger.info "Completed in: #{((Time.now.to_f - start.to_f) * 1000).to_i} ms | #{response.status} [#{request.url}]"
53
53
 
54
- response.finish
54
+ response.body.is_a?(Rack::File) ? response.body.call(env) : response.finish
55
55
  end
56
56
 
57
57
  end
@@ -11,44 +11,64 @@ module DAV4Rack
11
11
 
12
12
  alias :to_path :path
13
13
 
14
- # path:: Path to remote file
15
- # args:: Optional argument hash. Allowed keys: :size, :mime_type, :last_modified
16
- # Create a reference to a remote file.
17
- # NOTE: HTTPError will be raised if path does not return 200 result
14
+ # path:: path to file (Actual path, preferably a URL since this is a *REMOTE* file)
15
+ # args:: Hash of arguments:
16
+ # :size -> Integer - number of bytes
17
+ # :mime_type -> String - mime type
18
+ # :last_modified -> String/Time - Time of last modification
19
+ # :sendfile -> True or String to define sendfile header variation
20
+ # :cache_directory -> Where to store cached files
21
+ # :cache_ref -> Reference to be used for cache file name (useful for changing URLs like S3)
22
+ # :sendfile_prefix -> String directory prefix. Eg: 'webdav' will result in: /wedav/#{path.sub('http://', '')}
23
+ # :sendfile_fail_gracefully -> Boolean if true will simply proxy if unable to determine proper sendfile
18
24
  def initialize(path, args={})
19
- @fpath = args[:url]
20
- @size = args[:size] || nil
21
- @mime_type = args[:mime_type] || 'text/plain'
22
- @modified = args[:last_modified] || nil
23
- @cache = args[:cache_directory] || nil
24
- cached_file = @cache + '/' + Digest::SHA1.hexdigest(@fpath)
25
- if(File.exists?(cached_file))
25
+ @path = path
26
+ @args = args
27
+ @heads = {}
28
+ @cache_file = args[:cache_directory] ? cache_file_path : nil
29
+ @redefine_prefix = nil
30
+ if(@cache_file && File.exists?(@cache_file))
26
31
  @root = ''
27
- @path_info = cached_file
32
+ @path_info = @cache_file
28
33
  @path = @path_info
34
+ elsif(args[:sendfile])
35
+ @redefine_prefix = 'sendfile'
36
+ @sendfile_header = args[:sendfile].is_a?(String) ? args[:sendfile] : nil
29
37
  else
30
- begin
31
- @cf = File.open(cached_file, 'w+')
32
- rescue
33
- @cf = nil
34
- end
35
- @uri = URI.parse(path)
36
- @con = Net::HTTP.new(@uri.host, @uri.port)
37
- @call_path = @uri.path + (@uri.query ? "?#{@uri.query}" : '')
38
- res = @con.request_get(@call_path)
39
- @heads = res.to_hash
40
- res.value
41
- @store = nil
42
- self.public_methods.each do |method|
43
- m = method.to_s.dup
44
- next unless m.slice!(0,7) == 'remote_'
45
- self.class.class_eval "undef :'#{m}'"
46
- self.class.class_eval "alias :'#{m}' :'#{method}'"
47
- end
38
+ setup_remote
48
39
  end
40
+ do_redefines(@redefine_prefix) if @redefine_prefix
41
+ end
42
+
43
+ # env:: Environment variable hash
44
+ # Process the call
45
+ def call(env)
46
+ serving(env)
49
47
  end
50
48
 
51
- def remote_serving
49
+ # env:: Environment variable hash
50
+ # Return an empty result with the proper header information
51
+ def sendfile_serving(env)
52
+ header = @sendfile_header || env['sendfile.type'] || env['HTTP_X_SENDFILE_TYPE']
53
+ unless(header)
54
+ raise 'Failed to determine proper sendfile header value' unless @args[:sendfile_fail_gracefully]
55
+ setup_remote
56
+ do_redefines('remote')
57
+ call(env)
58
+ end
59
+ prefix = (@args[:sendfile_prefix] || env['HTTP_X_ACCEL_REMOTE_MAPPING']).to_s.sub(/^\//, '').sub(/\/$/, '')
60
+ [200, {
61
+ "Last-Modified" => last_modified,
62
+ "Content-Type" => content_type,
63
+ "Content-Length" => size,
64
+ header => "/#{prefix}/#{URI.decode(@path.sub('http://', ''))}"
65
+ },
66
+ ['']]
67
+ end
68
+
69
+ # env:: Environment variable hash
70
+ # Return self to be processed
71
+ def remote_serving(e)
52
72
  [200, {
53
73
  "Last-Modified" => last_modified,
54
74
  "Content-Type" => content_type,
@@ -56,15 +76,7 @@ module DAV4Rack
56
76
  }, self]
57
77
  end
58
78
 
59
-
60
- def remote_call(env)
61
- dup._call(env)
62
- end
63
-
64
- def remote__call(env)
65
- serving
66
- end
67
-
79
+ # Get the remote file
68
80
  def remote_each
69
81
  if(@store)
70
82
  yield @store
@@ -78,19 +90,57 @@ module DAV4Rack
78
90
  end
79
91
  end
80
92
 
93
+ # Size based on remote headers or given size
81
94
  def size
82
95
  @heads['content-length'] || @size
83
96
  end
84
97
 
85
98
  private
86
99
 
100
+ # Content type based on provided or remote headers
87
101
  def content_type
88
102
  @mime_type || @heads['content-type']
89
103
  end
90
104
 
105
+ # Last modified type based on provided, remote headers or current time
91
106
  def last_modified
92
- @heads['last-modified'] || @modified
107
+ @heads['last-modified'] || @modified || Time.now.httpdate
93
108
  end
94
109
 
110
+ # Builds the path for the cached file
111
+ def cache_file_path
112
+ raise IOError.new 'Write permission is required for cache directory' unless File.writable?(@args[:cache_directory])
113
+ "#{@args[:cache_directory]}/#{Digest::SHA1.hexdigest((@args[:cache_ref] || @path).to_s + size.to_s + last_modified.to_s)}.cache"
114
+ end
115
+
116
+ # prefix:: prefix of methods to be redefined
117
+ # Redefine methods to do what we want in the proper situation
118
+ def do_redefines(prefix)
119
+ self.public_methods.each do |method|
120
+ m = method.to_s.dup
121
+ next unless m.slice!(0, prefix.to_s.length + 1) == "#{prefix}_"
122
+ self.class.class_eval "undef :'#{m}'"
123
+ self.class.class_eval "alias :'#{m}' :'#{method}'"
124
+ end
125
+ end
126
+
127
+ # Sets up all the requirements for proxying a remote file
128
+ def setup_remote
129
+ if(@cache_file)
130
+ begin
131
+ @cf = File.open(@cache_file, 'w+')
132
+ rescue
133
+ @cf = nil
134
+ end
135
+ end
136
+ @uri = URI.parse(@path)
137
+ @con = Net::HTTP.new(@uri.host, @uri.port)
138
+ @call_path = @uri.path + (@uri.query ? "?#{@uri.query}" : '')
139
+ res = @con.request_get(@call_path)
140
+ @heads = res.to_hash
141
+ res.value
142
+ @store = nil
143
+ @redefine_prefix = 'remote'
144
+ end
95
145
  end
96
146
  end
@@ -0,0 +1,3 @@
1
+ module DAV4Rack
2
+ VERSION = '0.1.2'
3
+ end
data/lib/dav4rack.rb CHANGED
@@ -7,7 +7,3 @@ require 'dav4rack/http_status'
7
7
  require 'dav4rack/resource'
8
8
  require 'dav4rack/handler'
9
9
  require 'dav4rack/controller'
10
-
11
- module DAV4Rack
12
- VERSION = '0.1.1'
13
- end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dav4rack
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 31
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 1
10
- version: 0.1.1
9
+ - 2
10
+ version: 0.1.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Chris Roberts
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-11 00:00:00 -07:00
18
+ date: 2010-08-12 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -74,6 +74,7 @@ files:
74
74
  - lib/dav4rack/lock.rb
75
75
  - lib/dav4rack/lock_store.rb
76
76
  - lib/dav4rack/logger.rb
77
+ - lib/dav4rack/version.rb
77
78
  - bin/dav4rack
78
79
  - spec/handler_spec.rb
79
80
  - README.rdoc