rack-gridfs 0.3.0 → 0.4.0
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/CHANGES.rdoc +31 -0
- data/LICENSE +1 -1
- data/README.rdoc +116 -20
- data/Rakefile +8 -38
- data/lib/rack/gridfs.rb +33 -38
- data/lib/rack/gridfs/endpoint.rb +13 -0
- data/lib/rack/gridfs/endpoint/base.rb +76 -0
- data/lib/rack/gridfs/endpoint/caching.rb +34 -0
- data/lib/rack/gridfs/endpoint/connection.rb +43 -0
- data/lib/rack/gridfs/version.rb +5 -0
- metadata +123 -44
- data/.gitignore +0 -2
- data/VERSION +0 -1
- data/example/gridfs_server.rb +0 -9
- data/rack-gridfs.gemspec +0 -69
- data/test/artifacts/3wolfmoon.jpg +0 -0
- data/test/artifacts/test.html +0 -15
- data/test/artifacts/test.txt +0 -1
- data/test/gridfs_test.rb +0 -141
- data/test/test_helper.rb +0 -19
data/CHANGES.rdoc
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
== CHANGE LOG
|
2
|
+
|
3
|
+
=== 0.4.0 / May 12, 2011
|
4
|
+
|
5
|
+
Major refactoring and loads of new features! Thanks to {Ben Marini}[https://github.com/bmarini]
|
6
|
+
for his substantial contributions to this release.
|
7
|
+
|
8
|
+
{full commit log}[https://github.com/skinandbones/rack-gridfs/compare/v0.2.0...v0.4.0]
|
9
|
+
|
10
|
+
==== Features
|
11
|
+
|
12
|
+
- Allow configuration of MongoDB authentication (Steve Sloan)
|
13
|
+
- Allow option to look up objects by GridFS filename instead of +ObjectId+
|
14
|
+
(SHIBATA Hiroshi)
|
15
|
+
- Return iterable GridIO object instead of file contents, so Rack can stream in
|
16
|
+
chunks (Ches Martin)
|
17
|
+
- <tt>Rack::GridFS::Endpoint</tt>: support for mounting as a Rack endpoint in
|
18
|
+
addition to middleware (Ben Marini)
|
19
|
+
- Cache headers: set <tt>Last-Modified</tt> and +Etag+ so that
|
20
|
+
<tt>Rack::ConditionalGet</tt> sends 304s. +expires+ option to set
|
21
|
+
<tt>Cache-Control</tt> (Alexander Gräfe & Ben Marini)
|
22
|
+
- <tt>mime-types</tt> dependency so GridFS lib can determine content types
|
23
|
+
(Ben Marini)
|
24
|
+
- You can now pass a <tt>Mongo::DB</tt> instance instead of discrete database
|
25
|
+
configuration parameters. Connections are retried so we take advantage of a
|
26
|
+
+ReplSetConnection+ in high-availability architectures (Ben Marini)
|
27
|
+
|
28
|
+
==== Bug Fixes
|
29
|
+
|
30
|
+
- <tt>BSON::ObjectID</tt> renamed to +ObjectId+, and other changes supporting
|
31
|
+
current versions of Mongo libraries
|
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -2,53 +2,149 @@
|
|
2
2
|
|
3
3
|
Rack:GridFS is a Rack middleware for creating HTTP endpoints for files
|
4
4
|
stored in MongoDB's GridFS. You can configure a prefix string which
|
5
|
-
will be used to match the path of a request and
|
6
|
-
|
5
|
+
will be used to match the path of a request, and further look up GridFS
|
6
|
+
files based on either their +ObjectId+ or +filename+ field.
|
7
7
|
|
8
8
|
For example,
|
9
9
|
|
10
10
|
GET '/gridfs/someobjectid'
|
11
|
-
|
11
|
+
|
12
12
|
If the prefix is "gridfs", then the id will be be "someobjectid".
|
13
13
|
|
14
|
-
|
14
|
+
You can also use Rack::GridFS::Endpoint as a rack endpoint if you want to
|
15
|
+
handle routing another way
|
15
16
|
|
16
|
-
|
17
|
+
== Mongo Driver Compatibility Notes
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
vs. BSON::ObjectID). For now, I am choosing to support mongo-0.20.
|
19
|
+
This version is currently based on mongo-1.2+. As there were significant changes
|
20
|
+
to the GridFS API prior to v1.0, you may have luck with the git-tagged version
|
21
|
+
0.2.0 of this library with earlier versions of the driver.
|
22
22
|
|
23
23
|
== Installation
|
24
24
|
|
25
|
-
|
25
|
+
gem install rack-gridfs
|
26
|
+
|
27
|
+
== Features
|
28
|
+
- Use as rack middleware or mount as a rack endpoint
|
29
|
+
- File lookup using a path or object id
|
30
|
+
- Chunked transfer encoding, keeps memory usage low
|
31
|
+
- Content-Type header set using 'mime-types' gem
|
32
|
+
- Last-Modified and Etag headers set automatically for conditional get support
|
33
|
+
- Cache-Control header support
|
34
|
+
- High availability when using replication sets
|
26
35
|
|
27
36
|
== Usage
|
28
37
|
|
29
38
|
require 'rack/gridfs'
|
30
|
-
use Rack::GridFS, :hostname => 'localhost', :port => 27017, :database => 'test'
|
39
|
+
use Rack::GridFS, :prefix => 'gridfs', :hostname => 'localhost', :port => 27017, :database => 'test'
|
31
40
|
|
32
|
-
|
33
|
-
-
|
34
|
-
|
35
|
-
-
|
36
|
-
|
41
|
+
Options:
|
42
|
+
- +prefix+: a string used to match against incoming paths and route to through
|
43
|
+
the middleware. Default 'gridfs'.
|
44
|
+
- +lookup+: whether to look up a file based on <tt>:id</tt> or <tt>:path</tt>
|
45
|
+
(example below). Default is <tt>:id</tt>.
|
37
46
|
|
38
|
-
|
47
|
+
You must also specify MongoDB database details:
|
48
|
+
- +hostname+: the hostname/IP where the MongoDB server is running. Default 'localhost'.
|
49
|
+
- +port+: the port of the MongoDB server. Default 27017.
|
50
|
+
- +database+: the name of the MongoDB database to connect to.
|
51
|
+
- +username+ and +password+: if you need to authenticate to MongoDB.
|
39
52
|
|
40
|
-
|
53
|
+
Alternatively you can pass in a <tt>Mongo::DB</tt> instance instead:
|
54
|
+
- +db+: MongoMapper.database, or Mongoid.database for example.
|
55
|
+
|
56
|
+
=== Simple Sinatra Example
|
41
57
|
|
42
58
|
require 'rubygems'
|
43
59
|
require 'sinatra'
|
44
60
|
|
45
61
|
require 'rack/gridfs'
|
46
|
-
use Rack::GridFS, :
|
62
|
+
use Rack::GridFS, :database => 'test', :prefix => 'gridfs'
|
47
63
|
|
48
64
|
get /.*/ do
|
49
65
|
"The URL did not match a file in GridFS."
|
50
66
|
end
|
51
67
|
|
68
|
+
=== Usage with Rails 2
|
69
|
+
|
70
|
+
To use <tt>Rack::GridFS</tt> in a Rails application, add it as middleware in
|
71
|
+
<tt>application.rb</tt> or <tt>config/environments/*</tt>with something like this:
|
72
|
+
|
73
|
+
config.middleware.insert_after Rack::Runtime, Rack::GridFS,
|
74
|
+
:prefix => 'uploads', :database => "my_app_#{Rails.env}"
|
75
|
+
|
76
|
+
Run <tt>rake middleware</tt> to decide for yourself where to best place it in
|
77
|
+
the middleware stack for your app using {the Rails convenience methods}[http://guides.rubyonrails.org/rails_on_rack.html#configuring-middleware-stack],
|
78
|
+
taking into consideration that it can probably be near the top since it simply
|
79
|
+
returns a "static" file or a 404.
|
80
|
+
|
81
|
+
=== Usage with Rails 3
|
82
|
+
|
83
|
+
To use in Rails 3, you can insert into the middleware stack as above, or mount
|
84
|
+
the app directly in your routes (recommended). In <tt>config/routes.rb</tt>:
|
85
|
+
|
86
|
+
mount Rack::GridFS::Endpoint.new(:db => Mongoid.database), :at => "gridfs"
|
87
|
+
|
88
|
+
This allows for much more straightforward and sensible configuration, if you do
|
89
|
+
not require other middleware in front of GridFS (Rack-based authorization, for
|
90
|
+
instance).
|
91
|
+
|
92
|
+
=== Path (filename) Lookup
|
93
|
+
|
94
|
+
The <tt>:lookup => :path</tt> option causes files to be looked up from the GridFS
|
95
|
+
store based on their +filename+ field (which can be a full file path) rather than
|
96
|
+
+ObjectId+ (requests still need to match the +prefix+ you've set). This allows
|
97
|
+
you to find files based on essentially arbitrary URLs such as:
|
98
|
+
|
99
|
+
GET '/prefix/media/images/jane_avatar.jpg'
|
100
|
+
|
101
|
+
How filenames are set is specific to your application. We'll look at an example
|
102
|
+
with Carrierwave below.
|
103
|
+
|
104
|
+
*NOTE*: The Mongo Ruby driver will try to create an index on the +filename+
|
105
|
+
field for you automatically, but if you are using filename lookup you'll want to
|
106
|
+
double-check that it is created appropriately (on slaves only if you have a
|
107
|
+
master-slave architecture, etc.).
|
108
|
+
|
109
|
+
=== Carrierwave Example
|
110
|
+
|
111
|
+
Path lookup works well for usage with Carrierwave[https://github.com/jnicklas/carrierwave].
|
112
|
+
As a minimal example with Mongoid:
|
113
|
+
|
114
|
+
# config/initializers/carrierwave.rb
|
115
|
+
CarrierWave.configure do |config|
|
116
|
+
config.storage = :grid_fs
|
117
|
+
config.grid_fs_connection = Mongoid.database
|
118
|
+
config.grid_fs_access_url = "/uploads"
|
119
|
+
end
|
120
|
+
|
121
|
+
# app/uploaders/avatar_uploader.rb
|
122
|
+
class AvatarUploader < CarrierWave::Uploader::Base
|
123
|
+
# (Virtual) path where uploaded files will be stored, appended to the
|
124
|
+
# gridfs_access_url by methods used with view helpers
|
125
|
+
def store_dir
|
126
|
+
"#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# app/models/user.rb
|
131
|
+
class User
|
132
|
+
include Mongoid::Document
|
133
|
+
mount_uploader :avatar, AvatarUploader
|
134
|
+
end
|
135
|
+
|
136
|
+
# app/views/user/show.html.erb
|
137
|
+
<%= image_tag(@user.avatar.url) if @user.avatar? %>
|
138
|
+
|
139
|
+
This will result in URL paths like <tt>/uploads/user/avatar/4d250d04a8f41c0a31000006/original_filename.jpg</tt>
|
140
|
+
being generated for the view helpers, and Carrierwave will store
|
141
|
+
<tt>user/avatar/4d250d04a8f41c0a31000006/original_filename.jpg</tt> as the
|
142
|
+
+filename+ in GridFS. Thus, you can configure <tt>Rack::GridFS</tt> to serve
|
143
|
+
these files as such:
|
144
|
+
|
145
|
+
config.middleware.insert_after Rack::Runtime, Rack::GridFS,
|
146
|
+
:prefix => 'uploads', :lookup => :path, :database => "my_app_#{Rails.env}"
|
147
|
+
|
52
148
|
== Copyright
|
53
149
|
|
54
|
-
Copyright (c) 2010 Blake Carlson. See LICENSE for details.
|
150
|
+
Copyright (c) 2010-2011 Blake Carlson. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,30 +1,5 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
begin
|
5
|
-
require 'jeweler'
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = "rack-gridfs"
|
8
|
-
gem.summary = "Rack middleware for creating HTTP endpoints for files stored in MongoDB's GridFS"
|
9
|
-
gem.email = "blake@coin-operated.net"
|
10
|
-
gem.homepage = "http://github.com/skinandbones/rack-gridfs"
|
11
|
-
gem.authors = ["Blake Carlson"]
|
12
|
-
gem.rubyforge_project = "rack-gridfs"
|
13
|
-
|
14
|
-
gem.add_dependency('rack')
|
15
|
-
gem.add_dependency('mongo', '>=0.20.1')
|
16
|
-
|
17
|
-
gem.add_development_dependency('mocha', '0.9.4')
|
18
|
-
gem.add_development_dependency('rack-test')
|
19
|
-
gem.add_development_dependency('shoulda')
|
20
|
-
end
|
21
|
-
Jeweler::GemcutterTasks.new
|
22
|
-
Jeweler::RubyforgeTasks.new do |rubyforge|
|
23
|
-
rubyforge.doc_task = "rdoc"
|
24
|
-
end
|
25
|
-
rescue LoadError
|
26
|
-
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
27
|
-
end
|
1
|
+
require 'bundler/setup'
|
2
|
+
Bundler::GemHelper.install_tasks
|
28
3
|
|
29
4
|
require 'rake/testtask'
|
30
5
|
Rake::TestTask.new(:test) do |test|
|
@@ -37,29 +12,24 @@ begin
|
|
37
12
|
require 'rcov/rcovtask'
|
38
13
|
Rcov::RcovTask.new do |test|
|
39
14
|
test.libs << 'test'
|
40
|
-
test.
|
15
|
+
test.test_files = FileList['test/**/*_test.rb']
|
41
16
|
test.verbose = true
|
17
|
+
test.rcov_opts << '--exclude /gems/,/Library/,/usr/,spec,lib/tasks'
|
42
18
|
end
|
43
19
|
rescue LoadError
|
44
20
|
task :rcov do
|
45
|
-
abort "RCov is not available. In order to run rcov, you must:
|
21
|
+
abort "RCov is not available. In order to run rcov, you must: gem install rcov"
|
46
22
|
end
|
47
23
|
end
|
48
24
|
|
49
|
-
task :test => :check_dependencies
|
50
|
-
|
51
25
|
task :default => :test
|
52
26
|
|
53
27
|
require 'rake/rdoctask'
|
54
28
|
Rake::RDocTask.new do |rdoc|
|
55
|
-
|
56
|
-
version = File.read('VERSION')
|
57
|
-
else
|
58
|
-
version = ""
|
59
|
-
end
|
29
|
+
require File.expand_path("../lib/rack/gridfs/version", __FILE__)
|
60
30
|
|
61
31
|
rdoc.rdoc_dir = 'rdoc'
|
62
|
-
rdoc.title = "Rack::GridFS #{
|
63
|
-
rdoc.rdoc_files.include(
|
32
|
+
rdoc.title = "Rack::GridFS #{Rack::GridFS::VERSION}"
|
33
|
+
rdoc.rdoc_files.include(%w[ README* CHANGES* ])
|
64
34
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
65
35
|
end
|
data/lib/rack/gridfs.rb
CHANGED
@@ -1,57 +1,52 @@
|
|
1
|
-
require 'timeout'
|
2
1
|
require 'mongo'
|
2
|
+
require 'mime/types'
|
3
3
|
|
4
4
|
module Rack
|
5
|
-
|
6
5
|
class GridFSConnectionError < StandardError ; end
|
7
|
-
|
6
|
+
|
7
|
+
# Rack middleware that will serve GridFS files from a specified path prefix.
|
8
|
+
# By default the prefix is stripped from the path before file lookup in
|
9
|
+
# GridFS occurs.
|
10
|
+
#
|
11
|
+
# For example:
|
12
|
+
#
|
13
|
+
# "/gridfs/filename.png" -> "filename.png"
|
14
|
+
#
|
15
|
+
# If you are using Rails you can mount the endpoint directly.
|
16
|
+
#
|
17
|
+
# For example (in config/routes.rb):
|
18
|
+
#
|
19
|
+
# mount Rack::GridFS::Endpoint, :at => "gridfs"
|
20
|
+
|
8
21
|
class GridFS
|
22
|
+
autoload :Endpoint, "rack/gridfs/endpoint"
|
9
23
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
options = {
|
14
|
-
:hostname => 'localhost',
|
15
|
-
:prefix => 'gridfs',
|
16
|
-
:port => Mongo::Connection::DEFAULT_PORT
|
17
|
-
}.merge(options)
|
18
|
-
|
19
|
-
@app = app
|
20
|
-
@hostname = options[:hostname]
|
21
|
-
@port = options[:port]
|
22
|
-
@database = options[:database]
|
23
|
-
@prefix = options[:prefix]
|
24
|
-
@db = nil
|
25
|
-
|
26
|
-
connect!
|
24
|
+
def initialize(app, options={})
|
25
|
+
@app = app
|
26
|
+
@options = normalize_options(options)
|
27
27
|
end
|
28
28
|
|
29
29
|
def call(env)
|
30
|
-
|
31
|
-
|
32
|
-
gridfs_request($1)
|
30
|
+
if env['PATH_INFO'] =~ %r{^/#{@options[:prefix]}/*}
|
31
|
+
endpoint.call(env)
|
33
32
|
else
|
34
33
|
@app.call(env)
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
38
|
-
def gridfs_request(id)
|
39
|
-
file = Mongo::Grid.new(db).get(BSON::ObjectID.from_string(id))
|
40
|
-
[200, {'Content-Type' => file.content_type}, [file.read]]
|
41
|
-
rescue Mongo::GridError, BSON::InvalidObjectID
|
42
|
-
[404, {'Content-Type' => 'text/plain'}, ['File not found.']]
|
43
|
-
end
|
44
|
-
|
45
37
|
private
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
38
|
+
|
39
|
+
# TODO: doc explanation/example of custom mapper
|
40
|
+
def normalize_options(options)
|
41
|
+
options.tap do |opts|
|
42
|
+
opts[:prefix] ||= "gridfs"
|
43
|
+
opts[:prefix].gsub!(/^\//, '')
|
44
|
+
opts[:mapper] ||= lambda { |path| %r!^/#{options[:prefix]}/(.+)!.match(path)[1] }
|
50
45
|
end
|
51
|
-
rescue Exception => e
|
52
|
-
raise Rack::GridFSConnectionError, "Unable to connect to the MongoDB server (#{e.to_s})"
|
53
46
|
end
|
54
|
-
|
47
|
+
|
48
|
+
def endpoint
|
49
|
+
@endpoint ||= Endpoint.new(@options)
|
50
|
+
end
|
55
51
|
end
|
56
|
-
|
57
52
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Rack
|
2
|
+
class GridFS
|
3
|
+
class Endpoint
|
4
|
+
module Base
|
5
|
+
attr_reader :db
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@options = default_options.merge(options)
|
9
|
+
|
10
|
+
@db = @options[:db]
|
11
|
+
@lookup = @options[:lookup]
|
12
|
+
@mapper = @options[:mapper]
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
with_rescues do
|
17
|
+
request = Rack::Request.new(env)
|
18
|
+
key = key_for_path(request.path_info)
|
19
|
+
file = find_file(key)
|
20
|
+
|
21
|
+
response_for(file, request)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def key_for_path(path)
|
26
|
+
@mapper.respond_to?(:call) ? @mapper.call(path) : path
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def default_options
|
32
|
+
{
|
33
|
+
:lookup => :id,
|
34
|
+
:mapper => lambda { |path| %r!/(.+)!.match(path)[1] }
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def with_rescues
|
39
|
+
rescue_connection_failure { yield }
|
40
|
+
rescue Mongo::GridFileNotFound, BSON::InvalidObjectId => e
|
41
|
+
[ 404, {'Content-Type' => 'text/plain'}, ["File not found. #{e}"] ]
|
42
|
+
rescue Mongo::GridError => e
|
43
|
+
[ 500, {'Content-Type' => 'text/plain'}, ["An error occured. #{e}"] ]
|
44
|
+
end
|
45
|
+
|
46
|
+
def rescue_connection_failure(max_retries=60)
|
47
|
+
retries = 0
|
48
|
+
begin
|
49
|
+
yield
|
50
|
+
rescue Mongo::ConnectionFailure => e
|
51
|
+
retries += 1
|
52
|
+
raise e if retries > max_retries
|
53
|
+
sleep(0.5)
|
54
|
+
retry
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def response_for(file, request)
|
59
|
+
[ 200, headers(file), file ]
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_file(id_or_path)
|
63
|
+
case @lookup.to_sym
|
64
|
+
when :id then Mongo::Grid.new(db).get(BSON::ObjectId.from_string(id_or_path))
|
65
|
+
when :path then Mongo::GridFileSystem.new(db).open(id_or_path, "r")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def headers(file)
|
70
|
+
{ 'Content-Type' => file.content_type }
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Rack
|
2
|
+
class GridFS
|
3
|
+
class Endpoint
|
4
|
+
|
5
|
+
module Caching
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
|
+
@options[:expires] ||= false
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def headers(file)
|
14
|
+
super.merge(
|
15
|
+
'Last-Modified' => file.upload_date.httpdate,
|
16
|
+
'Etag' => file.files_id.to_s
|
17
|
+
).merge(cache_control_header)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def cache_control_header
|
23
|
+
if @options[:expires]
|
24
|
+
{ "Cache-Control" => "max-age=#{@options[:expires]}, public" }
|
25
|
+
else
|
26
|
+
{}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class GridFS
|
5
|
+
class Endpoint
|
6
|
+
|
7
|
+
module Connection
|
8
|
+
def initialize(*)
|
9
|
+
super
|
10
|
+
|
11
|
+
@hostname, @port, @database, @username, @password =
|
12
|
+
@options.values_at(:hostname, :port, :database, :username, :password)
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_options
|
16
|
+
super.merge({
|
17
|
+
:hostname => 'localhost',
|
18
|
+
:port => Mongo::Connection::DEFAULT_PORT
|
19
|
+
})
|
20
|
+
end
|
21
|
+
|
22
|
+
def db
|
23
|
+
@db ||= (super || connect!)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def connect!
|
29
|
+
database = nil
|
30
|
+
|
31
|
+
Timeout::timeout(5) do
|
32
|
+
database = Mongo::Connection.new(@hostname, @port).db(@database)
|
33
|
+
database.authenticate(@username, @password) if @username
|
34
|
+
end
|
35
|
+
|
36
|
+
return database
|
37
|
+
rescue Exception => e
|
38
|
+
raise Rack::GridFSConnectionError, "Unable to connect to the MongoDB server (#{e.to_s})"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-gridfs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 15
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Blake Carlson
|
@@ -9,110 +15,183 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date:
|
18
|
+
date: 2011-05-13 00:00:00 +07:00
|
13
19
|
default_executable:
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
|
-
|
22
|
+
prerelease: false
|
17
23
|
type: :runtime
|
18
|
-
|
19
|
-
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
20
26
|
requirements:
|
21
27
|
- - ">="
|
22
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
23
32
|
version: "0"
|
24
|
-
|
33
|
+
version_requirements: *id001
|
34
|
+
name: rack
|
25
35
|
- !ruby/object:Gem::Dependency
|
36
|
+
prerelease: false
|
37
|
+
type: :runtime
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 11
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 2
|
47
|
+
version: "1.2"
|
48
|
+
version_requirements: *id002
|
26
49
|
name: mongo
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
prerelease: false
|
27
52
|
type: :runtime
|
28
|
-
|
29
|
-
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
version_requirements: *id003
|
63
|
+
name: mime-types
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
prerelease: false
|
66
|
+
type: :development
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
30
69
|
requirements:
|
31
70
|
- - ">="
|
32
71
|
- !ruby/object:Gem::Version
|
33
|
-
|
34
|
-
|
72
|
+
hash: 23
|
73
|
+
segments:
|
74
|
+
- 1
|
75
|
+
- 0
|
76
|
+
- 0
|
77
|
+
version: 1.0.0
|
78
|
+
version_requirements: *id004
|
79
|
+
name: bundler
|
35
80
|
- !ruby/object:Gem::Dependency
|
36
|
-
|
81
|
+
prerelease: false
|
37
82
|
type: :development
|
38
|
-
|
39
|
-
|
83
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
40
85
|
requirements:
|
41
86
|
- - "="
|
42
87
|
- !ruby/object:Gem::Version
|
43
|
-
|
44
|
-
|
88
|
+
hash: 35
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
- 9
|
92
|
+
- 12
|
93
|
+
version: 0.9.12
|
94
|
+
version_requirements: *id005
|
95
|
+
name: mocha
|
45
96
|
- !ruby/object:Gem::Dependency
|
97
|
+
prerelease: false
|
98
|
+
type: :development
|
99
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
version_requirements: *id006
|
46
109
|
name: rack-test
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
prerelease: false
|
47
112
|
type: :development
|
48
|
-
|
49
|
-
|
113
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
50
115
|
requirements:
|
51
116
|
- - ">="
|
52
117
|
- !ruby/object:Gem::Version
|
118
|
+
hash: 3
|
119
|
+
segments:
|
120
|
+
- 0
|
53
121
|
version: "0"
|
54
|
-
|
122
|
+
version_requirements: *id007
|
123
|
+
name: rake
|
55
124
|
- !ruby/object:Gem::Dependency
|
56
|
-
|
125
|
+
prerelease: false
|
57
126
|
type: :development
|
58
|
-
|
59
|
-
|
127
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
60
129
|
requirements:
|
61
130
|
- - ">="
|
62
131
|
- !ruby/object:Gem::Version
|
132
|
+
hash: 3
|
133
|
+
segments:
|
134
|
+
- 0
|
63
135
|
version: "0"
|
64
|
-
|
65
|
-
|
66
|
-
|
136
|
+
version_requirements: *id008
|
137
|
+
name: shoulda
|
138
|
+
description: Rack middleware for creating HTTP endpoints for files stored in MongoDB's GridFS
|
139
|
+
email:
|
140
|
+
- blake@coin-operated.net
|
67
141
|
executables: []
|
68
142
|
|
69
143
|
extensions: []
|
70
144
|
|
71
145
|
extra_rdoc_files:
|
146
|
+
- CHANGES.rdoc
|
72
147
|
- LICENSE
|
73
148
|
- README.rdoc
|
74
149
|
files:
|
75
|
-
- .
|
150
|
+
- lib/rack/gridfs/endpoint/base.rb
|
151
|
+
- lib/rack/gridfs/endpoint/caching.rb
|
152
|
+
- lib/rack/gridfs/endpoint/connection.rb
|
153
|
+
- lib/rack/gridfs/endpoint.rb
|
154
|
+
- lib/rack/gridfs/version.rb
|
155
|
+
- lib/rack/gridfs.rb
|
76
156
|
- LICENSE
|
77
157
|
- README.rdoc
|
78
158
|
- Rakefile
|
79
|
-
-
|
80
|
-
- example/gridfs_server.rb
|
81
|
-
- lib/rack/gridfs.rb
|
82
|
-
- rack-gridfs.gemspec
|
83
|
-
- test/artifacts/3wolfmoon.jpg
|
84
|
-
- test/artifacts/test.html
|
85
|
-
- test/artifacts/test.txt
|
86
|
-
- test/gridfs_test.rb
|
87
|
-
- test/test_helper.rb
|
159
|
+
- CHANGES.rdoc
|
88
160
|
has_rdoc: true
|
89
161
|
homepage: http://github.com/skinandbones/rack-gridfs
|
90
162
|
licenses: []
|
91
163
|
|
92
164
|
post_install_message:
|
93
|
-
rdoc_options:
|
94
|
-
|
165
|
+
rdoc_options: []
|
166
|
+
|
95
167
|
require_paths:
|
96
168
|
- lib
|
97
169
|
required_ruby_version: !ruby/object:Gem::Requirement
|
170
|
+
none: false
|
98
171
|
requirements:
|
99
172
|
- - ">="
|
100
173
|
- !ruby/object:Gem::Version
|
174
|
+
hash: 3
|
175
|
+
segments:
|
176
|
+
- 0
|
101
177
|
version: "0"
|
102
|
-
version:
|
103
178
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
179
|
+
none: false
|
104
180
|
requirements:
|
105
181
|
- - ">="
|
106
182
|
- !ruby/object:Gem::Version
|
107
|
-
|
108
|
-
|
183
|
+
hash: 23
|
184
|
+
segments:
|
185
|
+
- 1
|
186
|
+
- 3
|
187
|
+
- 6
|
188
|
+
version: 1.3.6
|
109
189
|
requirements: []
|
110
190
|
|
111
191
|
rubyforge_project: rack-gridfs
|
112
|
-
rubygems_version: 1.
|
192
|
+
rubygems_version: 1.5.2
|
113
193
|
signing_key:
|
114
194
|
specification_version: 3
|
115
|
-
summary:
|
116
|
-
test_files:
|
117
|
-
|
118
|
-
- test/test_helper.rb
|
195
|
+
summary: Serve MongoDB GridFS files from Rack
|
196
|
+
test_files: []
|
197
|
+
|
data/.gitignore
DELETED
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.3.0
|
data/example/gridfs_server.rb
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'sinatra'
|
3
|
-
|
4
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'rack', 'gridfs')
|
5
|
-
use Rack::GridFS, :hostname => 'localhost', :port => 27017, :database => 'test', :prefix => 'gridfs'
|
6
|
-
|
7
|
-
get /.*/ do
|
8
|
-
"Whatchya talking about?"
|
9
|
-
end
|
data/rack-gridfs.gemspec
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
-
# -*- encoding: utf-8 -*-
|
5
|
-
|
6
|
-
Gem::Specification.new do |s|
|
7
|
-
s.name = %q{rack-gridfs}
|
8
|
-
s.version = "0.3.0"
|
9
|
-
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["Blake Carlson"]
|
12
|
-
s.date = %q{2010-05-11}
|
13
|
-
s.email = %q{blake@coin-operated.net}
|
14
|
-
s.extra_rdoc_files = [
|
15
|
-
"LICENSE",
|
16
|
-
"README.rdoc"
|
17
|
-
]
|
18
|
-
s.files = [
|
19
|
-
".gitignore",
|
20
|
-
"LICENSE",
|
21
|
-
"README.rdoc",
|
22
|
-
"Rakefile",
|
23
|
-
"VERSION",
|
24
|
-
"example/gridfs_server.rb",
|
25
|
-
"lib/rack/gridfs.rb",
|
26
|
-
"rack-gridfs.gemspec",
|
27
|
-
"test/artifacts/3wolfmoon.jpg",
|
28
|
-
"test/artifacts/test.html",
|
29
|
-
"test/artifacts/test.txt",
|
30
|
-
"test/gridfs_test.rb",
|
31
|
-
"test/test_helper.rb"
|
32
|
-
]
|
33
|
-
s.homepage = %q{http://github.com/skinandbones/rack-gridfs}
|
34
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
35
|
-
s.require_paths = ["lib"]
|
36
|
-
s.rubyforge_project = %q{rack-gridfs}
|
37
|
-
s.rubygems_version = %q{1.3.5}
|
38
|
-
s.summary = %q{Rack middleware for creating HTTP endpoints for files stored in MongoDB's GridFS}
|
39
|
-
s.test_files = [
|
40
|
-
"test/gridfs_test.rb",
|
41
|
-
"test/test_helper.rb"
|
42
|
-
]
|
43
|
-
|
44
|
-
if s.respond_to? :specification_version then
|
45
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
46
|
-
s.specification_version = 3
|
47
|
-
|
48
|
-
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
49
|
-
s.add_runtime_dependency(%q<rack>, [">= 0"])
|
50
|
-
s.add_runtime_dependency(%q<mongo>, [">= 0.20.1"])
|
51
|
-
s.add_development_dependency(%q<mocha>, ["= 0.9.4"])
|
52
|
-
s.add_development_dependency(%q<rack-test>, [">= 0"])
|
53
|
-
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
54
|
-
else
|
55
|
-
s.add_dependency(%q<rack>, [">= 0"])
|
56
|
-
s.add_dependency(%q<mongo>, [">= 0.20.1"])
|
57
|
-
s.add_dependency(%q<mocha>, ["= 0.9.4"])
|
58
|
-
s.add_dependency(%q<rack-test>, [">= 0"])
|
59
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
60
|
-
end
|
61
|
-
else
|
62
|
-
s.add_dependency(%q<rack>, [">= 0"])
|
63
|
-
s.add_dependency(%q<mongo>, [">= 0.20.1"])
|
64
|
-
s.add_dependency(%q<mocha>, ["= 0.9.4"])
|
65
|
-
s.add_dependency(%q<rack-test>, [">= 0"])
|
66
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
Binary file
|
data/test/artifacts/test.html
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
2
|
-
"http://www.w3.org/TR/html4/strict.dtd">
|
3
|
-
|
4
|
-
<html lang="en">
|
5
|
-
<head>
|
6
|
-
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
7
|
-
<title>test</title>
|
8
|
-
<meta name="generator" content="TextMate http://macromates.com/">
|
9
|
-
<meta name="author" content="Blake Carlson">
|
10
|
-
<!-- Date: 2009-09-20 -->
|
11
|
-
</head>
|
12
|
-
<body>
|
13
|
-
Test
|
14
|
-
</body>
|
15
|
-
</html>
|
data/test/artifacts/test.txt
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
Lorem ipsum dolor sit amet.
|
data/test/gridfs_test.rb
DELETED
@@ -1,141 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class Rack::GridFSTest < Test::Unit::TestCase
|
4
|
-
include Rack::Test::Methods
|
5
|
-
|
6
|
-
def stub_mongodb_connection
|
7
|
-
Rack::GridFS.any_instance.stubs(:connect!).returns(true)
|
8
|
-
end
|
9
|
-
|
10
|
-
def test_database_options
|
11
|
-
{ :hostname => 'localhost', :port => 27017, :database => 'test', :prefix => 'gridfs' }
|
12
|
-
end
|
13
|
-
|
14
|
-
def db
|
15
|
-
@db ||= Mongo::Connection.new(test_database_options[:hostname], test_database_options[:port]).db(test_database_options[:database])
|
16
|
-
end
|
17
|
-
|
18
|
-
def app
|
19
|
-
gridfs_opts = test_database_options
|
20
|
-
Rack::Builder.new do
|
21
|
-
use Rack::GridFS, gridfs_opts
|
22
|
-
run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def load_artifact(filename, content_type)
|
27
|
-
contents = File.read(File.join(File.dirname(__FILE__), 'artifacts', filename))
|
28
|
-
Mongo::Grid.new(db).put(contents, :filename => filename, :content_type => content_type)
|
29
|
-
end
|
30
|
-
|
31
|
-
context "Rack::GridFS" do
|
32
|
-
|
33
|
-
context "on initialization" do
|
34
|
-
|
35
|
-
setup do
|
36
|
-
stub_mongodb_connection
|
37
|
-
@options = { :hostname => 'myhostname.mydomain', :port => 8765, :database => 'mydatabase', :prefix => 'myprefix' }
|
38
|
-
end
|
39
|
-
|
40
|
-
should "have a hostname option" do
|
41
|
-
mware = Rack::GridFS.new(nil, @options)
|
42
|
-
assert_equal @options[:hostname], mware.hostname
|
43
|
-
end
|
44
|
-
|
45
|
-
should "have a default hostname" do
|
46
|
-
mware = Rack::GridFS.new(nil, @options.except(:hostname))
|
47
|
-
assert_equal 'localhost', mware.hostname
|
48
|
-
end
|
49
|
-
|
50
|
-
should "have a port option" do
|
51
|
-
mware = Rack::GridFS.new(nil, @options)
|
52
|
-
assert_equal @options[:port], mware.port
|
53
|
-
end
|
54
|
-
|
55
|
-
should "have a default port" do
|
56
|
-
mware = Rack::GridFS.new(nil, @options.except(:port))
|
57
|
-
assert_equal Mongo::Connection::DEFAULT_PORT, mware.port
|
58
|
-
end
|
59
|
-
|
60
|
-
should "have a database option" do
|
61
|
-
mware = Rack::GridFS.new(nil, @options)
|
62
|
-
assert_equal @options[:database], mware.database
|
63
|
-
end
|
64
|
-
|
65
|
-
should "not have a default database" do
|
66
|
-
mware = Rack::GridFS.new(nil, @options.except(:database))
|
67
|
-
assert_nil mware.database
|
68
|
-
end
|
69
|
-
|
70
|
-
should "have a prefix option" do
|
71
|
-
mware = Rack::GridFS.new(nil, @options)
|
72
|
-
assert_equal mware.prefix, @options[:prefix]
|
73
|
-
end
|
74
|
-
|
75
|
-
should "have a default prefix" do
|
76
|
-
mware = Rack::GridFS.new(nil, @options.except(:prefix))
|
77
|
-
assert_equal mware.prefix, 'gridfs'
|
78
|
-
end
|
79
|
-
|
80
|
-
should "connect to the MongoDB server" do
|
81
|
-
Rack::GridFS.any_instance.expects(:connect!).returns(true).once
|
82
|
-
Rack::GridFS.new(nil, @options)
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
should "delegate requests with a non-matching prefix" do
|
88
|
-
%w( / /posts /posts/1 /posts/1/comments ).each do |path|
|
89
|
-
get path
|
90
|
-
assert last_response.ok?
|
91
|
-
assert 'Hello, World!', last_response.body
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
context "with files in GridFS" do
|
96
|
-
setup do
|
97
|
-
@text_id = load_artifact('test.txt', 'text/plain')
|
98
|
-
@html_id = load_artifact('test.html', 'text/html')
|
99
|
-
end
|
100
|
-
|
101
|
-
teardown do
|
102
|
-
db.collection('fs.files').remove
|
103
|
-
end
|
104
|
-
|
105
|
-
should "return TXT files stored in GridFS" do
|
106
|
-
get "/gridfs/#{@text_id}"
|
107
|
-
assert_equal "Lorem ipsum dolor sit amet.", last_response.body
|
108
|
-
end
|
109
|
-
|
110
|
-
should "return the proper content type for TXT files" do
|
111
|
-
get "/gridfs/#{@text_id}"
|
112
|
-
assert_equal 'text/plain', last_response.content_type
|
113
|
-
end
|
114
|
-
|
115
|
-
should "return HTML files stored in GridFS" do
|
116
|
-
get "/gridfs/#{@html_id}"
|
117
|
-
assert_match /html.*?body.*Test/m, last_response.body
|
118
|
-
end
|
119
|
-
|
120
|
-
should "return the proper content type for HTML files" do
|
121
|
-
get "/gridfs/#{@html_id}"
|
122
|
-
assert_equal 'text/html', last_response.content_type
|
123
|
-
end
|
124
|
-
|
125
|
-
should "return a not found for a unknown path" do
|
126
|
-
get '/gridfs/unknown'
|
127
|
-
assert last_response.not_found?
|
128
|
-
end
|
129
|
-
|
130
|
-
should "work for small images" do
|
131
|
-
image_id = load_artifact('3wolfmoon.jpg', 'image/jpeg')
|
132
|
-
get "/gridfs/#{image_id}"
|
133
|
-
assert last_response.ok?
|
134
|
-
assert_equal 'image/jpeg', last_response.content_type
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
end
|
139
|
-
|
140
|
-
end
|
141
|
-
|
data/test/test_helper.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'test/unit'
|
3
|
-
require 'shoulda'
|
4
|
-
require 'mocha'
|
5
|
-
|
6
|
-
require 'rack/builder'
|
7
|
-
require 'rack/mock'
|
8
|
-
require 'rack/test'
|
9
|
-
|
10
|
-
require 'mongo'
|
11
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'rack', 'gridfs')
|
12
|
-
|
13
|
-
|
14
|
-
class Hash
|
15
|
-
def except(*keys)
|
16
|
-
rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
|
17
|
-
reject { |key,| rejected.include?(key) }
|
18
|
-
end
|
19
|
-
end
|