dav4rack 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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