hatetepe 0.6.0.pre.1 → 0.6.0.pre.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/.travis.yml CHANGED
@@ -1,4 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
- script: bundle exec rake ci:metrics
4
+ - 2.0.0
5
+ - jruby-19mode
6
+ - rbx
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: jruby-19mode
10
+ script: bundle exec rake -t ci:metrics spec:integration
data/.yardopts CHANGED
@@ -1 +1,2 @@
1
- - LICENSE
1
+ - LICENSE.txt
2
+ - examples/*
data/Gemfile CHANGED
@@ -9,5 +9,11 @@ gem 'fuubar', git: 'https://github.com/lgierth/fuubar.git',
9
9
  ref: 'static-percentage'
10
10
  gem 'awesome_print'
11
11
 
12
+ platform :rbx do
13
+ gem 'rubysl-fiber', '~> 2.0'
14
+ gem 'rubysl-weakref', '~> 2.0'
15
+ gem 'rubinius', '~> 2.0'
16
+ end
17
+
12
18
  # Added by devtools
13
19
  eval_gemfile 'Gemfile.devtools'
data/Gemfile.devtools CHANGED
@@ -4,6 +4,10 @@ group :development do
4
4
  gem 'rake', '~> 10.1.0'
5
5
  gem 'rspec', '~> 2.14.1'
6
6
  gem 'yard', '~> 0.8.7'
7
+
8
+ platform :rbx do
9
+ gem 'rubysl-singleton', '~> 2.0.0'
10
+ end
7
11
  end
8
12
 
9
13
  group :yard do
@@ -11,14 +15,13 @@ group :yard do
11
15
  end
12
16
 
13
17
  group :guard do
14
- gem 'guard', '~> 1.8.1'
15
- gem 'guard-bundler', '~> 1.0.0'
16
- gem 'guard-rspec', '~> 3.0.2'
17
- gem 'guard-rubocop', '~> 0.2.0'
18
- gem 'guard-mutant', '~> 0.0.1'
18
+ gem 'guard', '~> 2.2.4'
19
+ gem 'guard-bundler', '~> 2.0.0'
20
+ gem 'guard-rspec', '~> 4.0.4'
21
+ gem 'guard-rubocop', '~> 1.0.0'
19
22
 
20
23
  # file system change event handling
21
- gem 'listen', '~> 1.3.0'
24
+ gem 'listen', '~> 2.2.0'
22
25
  gem 'rb-fchange', '~> 0.0.6', require: false
23
26
  gem 'rb-fsevent', '~> 0.9.3', require: false
24
27
  gem 'rb-inotify', '~> 0.9.0', require: false
@@ -30,18 +33,27 @@ group :guard do
30
33
  end
31
34
 
32
35
  group :metrics do
33
- gem 'coveralls', '~> 0.6.7'
36
+ gem 'coveralls', '~> 0.7.0'
34
37
  gem 'flay', '~> 2.4.0'
35
- gem 'flog', '~> 4.1.1'
38
+ gem 'flog', '~> 4.2.0'
36
39
  gem 'reek', '~> 1.3.2'
37
- gem 'rubocop', '~> 0.12.0'
38
- gem 'simplecov', '~> 0.7.1'
40
+ gem 'rubocop', '~> 0.15.0'
41
+ gem 'simplecov', '~> 0.8.2'
39
42
  gem 'yardstick', '~> 0.9.7', git: 'https://github.com/dkubb/yardstick.git'
40
43
 
41
44
  platforms :ruby_19, :ruby_20 do
42
- gem 'mutant', git: 'https://github.com/mbj/mutant.git'
45
+ gem 'mutant', '~> 0.3.0.rc3', git: 'https://github.com/mbj/mutant.git'
46
+ gem 'unparser', '~> 0.1.5', git: 'https://github.com/mbj/unparser.git'
43
47
  gem 'yard-spellcheck', '~> 0.1.5'
44
48
  end
49
+
50
+ platform :rbx do
51
+ gem 'json', '~> 1.8.1'
52
+ gem 'racc', '~> 1.4.10'
53
+ gem 'rubysl-logger', '~> 2.0.0'
54
+ gem 'rubysl-open-uri', '~> 2.0.0'
55
+ gem 'rubysl-prettyprint', '~> 2.0.2'
56
+ end
45
57
  end
46
58
 
47
59
  group :benchmarks do
data/Procfile ADDED
@@ -0,0 +1 @@
1
+ web: env PORT=$PORT ADDRESS=0.0.0.0 bundle exec ruby examples/echo_server.rb
data/README.md CHANGED
@@ -1,9 +1,19 @@
1
- # Hatetepe, the HTTP toolkit [![Build Status](https://travis-ci.org/lgierth/hatetepe.png?branch=master)](https://travis-ci.org/lgierth/hatetepe) [![Code Climate](https://codeclimate.com/github/lgierth/hatetepe.png)](https://codeclimate.com/github/lgierth/hatetepe)
1
+ # The HTTP toolkit [![Build Status](https://travis-ci.org/lgierth/hatetepe.png?branch=master)](https://travis-ci.org/lgierth/hatetepe) [![Code Climate](https://codeclimate.com/github/lgierth/hatetepe.png)](https://codeclimate.com/github/lgierth/hatetepe) [![Coverage Status](https://coveralls.io/repos/lgierth/hatetepe/badge.png?branch=master)](https://coveralls.io/r/lgierth/hatetepe?branch=master)
2
2
 
3
- General purpose toolkit for HTTP clients and servers.
3
+ Hatetepe is a Ruby toolkit for building HTTP clients and servers,
4
+ as well as compositions of them.
4
5
 
5
- Note: The `master` branch contains the latest development effort. Look at the
6
- `0.5.x` branch for stable releases.
6
+ - \#1 feature: readable, high-quality, extendable code with 61.11% mutation coverage (wip)
7
+ - \#2 feature: sophisticated request and response streaming
8
+ - \#3 feature: compatible with Rack, Faraday, and Webmachine for Ruby (todo)
9
+ - \#4 feature: concurrenct or non-concurrent
10
+ - Non-concurrent using one-off EventMachine reactor (clients only)
11
+ - Evented using EventMachine
12
+ - Synchronously evented using EventMachine and Fibers
13
+ - Threaded using Celluloid (todo)
14
+
15
+ *Note:* The `master` branch contains the latest development effort. Look at the
16
+ `0.5.x` branch for stable, but very old releases.
7
17
 
8
18
  ## Installation
9
19
 
@@ -21,30 +31,29 @@ Or install it yourself as:
21
31
 
22
32
  ## Usage
23
33
 
24
- TODO: Write usage instructions here
34
+ Code examples
35
+
36
+ - [Getting started ](http://rubydoc.info/github/lgierth/hatetepe/master/file/examples/getting_started.rb)
37
+ - [HTTP echo server](http://rubydoc.info/github/lgierth/hatetepe/master/file/examples/echo_server.rb)
38
+
39
+ API Reference
40
+
41
+ - [Hatetepe::Client ](http://rubydoc.info/github/lgierth/hatetepe/master/Hatetepe/Client)
42
+ - [Hatetepe::Server ](http://rubydoc.info/github/lgierth/hatetepe/master/Hatetepe/Server)
43
+ - [Hatetepe::Response ](http://rubydoc.info/github/lgierth/hatetepe/master/Hatetepe/Request)
44
+ - [Hatetepe::Request ](http://rubydoc.info/github/lgierth/hatetepe/master/Hatetepe/Response)
45
+ - [Hatetepe::Body ](http://rubydoc.info/github/lgierth/hatetepe/master/Hatetepe/Body)
46
+ - [Hatetepe::Promise ](http://rubydoc.info/github/lgierth/hatetepe/master/Hatetepe/Promise)
47
+ - [Hatetepe::Serializer](http://rubydoc.info/github/lgierth/hatetepe/master/Hatetepe/Serializer)
25
48
 
26
49
  ## To do
27
50
 
28
- - Next
29
- - Error handling
30
- - Unit tests
31
- - Method/Status semantics (GET/HEAD/204/etc.)
32
- - Host header
33
- - Date header
34
- - Documentation
35
- - Future
36
- - SSL support
37
- - Webmachine adapter
38
- - Request/Response pass-through
39
- - Graceful shutdown
40
- - Celluloid::IO backend
41
- - Circuit breaker
42
- - Nice to have
43
- - Rack handler
44
- - Faraday adapter
45
- - Following redirects
46
- - Proxy support
47
- - Client handling multipart responses
51
+ Here: https://trello.com/b/OoxEq1ze/hatetepe
52
+
53
+ ## License
54
+
55
+ Hatetepe is licensed under the [MIT License](http://opensource.org/licenses/MIT).
56
+ See LICENSE.txt for details.
48
57
 
49
58
  ## Contributing
50
59
 
data/config/devtools.yml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- unit_test_timeout: 0.1
2
+ unit_test_timeout: 0.0
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 8
3
- total_score: 147
3
+ total_score: 164
data/config/flog.yml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 9.6
2
+ threshold: 10.0
data/config/reek.yml CHANGED
@@ -34,6 +34,8 @@ LongParameterList:
34
34
  overrides:
35
35
  initialize:
36
36
  max_params: 4
37
+ setup:
38
+ max_params: 3
37
39
  LongYieldList:
38
40
  enabled: true
39
41
  exclude: []
@@ -57,7 +59,7 @@ TooManyInstanceVariables:
57
59
  TooManyMethods:
58
60
  enabled: true
59
61
  exclude: []
60
- max_methods: 10
62
+ max_methods: 11
61
63
  TooManyStatements:
62
64
  enabled: true
63
65
  exclude:
@@ -95,6 +97,8 @@ UncommunicativeVariableName:
95
97
  - !ruby/regexp /[0-9]$/
96
98
  - !ruby/regexp /[A-Z]/
97
99
  accept:
100
+ - k
101
+ - v
98
102
  - a
99
103
  - e
100
104
  - _
data/config/rubocop.yml CHANGED
@@ -24,14 +24,8 @@ CollectionMethods:
24
24
  find: 'detect'
25
25
  find_all: 'select'
26
26
 
27
- # Do not force public/protected/private keyword to be indented at the same
28
- # level as the def keyword. My personal preference is to outdent these keywords
29
- # because I think when scanning code it makes it easier to identify the
30
- # sections of code and visually separate them. When the keyword is at the same
31
- # level I think it sort of blends in with the def keywords and makes it harder
32
- # to scan the code and see where the sections are.
33
- AccessControl:
34
- Enabled: false
27
+ AccessModifierIndentation:
28
+ EnforcedStyle: indent
35
29
 
36
30
  # Limit line length
37
31
  LineLength:
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hatetepe'
4
+
5
+ class EchoServer
6
+ def serve(request, served)
7
+ response = streaming_response
8
+ served.fulfill(response)
9
+
10
+ serialize(request, response)
11
+ request.closed { response.close }
12
+ end
13
+
14
+ private
15
+
16
+ def streaming_response
17
+ Hatetepe::Response.new(200, { 'Content-Type' => 'text/plain' },
18
+ Hatetepe::Body.new)
19
+ end
20
+
21
+ def serialize(request, response)
22
+ writer = response.body.closed.method(:progress)
23
+ serializer = Hatetepe::Serializer.new(request, writer)
24
+ serializer.serialize
25
+ end
26
+
27
+ def writer(response)
28
+ proc { |data| response.body.closed.progress(data) }
29
+ end
30
+ end
31
+
32
+ Hatetepe.run do
33
+ Hatetepe::Server.start(address: ENV.fetch('ADDRESS', '127.0.0.1'),
34
+ port: ENV.fetch('PORT', 3000).to_i,
35
+ handlers: [Hatetepe::Server::KeepAlive, EchoServer])
36
+
37
+ terminated = Hatetepe::Promise.new
38
+ Signal.trap(:INT) { terminated.fulfill }
39
+ Signal.trap(:TERM) { terminated.fulfill }
40
+ terminated.sync
41
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hatetepe'
4
+
5
+ class HelloServer
6
+ def serve(request, served)
7
+ p request.http_method # => :get
8
+ p request.uri # => "/"
9
+ p request.headers # => {"Host"=>"127.0.0.1:3000",
10
+ # "Content-Length"=>"0"}
11
+ p request.body.to_s # => ""
12
+
13
+ served.fulfill(response)
14
+ end
15
+
16
+ def response
17
+ Hatetepe::Response.new(200, { 'Content-Type' => 'text/plain' }, 'hello')
18
+ end
19
+ end
20
+
21
+ Hatetepe.run do
22
+ Hatetepe::Server.start(address: '127.0.0.1', port: 3000,
23
+ handlers: [HelloServer])
24
+
25
+ client = Hatetepe::Client.new(address: '127.0.0.1', port: 3000)
26
+
27
+ response = client.request(:get, '/')
28
+
29
+ p response.status # => 200
30
+ p response.status_name # => "OK"
31
+ p response.headers # => {"Content-Type"=>"text/plain",
32
+ # "Content-Length"=>"5"}
33
+ p response.body.to_s # => "hello"
34
+ end
data/hatetepe.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_dependency "eventmachine"
23
23
  spec.add_dependency "http_parser.rb"
24
- spec.add_dependency "promise.rb"
24
+ spec.add_dependency "promise.rb", "~> 0.3.0"
25
25
 
26
26
  spec.add_development_dependency "rspec"
27
27
  end
data/lib/hatetepe/body.rb CHANGED
@@ -15,6 +15,7 @@ module Hatetepe
15
15
  end
16
16
  end
17
17
 
18
+ # TODO: eliminate string param
18
19
  def initialize(string = '')
19
20
  @io = StringIO.new(string)
20
21
 
@@ -49,6 +50,10 @@ module Hatetepe
49
50
  @io.string
50
51
  end
51
52
 
53
+ def string
54
+ @io.string
55
+ end
56
+
52
57
  private
53
58
 
54
59
  def optimized_read?(length)
@@ -2,10 +2,6 @@
2
2
 
3
3
  module Hatetepe
4
4
  class Client::KeepAlive
5
- def initialize(*_)
6
- @previous = nil
7
- end
8
-
9
5
  def perform(request)
10
6
  @previous, previous = request, @previous
11
7
  previous.closed! if previous
@@ -3,17 +3,12 @@
3
3
  module Hatetepe
4
4
  # @see EM.heartbeat_interval
5
5
  class Client::Timeouts
6
- def initialize(config, _, connection)
6
+ def setup(config, _, connection)
7
7
  @config = config
8
8
  @connection = connection
9
9
 
10
- @config[:timeout] ||= 2.0
11
- @config[:connect_timeout] ||= 2.0
12
- end
13
-
14
- def setup
15
- @connection.comm_inactivity_timeout = @config[:timeout]
16
- @connection.pending_connect_timeout = @config[:connect_timeout]
10
+ @connection.comm_inactivity_timeout = @config[:timeout] ||= 2.0
11
+ @connection.pending_connect_timeout = @config[:connect_timeout] ||= 2.0
17
12
  end
18
13
  end
19
14
  end
@@ -2,6 +2,11 @@
2
2
 
3
3
  module Hatetepe
4
4
  class Client
5
+ USER_AGENT = 'User-Agent'.freeze
6
+ USER_AGENT_VALUE = "hatetepe/#{Hatetepe::VERSION}".freeze
7
+ HOST = 'Host'.freeze
8
+ HOST_VALUE = '%s:%d'.freeze
9
+
5
10
  include Support::Handlers
6
11
  include Connection::Status
7
12
 
@@ -13,11 +18,11 @@ module Hatetepe
13
18
 
14
19
  setup_connection
15
20
  setup_handlers
16
- notify_handlers(:setup)
21
+ notify_handlers(:setup, @config, self, @connection)
17
22
  end
18
23
 
19
- def request(http_method, uri)
20
- request = Request.new(http_method, uri)
24
+ def request(*args)
25
+ request = Request.new(*args)
21
26
  perform(request).sync
22
27
  end
23
28
 
@@ -25,7 +30,9 @@ module Hatetepe
25
30
  served = Promise.new
26
31
  @requests << [request, served]
27
32
 
33
+ add_extra_headers(request)
28
34
  Fiber.new { perform!(request) }.resume
35
+
29
36
  served
30
37
  end
31
38
 
@@ -33,13 +40,12 @@ module Hatetepe
33
40
  request, served = correlate
34
41
 
35
42
  served.fulfill(response)
36
- response.closed { finish(request) }
37
-
38
43
  notify_handlers(:receive, request, response)
44
+ response.closed { finish(request) }
39
45
  end
40
46
 
41
47
  def finish(request)
42
- @requests.delete_if { |(req, _)| req == request }
48
+ @requests.delete_if { |(req)| req == request }
43
49
  notify_handlers(:finish, request)
44
50
  end
45
51
 
@@ -48,7 +54,7 @@ module Hatetepe
48
54
  end
49
55
 
50
56
  def teardown(reason)
51
- @requests.each do |(_, served)|
57
+ @requests.each do |_, served|
52
58
  served.reject(reason)
53
59
  if (response = served.value)
54
60
  response.reject_closed(reason)
@@ -58,14 +64,19 @@ module Hatetepe
58
64
 
59
65
  private
60
66
 
67
+ def add_extra_headers(request)
68
+ host = sprintf(HOST_VALUE, config[:address], config[:port])
69
+ request.add_extra_headers(USER_AGENT => USER_AGENT_VALUE, HOST => host)
70
+ end
71
+
61
72
  def perform!(request)
62
73
  notify_handlers(:perform, request)
63
74
  @connection.serialize(request)
64
75
  end
65
76
 
66
77
  def correlate
67
- tuple = @requests.detect { |(_, served)| served.pending? }
68
- raise ClientError, 'Unable to correlate with request' unless tuple
78
+ tuple = @requests.detect { |_, served| served.pending? }
79
+ fail ClientError, 'Unable to correlate with request' unless tuple
69
80
  tuple
70
81
  end
71
82
 
@@ -17,11 +17,13 @@ module Hatetepe
17
17
 
18
18
  def serialize(message)
19
19
  serializer = Serializer.new(message, method(:send_data))
20
+ serializer.setup_body
20
21
  serializer.serialize
21
22
  end
22
23
 
23
24
  def receive_data(data)
24
- # ap [__id__, :receive, data]
25
+ # p [__id__, :receive, data]
26
+ # p data
25
27
  @parser << data
26
28
  # rescue => error
27
29
  # $stderr.puts(error.to_s)
@@ -30,7 +32,8 @@ module Hatetepe
30
32
  end
31
33
 
32
34
  # def send_data(data)
33
- # ap [__id__, :send, data]
35
+ # # p [__id__, :send, data]
36
+ # p data
34
37
  # super
35
38
  # end
36
39
 
@@ -4,48 +4,22 @@ module Hatetepe
4
4
  class Promise < ::Promise
5
5
  include ::Promise::Progress
6
6
 
7
- def fulfill(value = nil)
8
- super(value)
9
- end
10
-
11
- def reject(reason = nil)
12
- super(reason)
13
- end
14
-
15
- def sync
16
- if fulfilled?
17
- return value
18
- elsif rejected?
19
- return reason
20
- end
7
+ private
21
8
 
22
- sync!
9
+ def defer
10
+ EM.next_tick { yield }
23
11
  end
24
12
 
25
- def sync!
13
+ def wait
26
14
  fiber = Fiber.current
27
15
  resume = proc do |arg|
28
- EM.next_tick { fiber.resume(arg) }
29
- # fiber.resume(arg)
16
+ defer { fiber.resume(arg) }
30
17
  end
31
18
 
32
19
  self.then(resume, resume)
33
20
  Fiber.yield
34
21
  end
35
22
 
36
- private
37
-
38
- def defer(callback, arg)
39
- EM.next_tick { safe_dispatch(callback, arg) }
40
- # safe_dispatch(callback, arg)
41
- end
42
-
43
- def safe_dispatch(callback, arg)
44
- callback.dispatch(arg)
45
- # rescue => error
46
- # ...
47
- end
48
-
49
23
  module Attribute
50
24
  def self.included(klass)
51
25
  klass.extend(ClassMethods)
@@ -16,6 +16,30 @@ module Hatetepe
16
16
  STATUS_NAMES[status.to_i] || UNKNOWN_STATUS
17
17
  end
18
18
 
19
+ def status_class
20
+ status / 100
21
+ end
22
+
23
+ def informational?
24
+ status_class == 1
25
+ end
26
+
27
+ def successful?
28
+ status_class == 2
29
+ end
30
+
31
+ def redirection?
32
+ status_class == 3
33
+ end
34
+
35
+ def client_error?
36
+ status_class == 4
37
+ end
38
+
39
+ def server_error?
40
+ status_class == 5
41
+ end
42
+
19
43
  UNKNOWN_STATUS = 'Unknown Status'.freeze
20
44
 
21
45
  STATUS_NAMES =
@@ -8,15 +8,6 @@ module Hatetepe
8
8
  CRLF = "\r\n".freeze
9
9
  CONTENT_LENGTH = 'Content-Length'.freeze
10
10
 
11
- def setup_body
12
- if body.closed.pending?
13
- setup_chunked_encoding
14
- setup_chunked_streaming
15
- else
16
- setup_identity_encoding
17
- end
18
- end
19
-
20
11
  def setup_chunked_encoding
21
12
  headers.delete(CONTENT_LENGTH)
22
13
  headers[TRANSFER_ENCODING] = CHUNKED
@@ -33,7 +24,9 @@ module Hatetepe
33
24
  end
34
25
 
35
26
  def write_body
36
- if headers.key?(CONTENT_LENGTH)
27
+ if headers.key?(TRANSFER_ENCODING)
28
+ write_chunk(body.string)
29
+ else
37
30
  write(body.to_s)
38
31
  end
39
32
  end
@@ -43,7 +36,7 @@ module Hatetepe
43
36
  end
44
37
 
45
38
  def write_chunk!(chunk)
46
- write(chunk.bytesize, CRLF, chunk, CRLF)
39
+ write(chunk.bytesize.to_s(16), CRLF, chunk, CRLF)
47
40
  end
48
41
  end
49
42
  end
@@ -21,18 +21,25 @@ module Hatetepe
21
21
  end
22
22
 
23
23
  def serialize
24
- setup_body
25
-
26
24
  write(banana_line)
27
25
  write(header_block)
28
26
 
29
27
  write_body
30
28
  end
31
29
 
30
+ def setup_body
31
+ if body.closed.pending?
32
+ setup_chunked_encoding
33
+ setup_chunked_streaming
34
+ else
35
+ setup_identity_encoding
36
+ end
37
+ end
38
+
32
39
  private
33
40
 
34
41
  def write(*data)
35
- data.each { |str| @writer.call(str) }
42
+ @writer.call(data.join(''))
36
43
  end
37
44
 
38
45
  def banana_line
@@ -54,7 +61,15 @@ module Hatetepe
54
61
  end
55
62
 
56
63
  def header_block
57
- headers.reduce('') { |a, e| a << sprintf(HEADER, *e) } + CRLF
64
+ headers.reduce('') { |a, e| a << header_line(*e) } + CRLF
65
+ end
66
+
67
+ def header_line(key, value)
68
+ if value.to_s.empty?
69
+ ''
70
+ else
71
+ sprintf(HEADER, key, value)
72
+ end
58
73
  end
59
74
  end
60
75
  end
@@ -6,7 +6,7 @@ module Hatetepe
6
6
  CLOSE = 'close'.freeze
7
7
  KEEP_ALIVE = 'keep-alive'.freeze
8
8
 
9
- def initialize(_, server, _)
9
+ def setup(_, server, _)
10
10
  @server = server
11
11
  @requests, @previous = {}, nil
12
12
  end
@@ -3,15 +3,11 @@
3
3
  module Hatetepe
4
4
  # @see EM.heartbeat_interval
5
5
  class Server::Timeouts
6
- def initialize(config, _, connection)
6
+ def setup(config, _, connection)
7
7
  @config = config
8
8
  @connection = connection
9
9
 
10
- @config[:timeout] ||= 2.0
11
- end
12
-
13
- def setup
14
- @connection.comm_inactivity_timeout = @config[:timeout]
10
+ @connection.comm_inactivity_timeout = @config[:timeout] ||= 2.0
15
11
  end
16
12
  end
17
13
  end
@@ -2,6 +2,10 @@
2
2
 
3
3
  module Hatetepe
4
4
  class Server
5
+ SERVER = 'Server'.freeze
6
+ SERVER_VALUE = "hatetepe/#{Hatetepe::VERSION}".freeze
7
+ DATE = 'Date'.freeze
8
+
5
9
  def self.start(config)
6
10
  callback = proc { |connection| new(config, connection) }
7
11
  EM.start_server(config[:address], config[:port],
@@ -18,7 +22,7 @@ module Hatetepe
18
22
 
19
23
  setup_connection(connection)
20
24
  setup_handlers
21
- notify_handlers(:setup)
25
+ notify_handlers(:setup, @config, self, @connection)
22
26
  end
23
27
 
24
28
  def serve(request)
@@ -30,8 +34,8 @@ module Hatetepe
30
34
 
31
35
  def respond(request, response)
32
36
  fiber = Fiber.new do
33
- notify_handlers(:respond, request, response)
34
- @connection.serialize(response)
37
+ add_extra_headers(response)
38
+ respond!(request, response)
35
39
  response.closed { finish(request) }
36
40
  end
37
41
  fiber.resume
@@ -47,13 +51,19 @@ module Hatetepe
47
51
 
48
52
  private
49
53
 
54
+ def add_extra_headers(response)
55
+ date = Time.now.httpdate
56
+ response.add_extra_headers(SERVER => SERVER_VALUE, DATE => date)
57
+ end
58
+
59
+ def respond!(request, response)
60
+ notify_handlers(:respond, request, response)
61
+ @connection.serialize(response)
62
+ end
63
+
50
64
  def setup_connection(connection)
51
65
  @connection = connection
52
66
  @connection.parse(method(:serve))
53
- @connection.closed.then(method(:shutdown))
54
- end
55
-
56
- def shutdown(_)
57
67
  end
58
68
  end
59
69
  end
@@ -4,9 +4,7 @@ module Hatetepe
4
4
  module Support
5
5
  module Handlers
6
6
  def setup_handlers
7
- @handlers = @config[:handlers].map do |klass|
8
- klass.new(@config, self, @connection)
9
- end
7
+ @handlers = (@config[:handlers] || []).map(&:new)
10
8
  end
11
9
 
12
10
  def notify_handlers(hook, *args)
@@ -23,6 +23,10 @@ module Hatetepe
23
23
  body.closed.reject(reason)
24
24
  end
25
25
 
26
+ def add_extra_headers(extra)
27
+ extra.each { |k, v| headers.key?(k) || headers[k] = v }
28
+ end
29
+
26
30
  def self.build(parser)
27
31
  if parser.http_method
28
32
  build_request(parser)
@@ -31,7 +35,7 @@ module Hatetepe
31
35
  end
32
36
  end
33
37
 
34
- private
38
+ private
35
39
 
36
40
  def self.build_request(parser)
37
41
  request = Request.new(http_method_from(parser),
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Hatetepe
4
- VERSION = '0.6.0.pre.1'
4
+ VERSION = '0.6.0.pre.2'
5
5
  end
data/lib/hatetepe.rb CHANGED
@@ -4,6 +4,7 @@ require 'eventmachine'
4
4
  require 'fiber'
5
5
  require 'promise'
6
6
  require 'http/parser'
7
+ require 'time'
7
8
 
8
9
  require 'hatetepe/version'
9
10
 
@@ -27,3 +28,19 @@ require 'hatetepe/client/timeouts'
27
28
  require 'hatetepe/server'
28
29
  require 'hatetepe/server/keep_alive'
29
30
  require 'hatetepe/server/timeouts'
31
+
32
+ module Hatetepe
33
+ def self.run
34
+ EM.run do
35
+ Fiber.new do
36
+ yield
37
+ stop
38
+ end.resume
39
+ end
40
+ end
41
+
42
+ def self.stop
43
+ EM.stop
44
+ EM.next_tick {}
45
+ end
46
+ end
@@ -55,7 +55,7 @@ describe do
55
55
 
56
56
  describe 'and Connection: keep-alive' do
57
57
  let(:requests) do
58
- [closed_request(:get, '/', { 'Connection' => 'keep-alive' })
58
+ [closed_request(:get, '/', 'Connection' => 'keep-alive')
59
59
  .tap { |r| r.http_version = 1.0 }]
60
60
  end
61
61
 
data/spec/spec_helper.rb CHANGED
@@ -28,20 +28,21 @@ RSpec.configure do |config|
28
28
  config.include(SpecHelper)
29
29
 
30
30
  config.around do |example|
31
- EM.run do
32
- fiber = Fiber.new do
33
- example.call
34
- EM.stop
35
- EM.next_tick {}
36
- end
31
+ stop = proc do
32
+ EM.stop
33
+ EM.next_tick {}
34
+ end
37
35
 
36
+ EM.run do
38
37
  EM.add_timer(0.5) do
39
- EM.stop
40
- EM.next_tick {}
41
- raise 'Example timed out'
38
+ stop.call
39
+ fail 'Example timed out'
42
40
  end
43
41
 
44
- fiber.resume
42
+ Fiber.new do
43
+ example.call
44
+ stop.call
45
+ end.resume
45
46
  end
46
47
  end
47
48
  end
@@ -1,9 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  class SpecHandler
4
- def initialize(config, server, connection)
5
- end
6
-
7
4
  def serve(request, served)
8
5
  served.fulfill(response_for(request))
9
6
  end
@@ -53,10 +53,7 @@ module SpecHelper
53
53
 
54
54
  def server_client_pair(server_config, client_config, interceptor)
55
55
  server = nil
56
- inspect_server(server_config) do |_, s, _|
57
- server = s
58
- interceptor
59
- end
56
+ inspect_server(server_config, interceptor) { |_, s, _| server = s }
60
57
 
61
58
  config = { address: localhost, port: random_port }
62
59
  Hatetepe::Server.start(config.merge(server_config))
@@ -66,9 +63,11 @@ module SpecHelper
66
63
  [server, client]
67
64
  end
68
65
 
69
- def inspect_server(server_config, &block)
70
- interceptor_class = double('interceptor_class')
71
- allow(interceptor_class).to receive(:new, &block)
72
- server_config[:handlers].unshift(interceptor_class)
66
+ def inspect_server(server_config, interceptor, &block)
67
+ klass = double
68
+ allow(klass).to receive(:new) { interceptor }
69
+ allow(interceptor).to receive(:setup, &block)
70
+
71
+ server_config[:handlers].unshift(klass)
73
72
  end
74
73
  end
@@ -15,7 +15,9 @@ describe Hatetepe::Client do
15
15
  close: nil)
16
16
  end
17
17
  let(:handler_class) { double('handler_class', new: handler) }
18
- let(:handler) { double('handler', setup: nil, receive: nil) }
18
+ let(:handler) do
19
+ double('handler', setup: nil, receive: nil, finish: nil)
20
+ end
19
21
 
20
22
  before do
21
23
  allow(EM).to receive(:connect) { connection }
@@ -37,9 +39,8 @@ describe Hatetepe::Client do
37
39
  end
38
40
 
39
41
  specify do
40
- expect(handler_class).to have_received(:new)
42
+ expect(handler).to have_received(:setup)
41
43
  .with(config, subject, connection)
42
- expect(handler).to have_received(:setup)
43
44
  end
44
45
  end
45
46
 
@@ -66,7 +67,7 @@ describe Hatetepe::Client do
66
67
  end
67
68
 
68
69
  describe '#perform' do
69
- let(:request) { double('request') }
70
+ let(:request) { double('request', add_extra_headers: nil) }
70
71
 
71
72
  before do
72
73
  @fibers = []
@@ -82,6 +83,10 @@ describe Hatetepe::Client do
82
83
 
83
84
  expect(@fibers).not_to include(Fiber.current)
84
85
  expect(@fibers.uniq).to be_one
86
+
87
+ expect(request).to have_received(:add_extra_headers)
88
+ .with('User-Agent' => "hatetepe/#{Hatetepe::VERSION}",
89
+ 'Host' => "#{config[:address]}:#{config[:port]}")
85
90
  end
86
91
  end
87
92
 
@@ -94,19 +99,29 @@ describe Hatetepe::Client do
94
99
  end
95
100
 
96
101
  describe '#receive' do
97
- let(:request) { open_request }
98
- let(:response) { open_response }
102
+ let(:request) { open_request }
103
+ let(:other_request) { open_request }
104
+ let(:response) { open_response }
105
+ let(:other_response) { open_response }
99
106
 
100
107
  describe 'with outstanding request' do
101
- let!(:served) { client.perform(request) }
108
+ let!(:served) { client.perform(request) }
109
+ let!(:other_served) { client.perform(other_request) }
102
110
 
103
- subject! { client.receive(response) }
111
+ subject! do
112
+ client.receive(response)
113
+ client.receive(other_response)
114
+ end
104
115
 
105
116
  it 'correlates response with request, and notifies handlers' do
106
117
  expect(served).to be_fulfilled
107
118
  expect(served.value).to be(response)
119
+ expect(other_served).to be_fulfilled
120
+ expect(other_served.value).to be(other_response)
108
121
 
109
122
  expect(handler).to have_received(:receive).with(request, response)
123
+ expect(handler)
124
+ .to have_received(:receive).with(other_request, other_response)
110
125
  end
111
126
  end
112
127
 
@@ -118,6 +133,27 @@ describe Hatetepe::Client do
118
133
  end
119
134
  end
120
135
 
136
+ describe 'wiring' do
137
+ let(:request) { open_request }
138
+ let(:response) { open_response }
139
+
140
+ before do
141
+ client.perform(request)
142
+
143
+ allow(client).to receive(:finish)
144
+ end
145
+
146
+ subject! do
147
+ client.receive(response)
148
+ response.close
149
+ tick
150
+ end
151
+
152
+ it 'wires up #finish' do
153
+ client.should have_received(:finish).with(request)
154
+ end
155
+ end
156
+
121
157
  [:fulfill, :reject].each do |action|
122
158
  describe "after #{action}ed response" do
123
159
  let(:request) { WeakRef.new(closed_request) }
@@ -146,6 +182,30 @@ describe Hatetepe::Client do
146
182
  end
147
183
  end
148
184
 
185
+ describe '#finish' do
186
+ let(:request) { open_request }
187
+ let(:other_request) { open_request }
188
+ let(:response) { open_response }
189
+
190
+ before do
191
+ client.perform(request)
192
+ client.perform(other_request)
193
+ end
194
+
195
+ subject! do
196
+ client.finish(request)
197
+ client.receive(response)
198
+ end
199
+
200
+ it 'removes the request' do
201
+ expect(handler).to have_received(:receive).with(other_request, response)
202
+ end
203
+
204
+ it 'notifies the handlers' do
205
+ expect(handler).to have_received(:finish).with(request)
206
+ end
207
+ end
208
+
149
209
  describe '#teardown' do
150
210
  let(:requests) { [closed_request, closed_request] }
151
211
  let(:responses) { [open_response] }
@@ -6,7 +6,7 @@ describe Hatetepe::Connection::EventMachine do
6
6
  let(:connection) { described_class.allocate }
7
7
  let(:callback) { double('callback', call: nil) }
8
8
  let(:parser) { double('parser', :<< => nil) }
9
- let(:serializer) { double('serializer', serialize: nil) }
9
+ let(:serializer) { double('serializer', setup_body: nil, serialize: nil) }
10
10
  let(:message) { open_request }
11
11
 
12
12
  before do
@@ -51,7 +51,8 @@ describe Hatetepe::Connection::EventMachine do
51
51
 
52
52
  it 'feeds a serializer' do
53
53
  expect(Hatetepe::Serializer).to have_received(:new).with(message, writer)
54
- expect(serializer).to have_received(:serialize)
54
+ expect(serializer).to have_received(:setup_body)
55
+ expect(serializer).to have_received(:serialize)
55
56
  end
56
57
  end
57
58
 
@@ -7,7 +7,7 @@ describe Hatetepe::Server::KeepAlive do
7
7
  let(:server) { double('server') }
8
8
  let(:connection) { double('connection') }
9
9
 
10
- let(:object) { described_class.new(config, server, connection) }
10
+ let(:object) { described_class.new }
11
11
  let(:request) do
12
12
  double('request', http_version: http_version,
13
13
  headers: { 'Connection' => header })
@@ -15,6 +15,8 @@ describe Hatetepe::Server::KeepAlive do
15
15
  let(:served) { double('served', value: response) }
16
16
  let(:response) { double('response', headers: { 'Connection' => header }) }
17
17
 
18
+ before { object.setup(config, server, connection) }
19
+
18
20
  describe '#close_connection?' do
19
21
  before { object.serve(request, served) }
20
22
 
@@ -15,7 +15,9 @@ describe Hatetepe::Server do
15
15
  close: nil)
16
16
  end
17
17
  let(:handler_class) { double('handler_class', new: handler) }
18
- let(:handler) { double('handler', setup: nil, respond: nil) }
18
+ let(:handler) do
19
+ double('handler', setup: nil, respond: nil, finish: nil)
20
+ end
19
21
 
20
22
  describe '.start' do
21
23
  specify { pending }
@@ -27,16 +29,11 @@ describe Hatetepe::Server do
27
29
  its(:config) { should be(config) }
28
30
 
29
31
  it 'sets up connection' do
30
- expect(connection).to have_received(:parse)
31
- .with(server.method(:serve))
32
- expect(connection.closed).to have_received(:then)
33
- .with(server.method(:shutdown))
32
+ expect(connection).to have_received(:parse).with(server.method(:serve))
34
33
  end
35
34
 
36
35
  it 'sets up handlers' do
37
- expect(handler_class).to have_received(:new)
38
- .with(config, server, connection)
39
- expect(handler).to have_received(:setup)
36
+ expect(handler).to have_received(:setup).with(config, server, connection)
40
37
  end
41
38
  end
42
39
 
@@ -68,8 +65,9 @@ describe Hatetepe::Server do
68
65
  end
69
66
 
70
67
  describe '#respond' do
68
+ let(:headers) { {} }
71
69
  let(:request) { open_request }
72
- let(:response) { open_response }
70
+ let(:response) { open_response(200, headers) }
73
71
 
74
72
  subject! { server.respond(request, response) }
75
73
 
@@ -80,6 +78,46 @@ describe Hatetepe::Server do
80
78
  it 'notifies the handlers' do
81
79
  expect(handler).to have_received(:respond).with(request, response)
82
80
  end
81
+
82
+ it 'sets Server and Date headers' do
83
+ expect(headers['Server']).to eq("hatetepe/#{Hatetepe::VERSION}")
84
+ expect(headers['Date']).to eq(Time.now.httpdate)
85
+ end
86
+
87
+ describe 'with Server or Date header set to anything' do
88
+ let(:headers) { { 'Server' => nil, 'Date' => nil } }
89
+
90
+ it 'does not override' do
91
+ expect(headers['Server']).to be(nil)
92
+ expect(headers['Date']).to be(nil)
93
+ end
94
+ end
95
+
96
+ describe 'wiring' do
97
+ let(:request) { open_request }
98
+ let(:response) { open_response }
99
+
100
+ before do
101
+ allow(server).to receive(:finish)
102
+
103
+ response.close
104
+ tick
105
+ end
106
+
107
+ it 'wires up #finish' do
108
+ expect(server).to have_received(:finish).with(request)
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#finish' do
114
+ let(:request) { open_request }
115
+
116
+ subject! { server.finish(request) }
117
+
118
+ it 'notifies the handlers' do
119
+ expect(handler).to have_received(:finish).with(request)
120
+ end
83
121
  end
84
122
 
85
123
  describe '#close' do
@@ -89,8 +127,4 @@ describe Hatetepe::Server do
89
127
  expect(connection).to have_received(:close)
90
128
  end
91
129
  end
92
-
93
- describe '#teardown' do
94
- specify { pending }
95
- end
96
130
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hatetepe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0.pre.1
4
+ version: 0.6.0.pre.2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-11-14 00:00:00.000000000 Z
12
+ date: 2013-12-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
@@ -48,17 +48,17 @@ dependencies:
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
51
- - - ! '>='
51
+ - - ~>
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: 0.3.0
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  none: false
58
58
  requirements:
59
- - - ! '>='
59
+ - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 0.3.0
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: rspec
64
64
  requirement: !ruby/object:Gem::Requirement
@@ -78,8 +78,7 @@ dependencies:
78
78
  description: The HTTP toolkit
79
79
  email:
80
80
  - lars.gierth@gmail.com
81
- executables:
82
- - hatetepe
81
+ executables: []
83
82
  extensions: []
84
83
  extra_rdoc_files: []
85
84
  files:
@@ -90,9 +89,9 @@ files:
90
89
  - Gemfile
91
90
  - Gemfile.devtools
92
91
  - LICENSE.txt
92
+ - Procfile
93
93
  - README.md
94
94
  - Rakefile
95
- - bin/hatetepe
96
95
  - config/devtools.yml
97
96
  - config/flay.yml
98
97
  - config/flog.yml
@@ -100,6 +99,8 @@ files:
100
99
  - config/reek.yml
101
100
  - config/rubocop.yml
102
101
  - config/yardstick.yml
102
+ - examples/echo_server.rb
103
+ - examples/getting_started.rb
103
104
  - hatetepe.gemspec
104
105
  - lib/hatetepe.rb
105
106
  - lib/hatetepe/body.rb
@@ -120,7 +121,6 @@ files:
120
121
  - lib/hatetepe/support/handlers.rb
121
122
  - lib/hatetepe/support/message.rb
122
123
  - lib/hatetepe/version.rb
123
- - spec/integration/error_handling_spec.rb
124
124
  - spec/integration/keep_alive_spec.rb
125
125
  - spec/integration/smoke_spec.rb
126
126
  - spec/integration/streaming_spec.rb
@@ -162,7 +162,6 @@ signing_key:
162
162
  specification_version: 3
163
163
  summary: General purpose toolkit for HTTP clients and servers
164
164
  test_files:
165
- - spec/integration/error_handling_spec.rb
166
165
  - spec/integration/keep_alive_spec.rb
167
166
  - spec/integration/smoke_spec.rb
168
167
  - spec/integration/streaming_spec.rb
data/bin/hatetepe DELETED
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # encoding: utf-8
3
-
4
- require 'hatetepe'
5
-
6
- class RequestHandler
7
- def initialize(config, server, connection)
8
- end
9
-
10
- def serve(request, served)
11
- puts "#{request.http_method.upcase} #{request.uri}"
12
- response = build_response
13
- served.fulfill(response)
14
- end
15
-
16
- def build_response
17
- Hatetepe::Response.new(
18
- 200, { 'Content-Type' => 'text/plain' }, "hello, world\n")
19
- end
20
- end
21
-
22
- address = ENV.fetch('ADDRESS', '127.0.0.1')
23
- port = ENV.fetch('PORT', 3000)
24
-
25
- $stdout.sync = true
26
-
27
- EM.run do
28
- puts "Listening on http://#{address}:#{port}"
29
- Hatetepe::Server.start(address: address, port: port,
30
- handlers: [Hatetepe::Server::KeepAlive,
31
- Hatetepe::Server::Timeouts,
32
- RequestHandler])
33
- end
@@ -1,7 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- describe 'Error handling' do
6
- specify { pending('Needs more thought') }
7
- end