webbed 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +22 -0
  2. data/.travis.yml +7 -0
  3. data/.yardopts +6 -0
  4. data/Gemfile +5 -13
  5. data/Guardfile +5 -0
  6. data/README.md +36 -31
  7. data/Rakefile +7 -14
  8. data/lib/webbed.rb +28 -6
  9. data/lib/webbed/generic_message.rb +27 -15
  10. data/lib/webbed/headers.rb +2 -0
  11. data/lib/webbed/helpers/entity_headers_helper.rb +62 -0
  12. data/lib/webbed/helpers/method_helper.rb +83 -0
  13. data/lib/webbed/helpers/rack_request_helper.rb +86 -0
  14. data/lib/webbed/helpers/rack_response_helper.rb +56 -0
  15. data/lib/webbed/helpers/request_headers_helper.rb +62 -0
  16. data/lib/webbed/helpers/request_uri_helper.rb +34 -0
  17. data/lib/webbed/helpers/response_headers_helper.rb +48 -0
  18. data/lib/webbed/helpers/scheme_helper.rb +26 -0
  19. data/lib/webbed/http_version.rb +88 -33
  20. data/lib/webbed/media_type.rb +160 -0
  21. data/lib/webbed/method.rb +63 -33
  22. data/lib/webbed/request.rb +65 -21
  23. data/lib/webbed/response.rb +65 -24
  24. data/lib/webbed/status_code.rb +84 -17
  25. data/lib/webbed/version.rb +1 -1
  26. data/test/support/assertions.rb +17 -0
  27. data/test/support/runner.rb +326 -0
  28. data/test/test_helper.rb +13 -0
  29. data/test/webbed/generic_message_test.rb +44 -0
  30. data/test/webbed/headers_test.rb +31 -0
  31. data/test/webbed/helpers/entity_headers_helper_test.rb +68 -0
  32. data/test/webbed/helpers/method_helper_test.rb +151 -0
  33. data/test/webbed/helpers/rack_request_helper_test.rb +108 -0
  34. data/test/webbed/helpers/rack_response_helper_test.rb +33 -0
  35. data/test/webbed/helpers/request_headers_helper_test.rb +57 -0
  36. data/test/webbed/helpers/request_uri_helper_test.rb +32 -0
  37. data/test/webbed/helpers/response_headers_helper_test.rb +46 -0
  38. data/test/webbed/helpers/scheme_helper_test.rb +28 -0
  39. data/test/webbed/http_version_test.rb +52 -0
  40. data/test/webbed/media_type_test.rb +100 -0
  41. data/test/webbed/method_test.rb +160 -0
  42. data/test/webbed/request_test.rb +74 -0
  43. data/test/webbed/response_test.rb +86 -0
  44. data/test/webbed/status_code_test.rb +105 -0
  45. data/webbed.gemspec +31 -0
  46. metadata +128 -41
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ coverage
2
+ rdoc
3
+ pkg
4
+ test/tmp
5
+ test/version_tmp
6
+ tmp
7
+ pkg
8
+ *.gem
9
+ *.rbc
10
+ lib/bundler/man
11
+ spec/reports
12
+ .config
13
+ InstalledFiles
14
+ .bundle
15
+
16
+ # YARD artifacts
17
+ .yardoc
18
+ _yardoc
19
+ doc/
20
+
21
+ # Gem-specific
22
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ script: 'rake test'
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - ree
6
+ - rbx
7
+ - jruby
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --readme README.md
2
+ --markup markdown
3
+ --markup-provider maruku
4
+ --default-return ""
5
+ --title "Webbed Documentation"
6
+ --hide-void-return
data/Gemfile CHANGED
@@ -1,15 +1,7 @@
1
- source 'http://rubygems.org'
1
+ source :gemcutter
2
2
 
3
- gem 'webbed', :path => File.expand_path("..", __FILE__)
4
- gem 'addressable', '~> 2.1'
3
+ # This is temporary until guard-minitest accepts my patch.
4
+ gem 'guard-minitest', :git => 'git://github.com/CapnKernul/guard-minitest.git'
5
+ gem 'rb-fsevent'
5
6
 
