rack-linkeddata 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ * Arto Bendiken <arto.bendiken@gmail.com>
data/README ADDED
@@ -0,0 +1,121 @@
1
+ Linked Data Content Negotiation for Rack Applications
2
+ =====================================================
3
+
4
+ This is [Rack][] middleware that provides [Linked Data][] content
5
+ negotiation for Rack applications. You can use `Rack::LinkedData` with any
6
+ Ruby web framework based on Rack, including with Ruby on Rails 3.0 and with
7
+ Sinatra.
8
+
9
+ * <http://github.com/datagraph/rack-linkeddata>
10
+
11
+ Features
12
+ --------
13
+
14
+ * Implements [HTTP content negotiation][conneg] for RDF content types.
15
+ * Supports all [RDF.rb][]-compatible serialization formats.
16
+ * Compatible with any Rack application and any Rack-based framework.
17
+
18
+ Examples
19
+ --------
20
+
21
+ ### Adding Linked Data content negotiation to a Rack application
22
+
23
+ #!/usr/bin/env rackup
24
+ require 'rack/linkeddata'
25
+
26
+ rdf = RDF::Graph.new do
27
+ self << [RDF::Node.new, RDF::DC.title, "Hello, world!"]
28
+ end
29
+
30
+ use Rack::LinkedData::ContentNegotiation
31
+ run lambda { |env| [200, {}, rdf] }
32
+
33
+ ### Defining a default Linked Data content type
34
+
35
+ use Rack::LinkedData::ContentNegotiation, :default => "text/turtle"
36
+
37
+ ### Testing Linked Data content negotiation using `rackup` and `curl`
38
+
39
+ $ rackup doc/examples/hello.ru
40
+
41
+ $ curl -iH "Accept: text/plain" http://localhost:9292/hello
42
+ $ curl -iH "Accept: text/turtle" http://localhost:9292/hello
43
+ $ curl -iH "Accept: application/rdf+xml" http://localhost:9292/hello
44
+ $ curl -iH "Accept: application/json" http://localhost:9292/hello
45
+ $ curl -iH "Accept: application/trix" http://localhost:9292/hello
46
+ $ curl -iH "Accept: */*" http://localhost:9292/hello
47
+
48
+ Description
49
+ -----------
50
+
51
+ `Rack::LinkedData` implements content negotiation for any [Rack][] response
52
+ object that implements the `RDF::Enumerable` mixin. You would typically
53
+ return an instance of `RDF::Graph` or `RDF::Repository` from your Rack
54
+ application, and let the `Rack::LinkedData::ContentNegotiation` middleware
55
+ take care serializing your response into whatever RDF format the HTTP client
56
+ requested and understands.
57
+
58
+ The middleware works by querying [RDF.rb][] for the MIME content types of
59
+ known RDF serialization formats, so it will work with whatever serialization
60
+ plugins that are currently available for RDF.rb. (At present, this includes
61
+ support for N-Triples, Turtle, RDF/XML, RDF/JSON and TriX.)
62
+
63
+ Documentation
64
+ -------------
65
+
66
+ <http://datagraph.rubyforge.org/rack-linkeddata/>
67
+
68
+ * {Rack::LinkedData}
69
+ * {Rack::LinkedData::ContentNegotiation}
70
+
71
+ Dependencies
72
+ ------------
73
+
74
+ * [Rack](http://rubygems.org/gems/rack) (>= 1.0.0)
75
+ * [Linked Data](http://rubygems.org/gems/linkeddata) (>= 0.1.10)
76
+
77
+ Installation
78
+ ------------
79
+
80
+ The recommended installation method is via [RubyGems](http://rubygems.org/).
81
+ To install the latest official release of the gem, do:
82
+
83
+ % [sudo] gem install rack-linkeddata
84
+
85
+ Download
86
+ --------
87
+
88
+ To get a local working copy of the development repository, do:
89
+
90
+ % git clone git://github.com/datagraph/rack-linkeddata.git
91
+
92
+ Alternatively, you can download the latest development version as a tarball
93
+ as follows:
94
+
95
+ % wget http://github.com/datagraph/rack-linkeddata/tarball/master
96
+
97
+ References
98
+ ----------
99
+
100
+ * <http://www.w3.org/DesignIssues/LinkedData.html>
101
+ * <http://linkeddata.org/docs/how-to-publish>
102
+ * <http://linkeddata.org/conneg-303-redirect-code-samples>
103
+ * <http://www.w3.org/TR/cooluris/>
104
+ * <http://www.w3.org/TR/swbp-vocab-pub/>
105
+ * <http://patterns.dataincubator.org/book/publishing-patterns.html>
106
+
107
+ Authors
108
+ -------
109
+
110
+ * [Arto Bendiken](mailto:arto.bendiken@gmail.com) - <http://ar.to/>
111
+
112
+ License
113
+ -------
114
+
115
+ `Rack::LinkedData` is free and unencumbered public domain software. For more
116
+ information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
117
+
118
+ [Rack]: http://rack.rubyforge.org/
119
+ [RDF.rb]: http://rdf.rubyforge.org/
120
+ [Linked Data]: http://linkeddata.org/
121
+ [conneg]: http://en.wikipedia.org/wiki/Content_negotiation
data/UNLICENSE 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/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,150 @@
1
+ module Rack; module LinkedData
2
+ ##
3
+ # Rack middleware for Linked Data content negotiation.
4
+ #
5
+ # @see http://www4.wiwiss.fu-berlin.de/bizer/pub/LinkedDataTutorial/
6
+ class ContentNegotiation
7
+ DEFAULT_CONTENT_TYPE = "text/plain" # N-Triples
8
+
9
+ # @return [#call]
10
+ attr_reader :app
11
+
12
+ # @return [Hash{Symbol => Object}]
13
+ attr_reader :options
14
+
15
+ ##
16
+ # @param [#call] app
17
+ # @param [Hash{Symbol => Object}] options
18
+ # @option options [String] :default (DEFAULT_CONTENT_TYPE)
19
+ def initialize(app, options = {})
20
+ @app, @options = app, options.to_hash.dup
21
+ @options[:default] = (@options[:default] || DEFAULT_CONTENT_TYPE).to_s
22
+ end
23
+
24
+ ##
25
+ # Handles a Rack protocol request.
26
+ #
27
+ # @param [Hash{String => String}] env
28
+ # @return [Array(Integer, Hash, #each)]
29
+ # @see http://rack.rubyforge.org/doc/SPEC.html
30
+ def call(env)
31
+ response = app.call(env)
32
+ case env['REQUEST_METHOD'].to_sym
33
+ when :GET, :HEAD
34
+ case response[2] # the body
35
+ when RDF::Enumerable
36
+ serialize(env, *response)
37
+ else response
38
+ end
39
+ else response
40
+ end
41
+ end
42
+
43
+ ##
44
+ # Serializes an `RDF::Enumerable` response into a Rack protocol
45
+ # response using HTTP content negotiation rules.
46
+ #
47
+ # @param [Hash{String => String}] env
48
+ # @param [Integer] status
49
+ # @param [Hash{String => Object}] headers
50
+ # @param [RDF::Enumerable] body
51
+ # @return [Array(Integer, Hash, #each)]
52
+ def serialize(env, status, headers, body)
53
+ writer, content_type = find_writer(env)
54
+ writer ? [status, headers.merge('Content-Type' => content_type), [writer.dump(body)]] : not_acceptable
55
+ end
56
+
57
+ ##
58
+ # Returns an `RDF::Writer` class for the given `env`.
59
+ #
60
+ # @param [Hash{String => String}] env
61
+ # @return [Array(Class, String)]
62
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
63
+ def find_writer(env)
64
+ unless env.has_key?('HTTP_ACCEPT')
65
+ # HTTP/1.1 §14.1: "If no Accept header field is present, then it is
66
+ # assumed that the client accepts all media types"
67
+ find_writer_for_content_type(options[:default])
68
+ else
69
+ content_types = parse_accept_header(env['HTTP_ACCEPT'])
70
+ content_types.each do |content_type|
71
+ writer, content_type = find_writer_for_content_type(content_type)
72
+ return [writer, content_type] if writer
73
+ end
74
+ return nil
75
+ end
76
+ end
77
+
78
+ ##
79
+ # Returns an `RDF::Writer` class for the given `content_type`.
80
+ #
81
+ # @param [String, #to_s] content_type
82
+ # @return [Array(Class, String)]
83
+ def find_writer_for_content_type(content_type)
84
+ writer = case content_type.to_s
85
+ when '*/*'
86
+ RDF::Writer.for(:content_type => (content_type = options[:default]))
87
+ when /^([^\/]+)\/\*$/
88
+ nil # TODO: match subtype wildcards
89
+ else
90
+ RDF::Writer.for(:content_type => content_type)
91
+ end
92
+ writer ? [writer, content_type] : nil
93
+ end
94
+
95
+ protected
96
+
97
+ ##
98
+ # Parses an HTTP `Accept` header, returning an array of MIME content
99
+ # types ordered by the precedence rules defined in HTTP/1.1 §14.1.
100
+ #
101
+ # @param [String, #to_s] header
102
+ # @return [Array<String>]
103
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
104
+ def parse_accept_header(header)
105
+ content_types = header.to_s.split(',').map do |content_type_and_weight|
106
+ content_type_and_weight.strip!
107
+ case content_type_and_weight
108
+ when /^([^;]+);\s*q=(\d+\.\d+)$/
109
+ [[1.0, $2.to_f].min, $1, content_type_and_weight]
110
+ when /(\S+)/
111
+ [1.0, $1, content_type_and_weight]
112
+ else nil
113
+ end
114
+ end
115
+ content_types.compact! # remove nils
116
+ content_types = content_types.sort_by { |elem| [elem[0], elem[2].size] }
117
+ content_types.reverse.map { |elem| elem[1] }
118
+ end
119
+
120
+ ##
121
+ # Outputs an HTTP `406 Not Acceptable` response.
122
+ #
123
+ # @param [String, #to_s] message
124
+ # @return [Array(Integer, Hash, #each)]
125
+ def not_acceptable(message = nil)
126
+ http_error(406, message)
127
+ end
128
+
129
+ ##
130
+ # Outputs an HTTP `4xx` or `5xx` response.
131
+ #
132
+ # @param [Integer, #to_i] code
133
+ # @param [String, #to_s] message
134
+ # @param [Hash{String => String}] headers
135
+ # @return [Array(Integer, Hash, #each)]
136
+ def http_error(code, message = nil, headers = {})
137
+ message = http_status(code) + (message.nil? ? "\n" : " (#{message})\n")
138
+ [code, {'Content-Type' => 'text/plain; charset=utf-8'}.merge(headers), [message]]
139
+ end
140
+
141
+ ##
142
+ # Returns the standard HTTP status message for the given status `code`.
143
+ #
144
+ # @param [Integer, #to_i] code
145
+ # @return [String]
146
+ def http_status(code)
147
+ [code, Rack::Utils::HTTP_STATUS_CODES[code]].join(' ')
148
+ end
149
+ end # class ContentNegotiation
150
+ end; end # module Rack::LinkedData
@@ -0,0 +1,23 @@
1
+ module Rack; module LinkedData
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+ EXTRA = nil
7
+
8
+ STRING = [MAJOR, MINOR, TINY].join('.')
9
+ STRING << ".#{EXTRA}" if EXTRA
10
+
11
+ ##
12
+ # @return [String]
13
+ def self.to_s() STRING end
14
+
15
+ ##
16
+ # @return [String]
17
+ def self.to_str() STRING end
18
+
19
+ ##
20
+ # @return [Array(Integer, Integer, Integer)]
21
+ def self.to_a() [MAJOR, MINOR, TINY] end
22
+ end
23
+ end; end
@@ -0,0 +1,9 @@
1
+ require 'rack'
2
+ require 'linkeddata'
3
+
4
+ module Rack
5
+ module LinkedData
6
+ autoload :ContentNegotiation, 'rack/linkeddata/conneg'
7
+ autoload :VERSION, 'rack/linkeddata/version'
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-linkeddata
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Datagraph
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-05-26 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rack-test
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 5
30
+ - 3
31
+ version: 0.5.3
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 1
43
+ - 3
44
+ - 0
45
+ version: 1.3.0
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: yard
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ - 5
58
+ - 5
59
+ version: 0.5.5
60
+ type: :development
61
+ version_requirements: *id003
62
+ - !ruby/object:Gem::Dependency
63
+ name: rack
64
+ prerelease: false
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ segments:
70
+ - 1
71
+ - 0
72
+ - 0
73
+ version: 1.0.0
74
+ type: :runtime
75
+ version_requirements: *id004
76
+ - !ruby/object:Gem::Dependency
77
+ name: linkeddata
78
+ prerelease: false
79
+ requirement: &id005 !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ~>
82
+ - !ruby/object:Gem::Version
83
+ segments:
84
+ - 0
85
+ - 1
86
+ - 10
87
+ version: 0.1.10
88
+ type: :runtime
89
+ version_requirements: *id005
90
+ description: Rack middleware for Linked Data content negotiation.
91
+ email: datagraph@googlegroups.com
92
+ executables: []
93
+
94
+ extensions: []
95
+
96
+ extra_rdoc_files: []
97
+
98
+ files:
99
+ - AUTHORS
100
+ - README
101
+ - UNLICENSE
102
+ - VERSION
103
+ - lib/rack/linkeddata/conneg.rb
104
+ - lib/rack/linkeddata/version.rb
105
+ - lib/rack/linkeddata.rb
106
+ has_rdoc: false
107
+ homepage: http://github.com/datagraph
108
+ licenses:
109
+ - Public Domain
110
+ post_install_message:
111
+ rdoc_options: []
112
+
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ segments:
120
+ - 1
121
+ - 8
122
+ - 2
123
+ version: 1.8.2
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ segments:
129
+ - 0
130
+ version: "0"
131
+ requirements: []
132
+
133
+ rubyforge_project: datagraph
134
+ rubygems_version: 1.3.6
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: Linked Data content negotiation for Rack applications.
138
+ test_files: []
139
+