hatetepe 0.6.0.pre.1 → 0.6.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.
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