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 +59 -4
- data/dav4rack.gemspec +2 -1
- data/lib/dav4rack/handler.rb +1 -1
- data/lib/dav4rack/remote_file.rb +91 -41
- data/lib/dav4rack/version.rb +3 -0
- data/lib/dav4rack.rb +0 -4
- metadata +5 -4
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
|
-
|
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
|
data/lib/dav4rack/handler.rb
CHANGED
@@ -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
|
data/lib/dav4rack/remote_file.rb
CHANGED
@@ -11,44 +11,64 @@ module DAV4Rack
|
|
11
11
|
|
12
12
|
alias :to_path :path
|
13
13
|
|
14
|
-
# path::
|
15
|
-
# args::
|
16
|
-
#
|
17
|
-
#
|
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
|
-
@
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@
|
24
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
data/lib/dav4rack.rb
CHANGED
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:
|
4
|
+
hash: 31
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.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-
|
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
|