m2r 0.0.3 → 1.0.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.
Files changed (51) hide show
  1. data/Gemfile +6 -0
  2. data/README.md +141 -35
  3. data/Rakefile +13 -45
  4. data/example/Procfile +4 -0
  5. data/example/config.sqlite +0 -0
  6. data/example/http_0mq.rb +37 -19
  7. data/example/lobster.ru +14 -6
  8. data/example/mongrel2.conf +47 -0
  9. data/example/tmp/access.log +505 -0
  10. data/example/uploading.ru +37 -0
  11. data/lib/m2r.rb +49 -3
  12. data/lib/m2r/connection.rb +66 -0
  13. data/lib/m2r/connection_factory.rb +41 -0
  14. data/lib/m2r/handler.rb +130 -0
  15. data/lib/m2r/headers.rb +72 -0
  16. data/lib/m2r/rack_handler.rb +47 -0
  17. data/lib/m2r/request.rb +129 -0
  18. data/lib/m2r/request/base.rb +33 -0
  19. data/lib/m2r/request/upload.rb +60 -0
  20. data/lib/m2r/response.rb +102 -0
  21. data/lib/m2r/response/content_length.rb +18 -0
  22. data/lib/m2r/version.rb +5 -0
  23. data/lib/rack/handler/mongrel2.rb +33 -0
  24. data/m2r.gemspec +30 -63
  25. data/test/acceptance/examples_test.rb +32 -0
  26. data/test/support/capybara.rb +4 -0
  27. data/test/support/mongrel_helper.rb +40 -0
  28. data/test/support/test_handler.rb +51 -0
  29. data/test/support/test_user.rb +37 -0
  30. data/test/test_helper.rb +5 -0
  31. data/test/unit/connection_factory_test.rb +29 -0
  32. data/test/unit/connection_test.rb +49 -0
  33. data/test/unit/handler_test.rb +41 -0
  34. data/test/unit/headers_test.rb +50 -0
  35. data/test/unit/m2r_test.rb +40 -0
  36. data/test/unit/rack_handler_test.rb +52 -0
  37. data/test/unit/request_test.rb +38 -0
  38. data/test/unit/response_test.rb +30 -0
  39. metadata +310 -105
  40. data/.document +0 -5
  41. data/.gitignore +0 -21
  42. data/ISSUES +0 -62
  43. data/VERSION +0 -1
  44. data/benchmarks/jruby +0 -60
  45. data/example/rack_handler.rb +0 -69
  46. data/lib/connection.rb +0 -158
  47. data/lib/fiber_handler.rb +0 -43
  48. data/lib/handler.rb +0 -66
  49. data/lib/request.rb +0 -44
  50. data/test/helper.rb +0 -10
  51. data/test/test_m2r.rb +0 -7
