link-middleware 0.0.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/LICENSE.md +24 -0
- data/README.md +76 -0
- data/lib/link-middleware.rb +18 -0
- data/lib/link-middleware/annotator.rb +33 -0
- data/lib/link-middleware/filter.rb +53 -0
- data/lib/link-middleware/redis_store.rb +40 -0
- data/lib/link-middleware/store.rb +40 -0
- data/lib/link-middleware/version.rb +3 -0
- metadata +150 -0
data/LICENSE.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
4
|
+
distribute this software, either in source code form or as a compiled
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
6
|
+
means.
|
7
|
+
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
9
|
+
of this software dedicate any and all copyright interest in the
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
11
|
+
of the public at large and to the detriment of our heirs and
|
12
|
+
successors. We intend this dedication to be an overt act of
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
14
|
+
software under copyright law.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
For more information, please refer to <http://unlicense.org/>
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Rack Middleware for LINK/UNLINK
|
2
|
+
|
3
|
+
Provides a simple implementation of [HTTP Link and Unlink Methods draft-snell-link-method-08](http://tools.ietf.org/html/draft-snell-link-method-08)
|
4
|
+
as configurable [Rack](http://rack.github.io/) middle-ware.
|
5
|
+
|
6
|
+
The code provides an easy way to instrument any existing Rack based application with support for the
|
7
|
+
`LINK` and `UNLINK` HTTP Methods.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add the gem into your Gemfile:
|
12
|
+
|
13
|
+
gem 'link-middleware', :git => "git://github.com/ldodds/link-middleware.git"
|
14
|
+
|
15
|
+
Or:
|
16
|
+
|
17
|
+
sudo gem install link-middleware
|
18
|
+
|
19
|
+
## Basic Usage
|
20
|
+
|
21
|
+
There are two separate middleware components:
|
22
|
+
|
23
|
+
* `LinkMiddleware::Filter` -- provides the basic LINK/UNLINK support
|
24
|
+
* `LinkMiddleware::Annotator` --- provides support for annotating requests with stored links
|
25
|
+
|
26
|
+
Here is a simple example see: `examples/simple.rb`
|
27
|
+
|
28
|
+
require 'sinatra'
|
29
|
+
require 'link-middleware'
|
30
|
+
|
31
|
+
set :store, LinkMiddleware::MemoryStore.new
|
32
|
+
|
33
|
+
use LinkMiddleware::Filter, :store => settings.store
|
34
|
+
use LinkMiddleware::Annotator, :store => settings.store
|
35
|
+
|
36
|
+
get '/' do
|
37
|
+
'Hello World'
|
38
|
+
end
|
39
|
+
|
40
|
+
The `Filter` middleware will intercept and process `LINK` and `UNLINK` requests.
|
41
|
+
|
42
|
+
The `Annotator` middleware will automatically annotate responses to `/` with any stored links.
|
43
|
+
|
44
|
+
The middleware is configured to use a shared in-memory store. To make this persistent across
|
45
|
+
HTTP connections the store instance is cached as a Sinatra setting
|
46
|
+
|
47
|
+
## Link Store Implementations
|
48
|
+
|
49
|
+
* In-Memory
|
50
|
+
* Redis (TODO)
|
51
|
+
* RDF Triplestore (TODO)
|
52
|
+
|
53
|
+
To configure a Link Store. The following will instantiate a new instance, passing any
|
54
|
+
additional options to the constructor of the store class:
|
55
|
+
|
56
|
+
use LinkMiddleware::Filter, :store_impl => LinkMiddleware::MemoryStore
|
57
|
+
|
58
|
+
Or, initialize the middleware with an existing object:
|
59
|
+
|
60
|
+
use LinkMiddleware::Filter, :store => LinkMiddleware::MemoryStore.new
|
61
|
+
|
62
|
+
Link Store implementations should have the following methods:
|
63
|
+
|
64
|
+
add(context_uri, header)
|
65
|
+
remove(context_uri, header)
|
66
|
+
read(context_uri)
|
67
|
+
|
68
|
+
The header parameter passed to `add` and `remove` will be an instance of `LinkHeader` from the
|
69
|
+
[link-header gem](https://github.com/asplake/link_header). This will have an array of links. Similarly
|
70
|
+
the response from `read` should be a `LinkHeader` instance.
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
## Licence
|
75
|
+
|
76
|
+
Placed into the public domain under the unlicence. See `LICENCE.md`
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'link_header'
|
3
|
+
|
4
|
+
require 'link-middleware/annotator'
|
5
|
+
require 'link-middleware/filter'
|
6
|
+
require 'link-middleware/store'
|
7
|
+
require 'link-middleware/redis_store'
|
8
|
+
|
9
|
+
class LinkHeader::Link
|
10
|
+
|
11
|
+
def eql?(other)
|
12
|
+
return href == other.href && attr_pairs == other.attr_pairs
|
13
|
+
end
|
14
|
+
|
15
|
+
def hash
|
16
|
+
return href.hash + attr_pairs.hash
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module LinkMiddleware
|
2
|
+
|
3
|
+
class Annotator
|
4
|
+
|
5
|
+
def initialize(app, options={})
|
6
|
+
@app = app
|
7
|
+
@options = options
|
8
|
+
if options[:store]
|
9
|
+
@store = options[:store]
|
10
|
+
elsif options[:store_impl]
|
11
|
+
@store = options[:store].new(options)
|
12
|
+
else
|
13
|
+
@store = LinkMiddleware::MemoryStore.new(options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
status, headers, response = @app.call(env)
|
19
|
+
request = Rack::Request.new( env )
|
20
|
+
stored_links = @store.read( request.url )
|
21
|
+
if !stored_links.links.empty?
|
22
|
+
link_header = stored_links.to_s
|
23
|
+
if headers["Link"]
|
24
|
+
link_header = env["Link"] + ";" + link_header
|
25
|
+
end
|
26
|
+
headers["Link"] = link_header
|
27
|
+
end
|
28
|
+
[status, headers, response]
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module LinkMiddleware
|
2
|
+
|
3
|
+
class Filter
|
4
|
+
|
5
|
+
def initialize(app, options={})
|
6
|
+
@app = app
|
7
|
+
@options = options
|
8
|
+
if options[:store]
|
9
|
+
@store = options[:store]
|
10
|
+
elsif options[:store_impl]
|
11
|
+
@store = options[:store].new(options)
|
12
|
+
else
|
13
|
+
@store = LinkMiddleware::MemoryStore.new(options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
request = Rack::Request.new( env )
|
19
|
+
if request.request_method == "LINK"
|
20
|
+
return link( request.url, env )
|
21
|
+
elsif request.request_method == "UNLINK"
|
22
|
+
return unlink( request.url, env )
|
23
|
+
end
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
|
27
|
+
def link(url, env)
|
28
|
+
links = LinkHeader.parse( env["HTTP_LINK"] )
|
29
|
+
success = @store.add(url,links)
|
30
|
+
headers = {}
|
31
|
+
if success
|
32
|
+
headers["Link"] = links.to_s unless links.links.empty?
|
33
|
+
return [200, headers, ["OK"]]
|
34
|
+
else
|
35
|
+
return [500, headers, ["Unable to add links"]]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def unlink(url, env)
|
40
|
+
links = LinkHeader.parse( env["HTTP_LINK"] )
|
41
|
+
success = @store.remove(url,links)
|
42
|
+
headers = {}
|
43
|
+
if success
|
44
|
+
headers["Link"] = links.to_s unless links.links.empty?
|
45
|
+
return [200, headers, ["OK"]]
|
46
|
+
else
|
47
|
+
return [500, headers, ["Unable to add links"]]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module LinkMiddleware
|
2
|
+
class RedisStore
|
3
|
+
def initialize(options={})
|
4
|
+
if options[:store]
|
5
|
+
@redis = options[:store]
|
6
|
+
else
|
7
|
+
@redis = Redis.new(options)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(context_uri, header)
|
12
|
+
header.links.each do |link|
|
13
|
+
@redis.sadd context_uri, link.to_s
|
14
|
+
end
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def remove(context_uri, header)
|
19
|
+
process = true
|
20
|
+
header.links.each do |link|
|
21
|
+
process = process && @redis.sismember(context_uri, link.to_s)
|
22
|
+
end
|
23
|
+
header.links.each do |link|
|
24
|
+
@redis.srem context_uri, link.to_s
|
25
|
+
end if process
|
26
|
+
process
|
27
|
+
end
|
28
|
+
|
29
|
+
def read(context_uri)
|
30
|
+
header = LinkHeader.new
|
31
|
+
links = @redis.smembers context_uri
|
32
|
+
links.each do |link|
|
33
|
+
header << LinkHeader.parse(link).links.first
|
34
|
+
end
|
35
|
+
header
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module LinkMiddleware
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
#Use for testing only!
|
6
|
+
class MemoryStore
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
@store = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(context_uri, header)
|
13
|
+
stored_header = @store[context_uri] || LinkHeader.new
|
14
|
+
header.links.each do |link|
|
15
|
+
stored_header << link
|
16
|
+
end
|
17
|
+
@store[context_uri] = stored_header
|
18
|
+
return true
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove(context_uri, header)
|
22
|
+
stored_header = @store[context_uri] || LinkHeader.new
|
23
|
+
return true if stored_header.links.empty?
|
24
|
+
|
25
|
+
#if there are links to remove, which aren't in the stored set, then return false
|
26
|
+
return false if !(header.links - stored_header.links).empty?
|
27
|
+
|
28
|
+
#otherwise remove them
|
29
|
+
final = stored_header.links - header.links
|
30
|
+
@store[context_uri] = LinkHeader.new( final )
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
|
34
|
+
def read(context_uri)
|
35
|
+
@store[context_uri] || LinkHeader.new
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: link-middleware
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Leigh Dodds
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-12-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: link_header
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: redis
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: simplecov-rcov
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: mock_redis
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description:
|
111
|
+
email:
|
112
|
+
- leigh@ldodds.com
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- lib/link-middleware.rb
|
118
|
+
- lib/link-middleware/annotator.rb
|
119
|
+
- lib/link-middleware/redis_store.rb
|
120
|
+
- lib/link-middleware/filter.rb
|
121
|
+
- lib/link-middleware/store.rb
|
122
|
+
- lib/link-middleware/version.rb
|
123
|
+
- LICENSE.md
|
124
|
+
- README.md
|
125
|
+
homepage: http://github.com/ldidds/link-middleware
|
126
|
+
licenses: []
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
133
|
+
requirements:
|
134
|
+
- - ! '>='
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
requirements: []
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 1.8.23
|
146
|
+
signing_key:
|
147
|
+
specification_version: 3
|
148
|
+
summary: Implementation of LINK/UNLINK using Rack middleware
|
149
|
+
test_files: []
|
150
|
+
has_rdoc:
|