xenon-http 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/{xenon-http/lib → lib}/xenon/auth.rb +0 -0
  3. data/{xenon-http/lib → lib}/xenon/errors.rb +0 -0
  4. data/lib/xenon/etag.rb +85 -0
  5. data/{xenon-http/lib → lib}/xenon/headers.rb +7 -1
  6. data/{xenon-http/lib → lib}/xenon/headers/accept.rb +1 -1
  7. data/{xenon-http/lib → lib}/xenon/headers/accept_charset.rb +1 -1
  8. data/{xenon-http/lib → lib}/xenon/headers/accept_encoding.rb +1 -1
  9. data/{xenon-http/lib → lib}/xenon/headers/accept_language.rb +1 -1
  10. data/{xenon-http/lib → lib}/xenon/headers/authorization.rb +0 -0
  11. data/{xenon-http/lib → lib}/xenon/headers/cache_control.rb +0 -0
  12. data/lib/xenon/headers/content_length.rb +23 -0
  13. data/{xenon-http/lib → lib}/xenon/headers/content_type.rb +0 -0
  14. data/{xenon-http/lib → lib}/xenon/headers/if_match.rb +0 -0
  15. data/{xenon-http/lib → lib}/xenon/headers/if_modified_since.rb +0 -0
  16. data/{xenon-http/lib → lib}/xenon/headers/if_none_match.rb +0 -0
  17. data/{xenon-http/lib → lib}/xenon/headers/if_range.rb +0 -0
  18. data/{xenon-http/lib → lib}/xenon/headers/if_unmodified_since.rb +0 -0
  19. data/{xenon-http/lib → lib}/xenon/headers/user_agent.rb +0 -0
  20. data/{xenon-http/lib → lib}/xenon/headers/www_authenticate.rb +0 -0
  21. data/{xenon-http/lib → lib}/xenon/http.rb +0 -0
  22. data/{xenon-http/lib → lib}/xenon/http_version.rb +0 -0
  23. data/{xenon-http/lib → lib}/xenon/media_type.rb +4 -0
  24. data/{xenon-http/lib → lib}/xenon/parsers/basic_rules.rb +0 -0
  25. data/lib/xenon/parsers/etag.rb +9 -0
  26. data/{xenon-http/lib → lib}/xenon/parsers/header_rules.rb +0 -0
  27. data/{xenon-http/lib → lib}/xenon/parsers/media_type.rb +0 -0
  28. data/{xenon-http/lib → lib}/xenon/quoted_string.rb +4 -4
  29. data/{xenon-http/spec → spec}/spec_helper.rb +0 -0
  30. data/spec/xenon/etag_spec.rb +105 -0
  31. data/{xenon-http/spec → spec}/xenon/headers/accept_charset_spec.rb +0 -0
  32. data/{xenon-http/spec → spec}/xenon/headers/accept_encoding_spec.rb +0 -0
  33. data/{xenon-http/spec → spec}/xenon/headers/accept_language_spec.rb +0 -0
  34. data/{xenon-http/spec → spec}/xenon/headers/accept_spec.rb +0 -0
  35. data/{xenon-http/spec → spec}/xenon/headers/authorization_spec.rb +0 -0
  36. data/{xenon-http/spec → spec}/xenon/headers/cache_control_spec.rb +0 -0
  37. data/{xenon-http/spec → spec}/xenon/headers/if_match_spec.rb +0 -0
  38. data/{xenon-http/spec → spec}/xenon/headers/if_modified_since_spec.rb +0 -0
  39. data/{xenon-http/spec → spec}/xenon/headers/if_none_match_spec.rb +0 -0
  40. data/{xenon-http/spec → spec}/xenon/headers/if_range_spec.rb +1 -1
  41. data/{xenon-http/spec → spec}/xenon/headers/if_unmodified_since_spec.rb +0 -0
  42. data/{xenon-http/spec → spec}/xenon/headers/user_agent_spec.rb +0 -0
  43. data/{xenon-http/spec → spec}/xenon/headers/www_authenticate_spec.rb +0 -0
  44. data/{xenon-http/spec → spec}/xenon/media_type_spec.rb +1 -1
  45. data/{xenon-http/xenon-http.gemspec → xenon-http.gemspec} +2 -2
  46. metadata +50 -93
  47. data/.codeclimate.yml +0 -18
  48. data/.gitignore +0 -25
  49. data/.rspec +0 -3
  50. data/.travis.yml +0 -6
  51. data/Gemfile +0 -20
  52. data/Guardfile +0 -16
  53. data/LICENSE +0 -22
  54. data/README.md +0 -116
  55. data/Rakefile +0 -40
  56. data/VERSION +0 -1
  57. data/examples/hello_world/config.ru +0 -3
  58. data/examples/hello_world/hello_world.rb +0 -27
  59. data/xenon-http/lib/xenon/etag.rb +0 -48
  60. data/xenon-http/spec/xenon/etag_spec.rb +0 -19
  61. data/xenon-routing/lib/xenon/api.rb +0 -118
  62. data/xenon-routing/lib/xenon/marshallers.rb +0 -48
  63. data/xenon-routing/lib/xenon/request.rb +0 -40
  64. data/xenon-routing/lib/xenon/response.rb +0 -29
  65. data/xenon-routing/lib/xenon/routing.rb +0 -6
  66. data/xenon-routing/lib/xenon/routing/context.rb +0 -35
  67. data/xenon-routing/lib/xenon/routing/directives.rb +0 -14
  68. data/xenon-routing/lib/xenon/routing/header_directives.rb +0 -32
  69. data/xenon-routing/lib/xenon/routing/method_directives.rb +0 -26
  70. data/xenon-routing/lib/xenon/routing/param_directives.rb +0 -22
  71. data/xenon-routing/lib/xenon/routing/path_directives.rb +0 -37
  72. data/xenon-routing/lib/xenon/routing/route_directives.rb +0 -51
  73. data/xenon-routing/lib/xenon/routing/security_directives.rb +0 -34
  74. data/xenon-routing/lib/xenon/routing_version.rb +0 -3
  75. data/xenon-routing/spec/spec_helper.rb +0 -94
  76. data/xenon-routing/xenon-routing.gemspec +0 -25
  77. data/xenon.gemspec +0 -26