@@ -0,0 +1,129 @@
1
+ require 'm2r'
2
+ require 'm2r/request/base'
3
+ require 'm2r/request/upload'
4
+ require 'm2r/headers'
5
+
6
+ module M2R
7
+ # Abstraction over Mongrel 2 request
8
+ # @api public
9
+ class Request
10
+ # @api private
11
+ TRUE_STRINGS = %w(true yes on 1).map(&:freeze).freeze
12
+
13
+ include Base
14
+ include Upload
15
+
16
+ # @return [String] UUID of mongrel2 origin instance
17
+ attr_reader :sender
18
+
19
+ # @return [String] Mongrel2 connection id sending this request
20
+ attr_reader :conn_id
21
+
22
+ # @return [String] HTTP Path of request
23
+ attr_reader :path
24
+
25
+ # @return [String] HTTP Body of request
26
+ attr_reader :body
27
+
28
+ # @param [String] sender UUID of mongrel2 origin instance
29
+ # @param [String] conn_id Mongrel2 connection id sending this request
30
+ # @param [String] path HTTP Path of request
31
+ # @param [M2R::Headers] headers HTTP headers of request
32
+ # @param [String] body HTTP Body of request
33
+ def initialize(sender, conn_id, path, headers, body)
34
+ @http_headers, @mongrel_headers = split_headers(headers)
35
+ @sender = sender
36
+ @conn_id = conn_id
37
+ @path = path
38
+ @body = body
39
+ @data = MultiJson.load(@body) if json?
40
+ end
41
+
42
+ # Parse Mongrel2 request received via ZMQ message
43
+ #
44
+ # @param [String] msg Monrel2 Request message formatted according to rules
45
+ # of creating it described it m2 manual.
46
+ # @return [Request]
47
+ #
48
+ # @api public
49
+ def self.parse(msg)
50
+ sender, conn_id, path, rest = msg.split(' ', 4)
51
+
52
+ headers, rest = TNetstring.parse(rest)
53
+ body, _ = TNetstring.parse(rest)
54
+ headers = Headers.new MultiJson.load(headers)
55
+ self.new(sender, conn_id, path, headers, body)
56
+ end
57
+
58
+ # @return [M2R::Headers] HTTP headers
59
+ def headers
60
+ @http_headers
61
+ end
62
+
63
+ # @return [String] Mongrel2 pattern used to match this request
64
+ def pattern
65
+ @mongrel_headers['pattern']
66
+ end
67
+
68
+ # @return [String] HTTP method
69
+ def method
70
+ @mongrel_headers['method']
71
+ end
72
+
73
+ # @return [String] Request query string
74
+ def query
75
+ @mongrel_headers['query']
76
+ end
77
+
78
+ # return [String] URL scheme
79
+ def scheme
80
+ @mongrel_headers['url_scheme'] || mongrel17_scheme
81
+ end
82
+
83
+ # @return [true, false] Internal mongrel2 message to handler issued when
84
+ # message delivery is not possible because the client already
85
+ # disconnected and there is no connection with such {#conn_id}
86
+ def disconnect?
87
+ json? and @data['type'] == 'disconnect'
88
+ end
89
+
90
+ # @return [true, false] Information whether HTTP Connection should
91
+ # be closed after processing the request. Happens when HTTP/1.0
92
+ # or request has Connection=close header.
93
+ def close?
94
+ unsupported_version? or connection_close?
95
+ end
96
+
97
+ protected
98
+
99
+ def mongrel17_scheme
100
+ return 'https' if TRUE_STRINGS.include? env_https
101
+ return 'http'
102
+ end
103
+
104
+ def env_https
105
+ (ENV['HTTPS'] || "").downcase
106
+ end
107
+
108
+ def unsupported_version?
109
+ @http_headers['version'] != 'HTTP/1.1'
110
+ end
111
+
112
+ def connection_close?
113
+ @http_headers['connection'] == 'close'
114
+ end
115
+
116
+ def json?
117
+ method == 'JSON'
118
+ end
119
+
120
+ def split_headers(headers)
121
+ mongrel = Headers.new
122
+ mongrel_headers.each do |header|
123
+ next unless headers[header]
124
+ mongrel[header] = headers.delete(header)
125
+ end
126
+ return headers, mongrel
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,33 @@
1
+ module M2R
2
+ # Logic for typical Mongrel2 request with no fancy features such as
3
+ # async upload
4
+ #
5
+ # @private
6
+ module Base
7
+ MONGREL2_BASE_HEADERS = %w(pattern method path query url_scheme).map(&:freeze).freeze
8
+
9
+ # @return [StringIO] Request body encapsulated in IO compatible object
10
+ # @api public
11
+ def body_io
12
+ @body_io ||= begin
13
+ b = StringIO.new(body)
14
+ b.set_encoding(Encoding::BINARY) if b.respond_to?(:set_encoding)
15
+ b
16
+ end
17
+ end
18
+
19
+ # @return [nil] Free external resources such as files or sockets
20
+ # @api public
21
+ def free!
22
+ body_io.close
23
+ end
24
+
25
+ protected
26
+
27
+ def mongrel_headers
28
+ MONGREL2_BASE_HEADERS
29
+ end
30
+
31
+ end
32
+ end
33
+
@@ -0,0 +1,60 @@
1
+ module M2R
2
+ # Logic for Mongrel2 request delivered using async-upload feature
3
+ # Contains methods for recognizing such requests and reading them.
4
+ # @private
5
+ module Upload
6
+ # Headers related to async-upload feature
7
+ # @private
8
+ MONGREL2_UPLOAD_HEADERS = %w(x-mongrel2-upload-start x-mongrel2-upload-done).map(&:freeze).freeze
9
+
10
+ # @return [true,false] True if this is async-upload related request
11
+ # @api public
12
+ def upload?
13
+ !!@mongrel_headers['x-mongrel2-upload-start']
14
+ end
15
+
16
+ # @return [true,false] True if this is async-upload start notification
17
+ # @api public
18
+ def upload_start?
19
+ upload? and not upload_path
20
+ end
21
+
22
+ # @return [true,false] True if this is final async-upload request
23
+ # @api public
24
+ def upload_done?
25
+ upload? and upload_path
26
+ end
27
+
28
+ # @return [String] Relative path to file containing body of HTTP
29
+ # request.
30
+ # @api public
31
+ def upload_path
32
+ @mongrel_headers['x-mongrel2-upload-done']
33
+ end
34
+
35
+ # @return [File] Request body encapsulated in IO compatible object
36
+ # @api public
37
+ def body_io
38
+ return super unless upload_done?
39
+ @body_io ||= begin
40
+ f = File.open(upload_path, "r+b")
41
+ f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
42
+ f
43
+ end
44
+ end
45
+
46
+ # @return [nil] Free external resources such as files or sockets
47
+ # @api public
48
+ def free!
49
+ super
50
+ File.delete(body_io.path) if upload_done?
51
+ end
52
+
53
+ protected
54
+
55
+ def mongrel_headers
56
+ super + MONGREL2_UPLOAD_HEADERS
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,102 @@
1
+ require 'm2r'
2
+ require 'm2r/response/content_length'
3
+
4
+ module M2R
5
+ # Simplest possible abstraction layer over HTTP request
6
+ #
7
+ # @api public
8
+ class Response
9
+
10
+ # @private
11
+ VERSION = "HTTP/1.1".freeze
12
+
13
+ # @private
14
+ CRLF = "\r\n".freeze
15
+
16
+ # @private
17
+ STATUS_CODES = {
18
+ 100 => 'Continue',
19
+ 101 => 'Switching Protocols',
20
+ 102 => 'Processing',
21
+ 200 => 'OK',
22
+ 201 => 'Created',
23
+ 202 => 'Accepted',
24
+ 203 => 'Non-Authoritative Information',
25
+ 204 => 'No Content',
26
+ 205 => 'Reset Content',
27
+ 206 => 'Partial Content',
28
+ 207 => 'Multi-Status',
29
+ 226 => 'IM Used',
30
+ 300 => 'Multiple Choices',
31
+ 301 => 'Moved Permanently',
32
+ 302 => 'Found',
33
+ 303 => 'See Other',
34
+ 304 => 'Not Modified',
35
+ 305 => 'Use Proxy',
36
+ 306 => 'Reserved',
37
+ 307 => 'Temporary Redirect',
38
+ 400 => 'Bad Request',
39
+ 401 => 'Unauthorized',
40
+ 402 => 'Payment Required',
41
+ 403 => 'Forbidden',
42
+ 404 => 'Not Found',
43
+ 405 => 'Method Not Allowed',
44
+ 406 => 'Not Acceptable',
45
+ 407 => 'Proxy Authentication Required',
46
+ 408 => 'Request Timeout',
47
+ 409 => 'Conflict',
48
+ 410 => 'Gone',
49
+ 411 => 'Length Required',
50
+ 412 => 'Precondition Failed',
51
+ 413 => 'Request Entity Too Large',
52
+ 414 => 'Request-URI Too Long',
53
+ 415 => 'Unsupported Media Type',
54
+ 416 => 'Requested Range Not Satisfiable',
55
+ 417 => 'Expectation Failed',
56
+ 418 => "I'm a Teapot",
57
+ 422 => 'Unprocessable Entity',
58
+ 423 => 'Locked',
59
+ 424 => 'Failed Dependency',
60
+ 426 => 'Upgrade Required',
61
+ 500 => 'Internal Server Error',
62
+ 501 => 'Not Implemented',
63
+ 502 => 'Bad Gateway',
64
+ 503 => 'Service Unavailable',
65
+ 504 => 'Gateway Timeout',
66
+ }
67
+ STATUS_CODES.freeze
68
+
69
+ # @return [Fixnum] HTTP Status code
70
+ attr_reader :status
71
+
72
+ # @return [Hash, Headers] Collection of response HTTP Headers
73
+ attr_reader :headers
74
+
75
+ # @return [String] HTTP Body
76
+ attr_reader :body
77
+
78
+ # @return [String] HTTP Status code description
79
+ attr_reader :reason
80
+
81
+ # @param [Fixnum, #to_i] status HTTP status code
82
+ # @param [Hash] headers HTTP headers
83
+ # @param [String, nil] body HTTP body
84
+ def initialize(status, headers, body = nil)
85
+ @status = status.to_i
86
+ @headers = headers
87
+ @body = body || ""
88
+ @reason = STATUS_CODES[status.to_i]
89
+ end
90
+
91
+ # @return [String] HTTP Response
92
+ def to_s
93
+ response = "#{VERSION} #{status} #{reason}#{CRLF}"
94
+ unless headers.empty?
95
+ response << headers.map { |h, v| "#{h}: #{v}" }.join(CRLF) << CRLF
96
+ end
97
+ response << CRLF
98
+ response << body
99
+ response
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,18 @@
1
+ require 'm2r/response'
2
+
3
+ module M2R
4
+ class Response
5
+ # Adds Content-Length header based on body size
6
+ # This is mostly required when you use bare
7
+ # Response class without any framework on top of it.
8
+ # HTTP clients require such header when there is
9
+ # body in response. Otherwise they hang out.
10
+ #
11
+ # @api public
12
+ module ContentLength
13
+ def headers
14
+ super.merge('Content-Length' => body.bytesize)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module M2R
2
+ # m2r gem version
3
+ # @api public
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,33 @@
1
+ require 'rack/handler'
2
+ require 'm2r/rack_handler'
3
+ require 'securerandom'
4
+ require 'ostruct'
5
+
6
+ module Rack
7
+ module Handler
8
+ class Mongrel2
9
+ DEFAULT_OPTIONS = {
10
+ 'recv_addr' => 'tcp://127.0.0.1:9997',
11
+ 'send_addr' => 'tcp://127.0.0.1:9996',
12
+ 'sender_id' => SecureRandom.uuid
13
+ }
14
+
15
+ def self.run(app, options = {})
16
+ options = OpenStruct.new( DEFAULT_OPTIONS.merge(options) )
17
+ factory = M2R::ConnectionFactory.new(options.sender_id, options.recv_addr, options.send_addr)
18
+ adapter = M2R::RackHandler.new(app, factory)
19
+ adapter.listen
20
+ end
21
+
22
+ def self.valid_options
23
+ {
24
+ 'recv_addr=RECV_ADDR' => 'Receive address',
25
+ 'send_addr=SEND_ADDR' => 'Send address',
26
+ 'sender_id=UUID' => 'Sender UUID'
27
+ }
28
+ end
29
+ end
30
+
31
+ register :mongrel2, ::Rack::Handler::Mongrel2
32
+ end
33
+ end
@@ -1,69 +1,36 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
1
  # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/m2r/version', __FILE__)