6
- group :development do
7
- gem 'rake'
8
- end
9
-
10
- group :test do
11
- gem 'rspec', '~> 1.3', :require => 'spec'
12
- gem 'bourne'
13
- gem 'heckle'
14
- gem 'ruby2ruby', '1.2.2'
15
- end
7
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard 'minitest', :notify => true do
2
+ watch(/^test\/(.*)_test.rb/)
3
+ watch(/^lib\/(.*)\.rb/) { |m| "test/#{m[1]}_test.rb" }
4
+ watch(/^test\/test_helper.rb/) { 'test' }
5
+ end
data/README.md CHANGED
@@ -1,32 +1,37 @@
1
- Webbed
2
- ======
3
- Take control of HTTP.
4
-
5
- Note on Releases
6
- ----------------
7
- Releases are tagged on GitHub and also released as gems on RubyGems. The branch `master` is pushed to whenever I add a patch or a new feature. To build from master, you can clone the code, generate the updated `gemspec`, build the gem and install.
8
-
9
- * `rake gemspec`
10
- * `gem build webbed.gemspec`
11
- * `gem install` the gem that was built
12
-
13
- Note on Patches/Pull Requests
14
- -----------------------------
15
- * Fork the project.
16
- * Make your feature addition or bug fix.
17
- * Add tests for it. This is important so I don't break it in a future version unintentionally.
18
- * Commit, do not mess with the `Rakefile`, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull)
19
- * Send me a pull request. Bonus points for topic branches.
20
-
21
- Requirements
22
- ------------
23
- * [Bundler](http://github.com/carlhuda/bundler)
24
- * [Addressable](http://github.com/sporkmonger/addressable)
25
-
26
- Install
27
- -------
28
- * `gem install webbed`
29
-
30
- License
31
- -------
1
+ # webbed - take control of HTTP [![StillMaintained Status](http://stillmaintained.com/CapnKernul/webbed.png)](http://stillmaintained.com/CapnKernul/webbed) [![Build Status](http://travis-ci.org/CapnKernul/webbed.png)](http://travis-ci.org/CapnKernul/webbed) #
2
+
3
+ Webbed provides useful abstractions on top of the basic parts of the HTTP
4
+ specification, most notably HTTP Requests and Responses. It is far more
5
+ full-featured than `Rack::Request` and `Rack::Response`, allowing you to easily
6
+ access the complex features of HTTP.
7
+
8
+ ## Usage ##
9
+
10
+ Coming soon. Check out the tests and documentation in the meantime.
11
+
12
+ ## Installation ##
13
+
14
+ Without bundler:
15
+
16
+ gem install webbed
17
+
18
+ With bundler:
19
+
20
+ gem 'webbed'
21
+
22
+ ## Note on Patches/Pull Requests ##
23
+
24
+ * Fork the project.
25
+ * Make your feature addition or bug fix.
26
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
27
+ * Commit, but do not mess with the `Rakefile`. If you want to have your own version, that is fine but bump the version in a commit by itself in another branch so I can ignore it when I pull.
28
+ * Send me a pull request. Bonus points for git flow feature branches.
29
+
30
+ ## Resources ##
31
+
32
+ * [GitHub Repository](https://github.com/CapnKernul/webbed)
33
+ * [Documentation](http://rubydoc.info/github/CapnKernul/webbed)
34
+
35
+ ## License ##
36
+
32
37
  Webbed is licensed under the MIT License. See `LICENSE` for details.
data/Rakefile CHANGED
@@ -1,18 +1,11 @@
1
1
  require 'bundler'
2
- Bundler.setup :default, :development, :test
2
+ Bundler::GemHelper.install_tasks
3
3
 
4
- require 'webbed'
5
- require 'spec/rake/spectask'
4
+ task :default => :test
6
5
 
7
- Spec::Rake::SpecTask.new do |t|
8
- t.libs << 'lib'
9
- end
10
-
11
- gemspec = eval(File.read('webbed.gemspec'))
12
-
13
- task :build => "#{gemspec.full_name}.gem"
14
-
15
- file "#{gemspec.full_name}.gem" => gemspec.files + ['webbed.gemspec'] do
16
- system 'gem build webbed.gemspec'
17
- system "gem install webbed-#{Webbed::VERSION}.gem"
6
+ require 'rake/testtask'
7
+ Rake::TestTask.new do |t|
8
+ t.ruby_opts += ['-rubygems']
9
+ t.libs << 'test'
10
+ t.pattern = 'test/**/*_test.rb'
18
11
  end
data/lib/webbed.rb CHANGED
@@ -1,9 +1,31 @@
1
+ # Webbed provides two important abstraction on the HTTP specification (RFC
2
+ # 2616), {Request} and {Response}. Although the core classes have the
3
+ # bare-minimum of functionality for interacting with the two classes, they have
4
+ # been extended with a variety of helper modules, adding a significant amount of
5
+ # semantic meaning to the instances of each class.
6
+ #
7
+ # Webbed can be used with or without Rack, but it includes Rack helpers for
8
+ # convenience.
9
+ #
10
+ # @todo Add exmaples of webbed in action.
1
11
  module Webbed
2
- autoload :Headers, 'webbed/headers'
3
- autoload :HTTPVersion, 'webbed/http_version'
12
+ autoload :Headers, 'webbed/headers'
13
+ autoload :HTTPVersion, 'webbed/http_version'
4
14
  autoload :GenericMessage, 'webbed/generic_message'
5
- autoload :Method, 'webbed/method'
6
- autoload :StatusCode, 'webbed/status_code'
7
- autoload :Request, 'webbed/request'
8
- autoload :Response, 'webbed/response'
15
+ autoload :MediaType, 'webbed/media_type'
16
+ autoload :Method, 'webbed/method'
17
+ autoload :StatusCode, 'webbed/status_code'
18
+ autoload :Request, 'webbed/request'
19
+ autoload :Response, 'webbed/response'
20
+
21
+ module Helpers
22
+ autoload :MethodHelper, 'webbed/helpers/method_helper'
23
+ autoload :RackRequestHelper, 'webbed/helpers/rack_request_helper'
24
+ autoload :RackResponseHelper, 'webbed/helpers/rack_response_helper'
25
+ autoload :RequestURIHelper, 'webbed/helpers/request_uri_helper'
26
+ autoload :SchemeHelper, 'webbed/helpers/scheme_helper'
27
+ autoload :RequestHeadersHelper, 'webbed/helpers/request_headers_helper'
28
+ autoload :ResponseHeadersHelper, 'webbed/helpers/response_headers_helper'
29
+ autoload :EntityHeadersHelper, 'webbed/helpers/entity_headers_helper'
30
+ end
9
31
  end
@@ -1,28 +1,40 @@
1
1
  module Webbed
2
+ # Generic methods used for both {Request}'s and {Response}'s.
3
+ #
4
+ # @abstract Include and implement `#status_line`.
2
5
  module GenericMessage
3
-
6
+ # The HTTP-Version of the message.
7
+ #
8
+ # The method automatically converts the new value to an instance of
9
+ # {HTTPVersion} if it is not already one.
10
+ #
11
+ # @return [HTTPVersion]
4
12
  attr_reader :http_version
5
- attr_accessor :entity_body
6
-
7
- # This is purely for test/spec purposes.
8
- def initialize
9
- self.http_version = 'HTTP/1.1'
10
- self.entity_body = ''
11
- end
12
-
13
- # Must be overridden!
14
- def start_line
15
- "Invalid Start Line\r\n"
16
- end
17
13
 
18
14
  def http_version=(http_version)
19
15
  @http_version = Webbed::HTTPVersion.new(http_version)
20
16
  end
21
17
 
22
- def headers
23
- @headers ||= Webbed::Headers.new
18
+ # The Headers of the message.
19
+ #
20
+ # The method automatically converts the new value to an instance of
21
+ # {Headers} if it is not already one.
22
+ #
23
+ # @return [Headers]
24
+ attr_reader :headers
25
+
26
+ def headers=(headers)
27
+ @headers = Webbed::Headers.new(headers)
24
28
  end
25
29
 
30
+ # The Entity Body of the message.
31
+ #
32
+ # @return [#to_s]
33
+ attr_accessor :entity_body
34
+
35
+ # Converts the message into an HTTP Message as per RFC 2616.
36
+ #
37
+ # @return [String]
26
38
  def to_s
27
39
  "#{start_line}#{headers.to_s}\r\n#{entity_body}"
28
40
  end
@@ -1,4 +1,6 @@
1
1
  module Webbed
2
+ # Representation of HTTP headers.
3
+ #
2
4
  # This class is stolen directly from Rack.
3
5
  class Headers < Hash
4
6
  def self.new(hash={})
@@ -0,0 +1,62 @@
1
+ module Webbed
2
+ module Helpers
3
+ # Request and Response helper for Entity Headers
4
+ module EntityHeadersHelper
5
+ # The Content-Length of the Entity (as defined in the Content-Length Header).
6
+ #
7
+ # @return [Fixnum, nil]
8
+ def content_length
9
+ headers['Content-Length'] ? headers['Content-Length'].to_i : nil
10
+ end
11
+
12
+ # Sets the Content-Length of the Entity (as defined in the Content-Length Header).
13
+ #
14
+ # @param [#to_s] content_length
15
+ def content_length=(content_length)
16
+ headers['Content-Length'] = content_length.to_s
17
+ end
18
+
19
+ # The Content-Location of the Entity (as defined in the Content-Location Header).
20
+ #
21
+ # @return [Addressable::URI, nil]
22
+ def content_location
23
+ headers['Content-Location'] ? Addressable::URI.parse(headers['Content-Location']) : nil
24
+ end
25
+
26
+ # Sets the Content-Location of the Entity (as defined in the Content-Location Header).
27
+ #
28
+ # @param [#to_s] content_location
29
+ def content_location=(content_location)
30
+ headers['Content-Location'] = content_location.to_s
31
+ end
32
+
33
+ # The Content-MD5 of the Entity (as defined in the Content-MD5 Header).
34
+ #
35
+ # @return [String, nil]
36
+ def content_md5
37
+ headers['Content-MD5']
38
+ end
39
+
40
+ # Sets the Content-MD5 of the Entity (as defined in the Content-MD5 Header).
41
+ #
42
+ # @param [String] content_md5
43
+ def content_md5=(content_md5)
44
+ headers['Content-MD5'] = content_md5
45
+ end
46
+
47
+ # The Content-Type of the Entity (as defined in the Content-Type Header).
48
+ #
49
+ # @return [MediaType, nil]
50
+ def content_type
51
+ headers['Content-Type'] ? Webbed::MediaType.new(headers['Content-Type']) : nil
52
+ end
53
+
54
+ # Sets the Content-Type of the Entity (as defined in the Content-Type Header).
55
+ #
56
+ # @param [#to_s] content_type
57
+ def content_type=(content_type)
58
+ headers['Content-Type'] = content_type.to_s
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,83 @@
1
+ module Webbed
2
+ module Helpers
3
+ # Request helper for the Method
4
+ module MethodHelper
5
+ # Whether or not the Request is safe based on the Method
6
+ #
7
+ # @return [Boolean]
8
+ def safe?
9
+ method.safe?
10
+ end
11
+
12
+ # Whether or not the Request is idempotent based on the Method
13
+ #
14
+ # @return [Boolean]
15
+ def idempotent?
16
+ method.idempotent?
17
+ end
18
+
19
+ # Whether or not the Request uses the OPTIONS Method
20
+ #
21
+ # @return [Boolean]
22
+ def options?
23
+ method == 'OPTIONS'
24
+ end
25
+
26
+ # Whether or not the Request uses the GET Method
27
+ #
28
+ # @return [Boolean]
29
+ def get?
30
+ method == 'GET'
31
+ end
32
+
33
+ # Whether or not the Request uses the HEAD Method
34
+ #
35
+ # @return [Boolean]
36
+ def head?
37
+ method == 'HEAD'
38
+ end
39
+
40
+ # Whether or not the Request uses the POST Method
41
+ #
42
+ # @return [Boolean]
43
+ def post?
44
+ method == 'POST'
45
+ end
46
+
47
+ # Whether or not the Request uses the PUT Method
48
+ #
49
+ # @return [Boolean]
50
+ def put?
51
+ method == 'PUT'
52
+ end
53
+
54
+ # Whether or not the Request uses the DELETE Method
55
+ #
56
+ # @return [Boolean]
57
+ def delete?
58
+ method == 'DELETE'
59
+ end
60
+
61
+ # Whether or not the Request uses the TRACE Method
62
+ #
63
+ # @return [Boolean]
64
+ def trace?
65
+ method == 'TRACE'
66
+ end
67
+
68
+ # Whether or not the Request uses the CONNECT Method
69
+ #
70
+ # @return [Boolean]
71
+ def connect?
72
+ method == 'CONNECT'
73
+ end
74
+
75
+ # Whether or not the Request uses the PATCH Method
76
+ #
77
+ # @return [Boolean]
78
+ def patch?
79
+ method == 'PATCH'
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,86 @@
1
+ module Webbed
2
+ module Helpers
3
+ # Request helper for converting Rack environments into Requests
4
+ module RackRequestHelper
5
+ module ClassMethods
6
+ # Create a new Request from a Rack environment
7
+ #
8
+ # This method will take the HTTP-Version, Method, Request-URI, Headers,
9
+ # Entity Body, and scheme from the Rack environment and create a new
10
+ # Request using that data. The Rack environment can be accessed through
11
+ # `#rack_env`.
12
+ #
13
+ # @param [Hash{String => String}] rack_env
14
+ # @return [Request]
15
+ # @note The Rack environment will never be modified.
16
+ def from_rack(rack_env)
17
+ env = rack_env.dup
18
+ method = env.delete('REQUEST_METHOD')
19
+ request_uri = "#{env['PATH_INFO']}?#{env['QUERY_STRING']}"
20
+ http_version = env.delete('HTTP_VERSION') || 'HTTP/1.1'
21
+ scheme = env.delete('rack.url_scheme')
22
+ entity_body = env.delete('rack.input')
23
+
24
+ headers = env.inject({}) do |memo, h|
25
+ memo[$1.gsub('_', '-')] = h[1] if h[0] =~ /^HTTP_(.*)/
26
+ memo
27
+ end
28
+
29
+ headers['Content-Type'] = env.delete('CONTENT_TYPE')
30
+ headers['Content-Length'] = env.delete('CONTENT_LENGTH')
31
+
32
+ request = new(method, request_uri, headers, entity_body, {
33
+ :http_version => http_version,
34
+ :scheme => scheme
35
+ })
36
+
37
+ request.rack_env = rack_env
38
+
39
+ request
40
+ end
41
+ end
42
+
43
+ module InstanceMethods
44
+ # The Rack environment of the Request
45
+ #
46
+ # @return [Hash{String => String}]
47
+ attr_accessor :rack_env
48
+
49
+ # Generate the Rack enivronment equivalent of the Request.
50
+ #
51
+ # If the Request was created with an environment, the generated
52
+ # environment will be merged with the original environment. The original
53
+ # will not be modified.
54
+ #
55
+ # @return [Hash{String => String}]
56
+ def to_rack
57
+ env = rack_env ? rack_env.dup : {}
58
+ env['REQUEST_METHOD'] = method.to_s
59
+ env['PATH_INFO'] = request_uri.path
60
+ env['QUERY_STRING'] = request_uri.query
61
+ env['HTTP_VERSION'] = http_version.to_s
62
+ env['rack.url_scheme'] = scheme
63
+ env['rack.input'] = entity_body
64
+
65
+ rack_headers = headers.dup
66
+ env['CONTENT_TYPE'] = rack_headers.delete('Content-Type')
67
+ env['CONTENT_LENGTH'] = rack_headers.delete('Content-Length')
68
+
69
+ rack_headers.each do |header, value|
70
+ header = header.upcase.gsub('-', '_')
71
+ env["HTTP_#{header}"] = value
72
+ end
73
+
74
+ env
75
+ end
76
+ end
77
+
78
+ # @see ClassMethods
79
+ # @see InstanceMethods
80
+ def self.included(base)
81
+ base.send :include, InstanceMethods
82
+ base.send :extend, ClassMethods
83
+ end
84
+ end
85
+ end
86
+ end