@@ -1,18 +0,0 @@
1
- # For more details, see here:
2
- # http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform
3
-
4
- # For a list of all available engines, see here:
5
- # http://docs.codeclimate.com/article/296-engines-available-engines
6
-
7
- engines:
8
- rubocop:
9
- enabled: true
10
-
11
- ratings:
12
- paths:
13
- - lib/**/*
14
-
15
- exclude_paths:
16
- - examples/**/*
17
- - spec/**/*
18
- - vendor/**/*
data/.gitignore DELETED
@@ -1,25 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .ruby-version
4
- .ruby-gemset
5
- Gemfile.lock
6
- /.config
7
- /coverage/
8
- /InstalledFiles
9
- /pkg/
10
- /spec/reports/
11
- /test/tmp/
12
- /test/version_tmp/
13
- /tmp/
14
-
15
- ## Documentation cache and generated files:
16
- /.yardoc/
17
- /_yardoc/
18
- /doc/
19
- /rdoc/
20
-
21
- ## Environment normalisation:
22
- /.bundle/
23
- /lib/bundler/man/
24
- .ruby-gemset
25
- .ruby-version
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --color
2
- --require spec_helper
3
- --format documentation
@@ -1,6 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.2.3
4
- before_install:
5
- - gem install bundler
6
- cache: bundler
data/Gemfile DELETED
@@ -1,20 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec
4
-
5
- Dir[File.join('*', '*.gemspec')].each do |gemspec|
6
- lib = gemspec.scan(/xenon-(.*)\.gemspec/).flatten.first
7
- gemspec(:name => "xenon-#{lib}", development_group: lib)
8
- end
9
-
10
- group :development, :test do
11
- gem 'rake', require: false
12
- gem 'rspec', require: false
13
- gem 'guard', require: false
14
- gem 'guard-rspec', require: false
15
- gem 'yard', require: false
16
- end
17
-
18
- group :test do
19
- gem "codeclimate-test-reporter", require: false
20
- end
data/Guardfile DELETED
@@ -1,16 +0,0 @@
1
- clearing :on
2
-
3
- guard :rspec, cmd: "bundle exec rspec" do
4
- require "guard/rspec/dsl"
5
- dsl = Guard::RSpec::Dsl.new(self)
6
-
7
- # RSpec files
8
- rspec = dsl.rspec
9
- watch(rspec.spec_helper) { rspec.spec_dir }
10
- watch(rspec.spec_support) { rspec.spec_dir }
11
- watch(rspec.spec_files)
12
-
13
- # Ruby files
14
- ruby = dsl.ruby
15
- dsl.watch_spec_files_for(ruby.lib_files)
16
- end
data/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2015 Greg Beech
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
22
-
data/README.md DELETED
@@ -1,116 +0,0 @@
1
- # Xenon
2
-
3
- [![Gem Version][fury-badge]][fury] [![Build Status][travis-badge]][travis] [![Code Climate][cc-badge]][cc] [![Test Coverage][ccc-badge]][ccc] [![YARD Docs][docs-badge]][docs]
4
-
5
- An HTTP framework for building RESTful APIs. As you can probably tell from the very low version number, this is in very early stages of development so I wouldn't use it anywhere close to production yet. But have a play around and let me know what you think, I'm very open to feedback.
6
-
7
- The **xenon** gem is a top-level gem that simply pulls in the other gems for convenience.
8
-
9
- ## xenon-http
10
-
11
- A set of model objects for the HTTP protocol which can parse and format the strings you typically get into proper objects you can work with. At the moment this covers key things like media types and the most common headers, but it will expand to cover the whole protocol. You can use the model by itself without any other parts of the library.
12
-
13
- This is how things tend to look:
14
-
15
- ```ruby
16
- accept = Xenon::Headers::Accept.parse('application/json, application/*; q=0.5')
17
- accept.media_ranges.first.media_type.json? #=> true
18
- accept.media_ranges.last.q #=> 0.5
19
- # etc.
20
- ```
21
-
22
- Yeah, it's not exciting and it's not glamorous, but if you need to parse parts of the HTTP protocol that other frameworks just don't reach, Xenon is here for you.
23
-
24
- ## xenon-routing
25
-
26
- A tree-based routing approach using "directives" which gives you great flexibility in building APIs and without the need to write extensions, helpers, etc. because everything is a directive and you extend it by simply writing more directives!
27
-
28
- A really simple example with a custom authentication directive is shown below. You can run this from the [examples](examples/hello_world) directory!
29
-
30
- ~~~ruby
31
- class HelloWorld < Xenon::API
32
- path '/' do
33
- get do
34
- hello_auth do |user|
35
- params :greeting do |greeting|
36
- complete :ok, { greeting => user.username }
37
- end
38
- end
39
- end
40
- end
41
-
42
- private
43
-
44
- def hello_auth
45
- @authenticator ||= Xenon::BasicAuth.new realm: 'hello world' do |credentials|
46
- OpenStruct.new(username: credentials.username) # should actually auth here!
47
- end
48
- authenticate @authenticator do |user|
49
- authorize user.username == 'greg' do
50
- yield user
51
- end
52
- end
53
- end
54
- end
55
- ~~~
56
-
57
- Of course, it does all the things you'd expect like support content negotiation properly and return the correct status codes when paths or methods aren't found. For example, if you try to `POST` to the above code you'll see the error:
58
-
59
- ~~~json
60
- {
61
- "status": 405,
62
- "developer_message": "Supported methods: GET"
63
- }
64
- ~~~
65
-
66
- Or if you send it an `Accept` header that doesn't allow JSON (the only supported format by default) you'll see:
67
-
68
- ~~~ruby
69
- {
70
- "status": 406,
71
- "developer_message": "Supported media types: application/json"
72
- }
73
- ~~~
74
-
75
- ## Spray
76
-
77
- Xenon is inspired by [Spray][spray], an awesome Scala framework for building RESTful APIs, and which I sorely miss while working in Ruby. However although it's inspired by it, there are some key differences.
78
-
79
- Firsly Xenon is synchronous rather than asynchronous, as that is a much more common approach to writing code in Ruby, and fits better with commonly used frameworks such as Rack and ActiveRecord. It's also much easier to write and reason about synchronous code, and you can still scale it pretty well using the process model.
80
-
81
- Secondly the directives are just methods which are composed using Ruby's usual `yield` mechanism rather than being monads composed with flat map as in Spray. This is primarily to make the framework feel natural for Ruby users where the general desire is for simplicity and "it just works". This does limit composability of directives, but for most real-world situations this doesn't seem to be a problem so I think it's the right trade-off.
82
-
83
- ## Installation
84
-
85
- Add this line to your application's Gemfile:
86
-
87
- gem 'xenon'
88
-
89
- And then execute:
90
-
91
- $ bundle
92
-
93
- Or install it yourself as:
94
-
95
- $ gem install xenon
96
-
97
- ## Contributing
98
-
99
- 1. Fork it ( https://github.com/gregbeech/xenon/fork )
100
- 2. Create your feature branch (`git checkout -b my-new-feature`)
101
- 3. Commit your changes (`git commit -am 'Add some feature'`)
102
- 4. Push to the branch (`git push origin my-new-feature`)
103
- 5. Create a new Pull Request
104
-
105
-
106
- [fury]: http://badge.fury.io/rb/xenon "Xenon at Rubygems"
107
- [fury-badge]: https://badge.fury.io/rb/xenon.svg "Gem Version"
108
- [travis]: https://travis-ci.org/gregbeech/xenon "Xenon at Travis CI"
109
- [travis-badge]: https://travis-ci.org/gregbeech/xenon.svg "Build Status"
110
- [cc]: https://codeclimate.com/github/gregbeech/xenon "Xenon Quality at Code Climate"
111
- [cc-badge]: https://codeclimate.com/github/gregbeech/xenon/badges/gpa.svg "Code Quality"
112
- [ccc]: https://codeclimate.com/github/gregbeech/xenon/coverage "Xenon Coverage at Code Climate"
113
- [ccc-badge]: https://codeclimate.com/github/gregbeech/xenon/badges/coverage.svg "Code Coverage"
114
- [docs]: http://www.rubydoc.info/github/gregbeech/xenon "YARD Docs"
115
- [docs-badge]: http://img.shields.io/badge/yard-docs-blue.svg "YARD Docs"
116
- [spray]: http://spray.io/ "spray"
data/Rakefile DELETED
@@ -1,40 +0,0 @@
1
- # require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- task :default => :spec
5
- task :test => :spec
6
-
7
- desc 'Do not use; use the spec task'
8
- RSpec::Core::RakeTask.new(:rspec)
9
-
10
- desc 'Runs specifications for all gems'
11
- task :spec => [:'spec:http', :'spec:routing']
12
- namespace :spec do
13
- %i[http routing].each do |lib|
14
- desc "Runs specifications for the xenon-#{lib} gem"
15
- task lib do
16
- Dir.chdir("xenon-#{lib}") do
17
- Rake::Task['rspec'].reenable
18
- Rake::Task['rspec'].invoke
19
- end
20
- end
21
- end
22
- end
23
-
24
- desc 'Build gems into the pkg directory'
25
- task :build do
26
- FileUtils.rm_rf('pkg')
27
- Dir[File.join('*', '*.gemspec')].each do |gemspec|
28
- system "gem build #{gemspec}"
29
- end
30
- system "gem build xenon.gemspec"
31
- FileUtils.mkdir_p('pkg')
32
- FileUtils.mv(Dir['*.gem'], 'pkg')
33
- end
34
-
35
- desc 'Tags version, pushes to remote, and pushes gems'
36
- task :release => :build do
37
- sh "git tag v#{File.read(File.join(__dir__, 'VERSION'))}"
38
- sh "git push origin master"
39
- sh "ls pkg/*.gem | xargs -n 1 gem push"
40
- end
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.0.4
@@ -1,3 +0,0 @@
1
- require_relative 'hello_world'
2
-
3
- run HelloWorld.new
@@ -1,27 +0,0 @@
1
- require 'xenon/api'
2
- require 'ostruct'
3
-
4
- class HelloWorld < Xenon::API
5
- path '/' do
6
- get do
7
- hello_auth do |user|
8
- params :greeting do |greeting|
9
- complete :ok, { greeting => user.username }
10
- end
11
- end
12
- end
13
- end
14
-
15
- private
16
-
17
- def hello_auth
18
- @authenticator ||= Xenon::BasicAuth.new realm: 'hello world' do |credentials|
19
- OpenStruct.new(username: credentials.username) # should actually auth here!
20
- end
21
- authenticate @authenticator do |user|
22
- authorize user.username == 'greg' do
23
- yield user
24
- end
25
- end
26
- end
27
- end
@@ -1,48 +0,0 @@
1
- require 'xenon/parsers/header_rules'
2
-
3
- module Xenon
4
- class ETag
5
- attr_reader :opaque_tag
6
-
7
- def initialize(opaque_tag, weak: false)
8
- @opaque_tag = opaque_tag
9
- @weak = weak
10
- end
11
-
12
- def self.parse(s)
13
- tree = Parsers::ETag.new.etag.parse(s)
14
- Parsers::ETagHeaderTransform.new.apply(tree)
15
- end
16
-
17
- def weak?
18
- @weak
19
- end
20
-
21
- def strong?
22
- !weak?
23
- end
24
-
25
- def strong_eq?(other)
26
- strong? && other.strong? && @opaque_tag == other.opaque_tag
27
- end
28
-
29
- def weak_eq?(other)
30
- @opaque_tag == other.opaque_tag
31
- end
32
-
33
- def ==(other)
34
- strong? == other.strong? && @opaque_tag == other.opaque_tag
35
- end
36
-
37
- def to_s
38
- s = weak? ? "W/" : ""
39
- s << '"' << @opaque_tag << '"'
40
- end
41
- end
42
-
43
- module Parsers
44
- class ETag < Parslet::Parser
45
- include ETagHeaderRules
46
- end
47
- end
48
- end
@@ -1,19 +0,0 @@
1
- require 'xenon/etag'
2
-
3
- describe Xenon::ETag do
4
-
5
- describe '::parse' do
6
- it 'can parse a strong etag' do
7
- etag = Xenon::ETag.parse('"xyzzy"')
8
- expect(etag.opaque_tag).to eq 'xyzzy'
9
- expect(etag).to be_strong
10
- end
11
-
12
- it 'can parse a weak etag' do
13
- etag = Xenon::ETag.parse('W/"xyzzy"')
14
- expect(etag.opaque_tag).to eq 'xyzzy'
15
- expect(etag).to be_weak
16
- end
17
- end
18
-
19
- end
@@ -1,118 +0,0 @@
1
- require 'json'
2
- require 'rack'
3
- require 'xenon/routing'
4
-
5
- module Xenon
6
- class API
7
- include Xenon::Routing::Directives
8
-
9
- DEFAULT_MARSHALLERS = [JsonMarshaller.new]
10
-
11
- class << self
12
- def marshallers(*marshallers)
13
- @marshallers = marshallers unless marshallers.nil? || marshallers.empty?
14
- (@marshallers.nil? || @marshallers.empty?) ? DEFAULT_MARSHALLERS : @marshallers
15
- end
16
-
17
- def select_marshaller(media_ranges)
18
- weighted = marshallers.map do |marshaller|
19
- media_range = media_ranges.find { |media_range| marshaller.marshal_to?(media_range) }
20
- [marshaller, media_range ? media_range.q : 0]
21
- end
22
- weighted.select { |_, q| q > 0 }.sort_by { |_, q| q }.map { |m, _| m }.last
23
- end
24
- end
25
-
26
- attr_reader :context
27
-
28
- class << self
29
- def routes
30
- @routes ||= []
31
- end
32
-
33
- def method_missing(name, *args, &block)
34
- if instance_methods.include?(name)
35
- routes << [name, args, block]
36
- else
37
- super
38
- end
39
- end
40
- end
41
-
42
- def call(env)
43
- dup.call!(env)
44
- end
45
-
46
- def call!(env)
47
- @context = Routing::Context.new(Request.new(Rack::Request.new(env)), Response.new)
48
-
49
- accept = @context.request.header('Accept')
50
- marshaller = accept ? self.class.select_marshaller(accept.media_ranges) : self.class.marshallers.first
51
-
52
- catch (:complete) do
53
- begin
54
- if marshaller
55
- self.class.routes.each do |route|
56
- name, args, block = route
57
- route_block = proc { instance_eval(&block) }
58
- send(name, *args, &route_block)
59
- end
60
- else
61
- reject :accept, supported: self.class.marshallers.map(&:media_type)
62
- end
63
- handle_rejections(@context.rejections)
64
- rescue => e
65
- handle_error(e)
66
- end
67
- end
68
-
69
- marshaller ||= self.class.marshallers.first
70
- resp = @context.response.copy(
71
- headers: @context.response.headers.set(Headers::ContentType.new(marshaller.content_type)),
72
- body: marshaller.marshal(@context.response.body))
73
- [resp.status, resp.headers.map { |h| [h.name, h.to_s] }.to_h, resp.body]
74
- end
75
-
76
- def handle_error(e)
77
- puts "handle_error: #{e.class}: #{e}\n #{e.backtrace.join("\n ")}"
78
- case e
79
- when ParseError
80
- fail 400, e.message
81
- else
82
- fail 500, e.message # TODO: Only if verbose errors configured
83
- end
84
- end
85
-
86
- def handle_rejections(rejections)
87
- puts "handle_rejections: #{rejections}"
88
- if rejections.empty?
89
- fail 404
90
- else
91
- rejection = rejections.first
92
- case rejection.reason
93
- when :accept
94
- fail 406, "Supported media types: #{rejection[:supported].join(", ")}"
95
- when :forbidden
96
- fail 403
97
- when :header
98
- fail 400, "Missing required header: #{rejection[:required]}"
99
- when :method
100
- supported = rejections.take_while { |r| r.reason == :method }.map { |r| r[:supported].upcase }
101
- fail 405, "Supported methods: #{supported.join(", ")}"
102
- when :unauthorized
103
- if rejection[:scheme]
104
- challenge = Headers::Challenge.new(rejection[:scheme], rejection.info.except(:scheme))
105
- respond_with_header Headers::WWWAuthenticate.new(challenge) do
106
- fail 401
107
- end
108
- else
109
- fail 401
110
- end
111
- else
112
- fail 500
113
- end
114
- end
115
- end
116
-
117
- end
118
- end