5
3
 
6
- Gem::Specification.new do |s|
7
- s.name = %q{m2r}
8
- s.version = "0.0.3"
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Colin Curtin", "Pradeep Elankumaran", "Pawel Pacana", "Robert Pankowecki"]
6
+ gem.email = ["colin.t.curtin+m2r@gmail.com", "pawel.pacana+m2r@gmail.com", "robert.pankowecki+m2r@gmail.com"]
7
+ gem.description = "A Mongrel2 interface and handler library for JRuby, and hopefully other Ruby implementations in the future. Works with Rack, so it works with Rails! (Rails installation guide forthcoming)."
8
+ gem.homepage = "http://github.com/perplexes/m2r"
9
+ gem.summary = "Mongrel2 interface and handler library for JRuby."
10
+ gem.license = "MIT"
9
11
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Colin Curtin", "Pradeep Elankumaran"]
12
- s.date = %q{2010-10-23}
13
- s.description = %q{A Mongrel2 interface and handler library for JRuby, and hopefully other Ruby implementations in the future. Works with Rack, so it works with Rails! (Rails installation guide forthcoming.)}
14
- s.email = %q{colin.t.curtin+m2r@gmail.com}
15
- s.extra_rdoc_files = [
16
- "LICENSE",
17
- "README.md"
18
- ]
19
- s.files = [
20
- ".document",
21
- ".gitignore",
22
- "ISSUES",
23
- "LICENSE",
24
- "README.md",
25
- "Rakefile",
26
- "VERSION",
27
- "benchmarks/jruby",
28
- "example/http_0mq.rb",
29
- "example/lobster.ru",
30
- "example/rack_handler.rb",
31
- "lib/connection.rb",
32
- "lib/fiber_handler.rb",
33
- "lib/handler.rb",
34
- "lib/m2r.rb",
35
- "lib/request.rb",
36
- "m2r.gemspec",
37
- "test/helper.rb",
38
- "test/test_m2r.rb"
39
- ]
40
- s.homepage = %q{http://github.com/perplexes/m2r}
41
- s.rdoc_options = ["--charset=UTF-8"]
42
- s.require_paths = ["lib"]
43
- s.rubygems_version = %q{1.3.6}
44
- s.summary = %q{Mongrel2 interface and handler library for JRuby}
45
- s.test_files = [
46
- "test/helper.rb",
47
- "test/test_m2r.rb"
48
- ]
12
+ gem.files = Dir.glob("{lib,example,test}/**/*") + %w(LICENSE README.md Rakefile Gemfile m2r.gemspec)
13
+ gem.test_files = Dir.glob("test/**/*")
14
+ gem.extra_rdoc_files = ["LICENSE", "README.md" ]
49
15
 
50
- if s.respond_to? :specification_version then
51
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
52
- s.specification_version = 3
16
+ gem.name = "m2r"
17
+ gem.date = "2010-10-23"
18
+ gem.require_path = "lib"
19
+ gem.version = M2R::VERSION
53
20
 
54
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
55
- s.add_runtime_dependency(%q<ffi>, [">= 0"])
56
- s.add_runtime_dependency(%q<ffi-rzmq>, [">= 0"])
57
- s.add_runtime_dependency(%q<json>, [">= 0"])
58
- else
59
- s.add_dependency(%q<ffi>, [">= 0"])
60
- s.add_dependency(%q<ffi-rzmq>, [">= 0"])
61
- s.add_dependency(%q<json>, [">= 0"])
62
- end
63
- else
64
- s.add_dependency(%q<ffi>, [">= 0"])
65
- s.add_dependency(%q<ffi-rzmq>, [">= 0"])
66
- s.add_dependency(%q<json>, [">= 0"])
67
- end
68
- end
21
+ gem.add_dependency "ffi-rzmq", "~> 0.9.3"
22
+ gem.add_dependency "ffi", ">= 1.0.0"
23
+ gem.add_dependency "multi_json"
24
+ gem.add_dependency "tnetstring"
25
+
26
+ gem.add_development_dependency "rack"
27
+ gem.add_development_dependency "rake"
28
+ gem.add_development_dependency "minitest", "= 3.2.0"
29
+ gem.add_development_dependency "mocha", "~> 0.12.1"
30
+ gem.add_development_dependency "bbq", "= 0.0.4"
31
+ gem.add_development_dependency "capybara-mechanize", "= 0.3.0"
32
+ gem.add_development_dependency "activesupport", "~> 3.2.7"
33
+ gem.add_development_dependency "yard", "~> 0.8.2"
34
+ gem.add_development_dependency "kramdown", "~> 0.13.7"
69
35
 
36
+ end