hatetepe 0.5.2 → 0.6.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/.yardopts +1 -0
- data/Gemfile +9 -4
- data/Gemfile.devtools +55 -0
- data/LICENSE.txt +22 -0
- data/README.md +39 -192
- data/Rakefile +3 -2
- data/bin/hatetepe +35 -2
- data/config/devtools.yml +2 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +103 -0
- data/config/rubocop.yml +58 -0
- data/config/yardstick.yml +2 -0
- data/hatetepe.gemspec +23 -27
- data/lib/hatetepe/client/keep_alive.rb +59 -0
- data/lib/hatetepe/client/timeouts.rb +19 -0
- data/lib/hatetepe/client.rb +54 -302
- data/lib/hatetepe/connection/eventmachine.rb +61 -0
- data/lib/hatetepe/connection/status.rb +28 -0
- data/lib/hatetepe/errors.rb +7 -0
- data/lib/hatetepe/promise.rb +86 -0
- data/lib/hatetepe/request.rb +15 -39
- data/lib/hatetepe/response.rb +82 -22
- data/lib/hatetepe/serializer/encoding.rb +58 -0
- data/lib/hatetepe/serializer.rb +61 -0
- data/lib/hatetepe/server/keep_alive.rb +53 -13
- data/lib/hatetepe/server/timeouts.rb +17 -0
- data/lib/hatetepe/server.rb +37 -85
- data/lib/hatetepe/support/handlers.rb +19 -0
- data/lib/hatetepe/support/keep_alive.rb +14 -0
- data/lib/hatetepe/support/message.rb +40 -0
- data/lib/hatetepe/version.rb +3 -1
- data/lib/hatetepe.rb +29 -7
- data/spec/integration/error_handling_spec.rb +7 -0
- data/spec/integration/keep_alive_spec.rb +106 -0
- data/spec/integration/smoke_spec.rb +21 -0
- data/spec/integration/streaming_spec.rb +61 -0
- data/spec/integration/timeouts_spec.rb +82 -0
- data/spec/shared/integration/server_client_pair.rb +26 -0
- data/spec/spec_helper.rb +41 -10
- data/spec/support/handler.rb +55 -0
- data/spec/support/helper.rb +74 -0
- data/spec/unit/client_spec.rb +115 -156
- data/spec/unit/connection/eventmachine_spec.rb +146 -0
- data/spec/unit/request_spec.rb +35 -0
- data/spec/unit/response_spec.rb +42 -0
- data/spec/unit/server_spec.rb +65 -100
- data/spec/unit/support/keep_alive_spec.rb +52 -0
- data/spec/unit/support/message_spec.rb +41 -0
- metadata +68 -103
- data/Gemfile.lock +0 -46
- data/LICENSE +0 -19
- data/Procfile +0 -1
- data/config.ru +0 -7
- data/examples/parallel_requests.rb +0 -32
- data/lib/hatetepe/body.rb +0 -182
- data/lib/hatetepe/builder.rb +0 -171
- data/lib/hatetepe/cli.rb +0 -61
- data/lib/hatetepe/connection.rb +0 -73
- data/lib/hatetepe/events.rb +0 -35
- data/lib/hatetepe/message.rb +0 -13
- data/lib/hatetepe/parser.rb +0 -83
- data/lib/hatetepe/server/pipeline.rb +0 -20
- data/lib/hatetepe/server/rack_app.rb +0 -39
- data/lib/rack/handler/hatetepe.rb +0 -33
- data/spec/integration/cli/start_spec.rb +0 -113
- data/spec/integration/client/keep_alive_spec.rb +0 -23
- data/spec/integration/client/timeout_spec.rb +0 -97
- data/spec/integration/server/keep_alive_spec.rb +0 -27
- data/spec/integration/server/timeout_spec.rb +0 -51
- data/spec/unit/body_spec.rb +0 -205
- data/spec/unit/builder_spec.rb +0 -372
- data/spec/unit/connection_spec.rb +0 -62
- data/spec/unit/events_spec.rb +0 -96
- data/spec/unit/parser_spec.rb +0 -209
- data/spec/unit/rack_handler_spec.rb +0 -60
data/lib/hatetepe/body.rb
DELETED
@@ -1,182 +0,0 @@
|
|
1
|
-
require "em-synchrony"
|
2
|
-
require "eventmachine"
|
3
|
-
require "stringio"
|
4
|
-
|
5
|
-
module Hatetepe
|
6
|
-
# Thin wrapper around StringIO for asynchronous body processing.
|
7
|
-
class Body
|
8
|
-
include EM::Deferrable
|
9
|
-
|
10
|
-
# The wrapped StringIO.
|
11
|
-
attr_reader :io
|
12
|
-
|
13
|
-
# The origin Client or Server connection.
|
14
|
-
attr_accessor :source
|
15
|
-
|
16
|
-
# Create a new Body instance.
|
17
|
-
#
|
18
|
-
# @param [String] data
|
19
|
-
# Initial content of the StringIO object.
|
20
|
-
def initialize(data = "")
|
21
|
-
@receivers = []
|
22
|
-
@io = StringIO.new(data)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Blocks until the Body is write-closed.
|
26
|
-
#
|
27
|
-
# Use this if you want to wait until _all_ of the body has arrived before
|
28
|
-
# continuing. It will resume the originating connection if it's paused.
|
29
|
-
#
|
30
|
-
# @return [undefined]
|
31
|
-
def sync
|
32
|
-
source.resume if source && source.paused?
|
33
|
-
EM::Synchrony.sync self
|
34
|
-
end
|
35
|
-
|
36
|
-
# Forwards to StringIO#length.
|
37
|
-
#
|
38
|
-
# Blocks until the Body is write-closed. Returns the current length of the
|
39
|
-
# underlying StringIO's content.
|
40
|
-
#
|
41
|
-
# @return [Fixnum]
|
42
|
-
# The StringIO's length.
|
43
|
-
def length
|
44
|
-
sync
|
45
|
-
io.length
|
46
|
-
end
|
47
|
-
|
48
|
-
# Returns true if the underlying StringIO is empty, false otherwise.
|
49
|
-
#
|
50
|
-
# @return [Boolean]
|
51
|
-
# True if empty, false otherwise.
|
52
|
-
def empty?
|
53
|
-
length == 0
|
54
|
-
end
|
55
|
-
|
56
|
-
# Forwards to StringIO#pos.
|
57
|
-
#
|
58
|
-
# Returns the underlying StringIO's current pointer position.
|
59
|
-
#
|
60
|
-
# @return [Fixnum]
|
61
|
-
# The current pointer position.
|
62
|
-
def pos
|
63
|
-
io.pos
|
64
|
-
end
|
65
|
-
|
66
|
-
# Forwards to StringIO#rewind.
|
67
|
-
#
|
68
|
-
# Moves the underlying StringIO's pointer back to the beginnung.
|
69
|
-
#
|
70
|
-
# @return [undefined]
|
71
|
-
def rewind
|
72
|
-
sync
|
73
|
-
rewind!
|
74
|
-
end
|
75
|
-
|
76
|
-
# Rewinds underlying IO without blocking
|
77
|
-
#
|
78
|
-
# TODO this is a hack. the whole blocking/rewinding stuff needs to be
|
79
|
-
# more though out.
|
80
|
-
#
|
81
|
-
# @api protected
|
82
|
-
def rewind!
|
83
|
-
io.rewind
|
84
|
-
end
|
85
|
-
|
86
|
-
# Forwards to StringIO#close_write.
|
87
|
-
#
|
88
|
-
# Write-closes the body and succeeds, thus releasing all blocking method
|
89
|
-
# calls like #length, #each, #read and #get.
|
90
|
-
#
|
91
|
-
# @return [undefined]
|
92
|
-
def close_write
|
93
|
-
io.close_write
|
94
|
-
succeed
|
95
|
-
end
|
96
|
-
|
97
|
-
# Forwards to StringIO#closed_write?.
|
98
|
-
#
|
99
|
-
# Returns true if the body is write-closed, false otherwise.
|
100
|
-
#
|
101
|
-
# @return [Boolean]
|
102
|
-
# True if the body is write-closed, false otherwise.
|
103
|
-
def closed_write?
|
104
|
-
io.closed_write?
|
105
|
-
end
|
106
|
-
|
107
|
-
# Yields incoming body data.
|
108
|
-
#
|
109
|
-
# Immediately yields all data that has already arrived. Blocks until the
|
110
|
-
# Body is write-closed and yields for each call to #write until then.
|
111
|
-
#
|
112
|
-
# @yield [String] Block to execute for each incoming data chunk.
|
113
|
-
#
|
114
|
-
# @return [Enumerator,self]
|
115
|
-
def each(&block)
|
116
|
-
return to_enum(__method__) unless block
|
117
|
-
|
118
|
-
@receivers << block
|
119
|
-
block.call io.string.dup unless io.string.empty?
|
120
|
-
sync
|
121
|
-
|
122
|
-
self
|
123
|
-
end
|
124
|
-
|
125
|
-
# Forwards to StringIO#read.
|
126
|
-
#
|
127
|
-
# From the Rack Spec: If given, +length+ must be a non-negative Integer
|
128
|
-
# (>= 0) or +nil+, and +buffer+ must be a String and may not be nil. If
|
129
|
-
# +length+ is given and not nil, then this method reads at most +length+
|
130
|
-
# bytes from the input stream. If +length+ is not given or nil, then this
|
131
|
-
# method reads all data until EOF. When EOF is reached, this method returns
|
132
|
-
# nil if +length+ is given and not nil, or "" if +length+ is not given or
|
133
|
-
# is nil. If +buffer+ is given, then the read data will be placed into
|
134
|
-
# +buffer+ instead of a newly created String object.
|
135
|
-
#
|
136
|
-
# @param [Fixnum] length (optional)
|
137
|
-
# How many bytes to read.
|
138
|
-
# @param [String] buffer (optional)
|
139
|
-
# Buffer for read data.
|
140
|
-
#
|
141
|
-
# @return [nil]
|
142
|
-
# +nil+ if EOF has been reached.
|
143
|
-
# @return [String]
|
144
|
-
# All data or at most +length+ bytes of data if +length+ is given.
|
145
|
-
def read(*args)
|
146
|
-
sync
|
147
|
-
io.read *args
|
148
|
-
end
|
149
|
-
|
150
|
-
# Forwards to StringIO#gets.
|
151
|
-
#
|
152
|
-
# Reads one line from the IO. Returns the line or +nil+ if EOF has been
|
153
|
-
# reached.
|
154
|
-
#
|
155
|
-
# @return [String]
|
156
|
-
# One line.
|
157
|
-
# @return [nil]
|
158
|
-
# If has been reached.
|
159
|
-
def gets
|
160
|
-
sync
|
161
|
-
io.gets
|
162
|
-
end
|
163
|
-
|
164
|
-
# Forwards to StringIO#write.
|
165
|
-
#
|
166
|
-
# Appends the given String to the underlying StringIO annd returns the
|
167
|
-
# number of bytes written.
|
168
|
-
#
|
169
|
-
# @param [String] data
|
170
|
-
# The data to append.
|
171
|
-
#
|
172
|
-
# @return [Fixnum]
|
173
|
-
# The number of bytes written.
|
174
|
-
def write(data)
|
175
|
-
ret = io.write data
|
176
|
-
@receivers.each do |r|
|
177
|
-
Fiber.new { r.call data }.resume
|
178
|
-
end
|
179
|
-
ret
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
data/lib/hatetepe/builder.rb
DELETED
@@ -1,171 +0,0 @@
|
|
1
|
-
require "rack/utils"
|
2
|
-
|
3
|
-
module Hatetepe
|
4
|
-
class BuilderError < StandardError; end
|
5
|
-
|
6
|
-
class Builder
|
7
|
-
def self.build(&block)
|
8
|
-
message = ""
|
9
|
-
builder = new do |b|
|
10
|
-
b.on_write {|data| message << data }
|
11
|
-
end
|
12
|
-
|
13
|
-
block.arity == 0 ? builder.instance_eval(&block) : block.call(builder)
|
14
|
-
|
15
|
-
builder.complete
|
16
|
-
return message.empty? ? nil : message
|
17
|
-
end
|
18
|
-
|
19
|
-
attr_reader :state
|
20
|
-
|
21
|
-
def initialize(&block)
|
22
|
-
reset
|
23
|
-
@on_write, @on_complete, @on_error = [], [], []
|
24
|
-
|
25
|
-
if block
|
26
|
-
block.arity == 0 ? instance_eval(&block) : block.call(self)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def reset
|
31
|
-
@state = :ready
|
32
|
-
@chunked = nil
|
33
|
-
end
|
34
|
-
|
35
|
-
[:write, :complete, :error].each do |hook|
|
36
|
-
define_method :"on_#{hook}" do |&block|
|
37
|
-
store = instance_variable_get(:"@on_#{hook}")
|
38
|
-
return store unless block
|
39
|
-
store << block
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def ready?
|
44
|
-
state == :ready
|
45
|
-
end
|
46
|
-
|
47
|
-
def writing_headers?
|
48
|
-
state == :writing_headers
|
49
|
-
end
|
50
|
-
|
51
|
-
def writing_body?
|
52
|
-
state == :writing_body
|
53
|
-
end
|
54
|
-
|
55
|
-
def writing_trailing_headers?
|
56
|
-
state == :writing_trailing_headers
|
57
|
-
end
|
58
|
-
|
59
|
-
def chunked?
|
60
|
-
@chunked
|
61
|
-
end
|
62
|
-
|
63
|
-
def request(req)
|
64
|
-
request_line req[0], req[1], (req[4] || "1.1")
|
65
|
-
headers req[2]
|
66
|
-
body req[3] if req[3]
|
67
|
-
complete
|
68
|
-
end
|
69
|
-
|
70
|
-
def request_line(verb, uri, version = "1.1")
|
71
|
-
complete unless ready?
|
72
|
-
write "#{verb.upcase} #{uri} HTTP/#{version}\r\n"
|
73
|
-
@state = :writing_headers
|
74
|
-
end
|
75
|
-
|
76
|
-
def response(res)
|
77
|
-
response_line res[0], (res[3] || "1.1")
|
78
|
-
headers res[1]
|
79
|
-
body res[2] if res[2]
|
80
|
-
complete
|
81
|
-
end
|
82
|
-
|
83
|
-
def response_line(code, version = "1.1")
|
84
|
-
complete unless ready?
|
85
|
-
unless status = Rack::Utils::HTTP_STATUS_CODES[code]
|
86
|
-
error "Unknown status code: #{code}"
|
87
|
-
end
|
88
|
-
|
89
|
-
write "HTTP/#{version} #{code} #{status}\r\n"
|
90
|
-
@state = :writing_headers
|
91
|
-
end
|
92
|
-
|
93
|
-
def header(name, value)
|
94
|
-
value = String(value)
|
95
|
-
raw_header "#{name}: #{value}" unless value.empty?
|
96
|
-
end
|
97
|
-
|
98
|
-
def headers(hash)
|
99
|
-
hash.each {|k, v| header k, v }
|
100
|
-
end
|
101
|
-
|
102
|
-
def raw_header(header)
|
103
|
-
if ready?
|
104
|
-
error "A request or response line is required before writing headers"
|
105
|
-
elsif writing_body?
|
106
|
-
error "Trailing headers require chunked transfer encoding" unless chunked?
|
107
|
-
write "0\r\n"
|
108
|
-
@state = :writing_trailing_headers
|
109
|
-
end
|
110
|
-
|
111
|
-
if @chunked.nil? && header[0..13] == "Content-Length"
|
112
|
-
@chunked = false
|
113
|
-
elsif @chunked.nil? && header[0..16] == "Transfer-Encoding"
|
114
|
-
@chunked = true
|
115
|
-
end
|
116
|
-
|
117
|
-
write "#{header}\r\n"
|
118
|
-
end
|
119
|
-
|
120
|
-
def body(body)
|
121
|
-
body.each {|c| body_chunk c }
|
122
|
-
# XXX complete here?
|
123
|
-
end
|
124
|
-
|
125
|
-
def body_chunk(chunk)
|
126
|
-
if ready?
|
127
|
-
error "A request or response line and headers are required before writing body"
|
128
|
-
elsif writing_trailing_headers?
|
129
|
-
error "Cannot write body after trailing headers"
|
130
|
-
elsif writing_headers?
|
131
|
-
if @chunked.nil?
|
132
|
-
header "Transfer-Encoding", "chunked"
|
133
|
-
end
|
134
|
-
write "\r\n"
|
135
|
-
@state = :writing_body
|
136
|
-
end
|
137
|
-
|
138
|
-
chunk = chunk.to_s
|
139
|
-
if chunked?
|
140
|
-
write "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
141
|
-
else
|
142
|
-
write chunk unless chunk.empty?
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
def complete
|
147
|
-
if ready?
|
148
|
-
return
|
149
|
-
elsif writing_headers? && @chunked.nil?
|
150
|
-
header "Content-Length", "0"
|
151
|
-
end
|
152
|
-
body_chunk ""
|
153
|
-
|
154
|
-
on_complete.each {|blk| blk.call }
|
155
|
-
reset
|
156
|
-
end
|
157
|
-
|
158
|
-
def write(data)
|
159
|
-
on_write.each {|blk| blk.call data }
|
160
|
-
end
|
161
|
-
|
162
|
-
def error(message)
|
163
|
-
exception = BuilderError.new(message)
|
164
|
-
unless on_error.empty?
|
165
|
-
on_error.each {|blk| blk.call exception }
|
166
|
-
else
|
167
|
-
raise exception
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
data/lib/hatetepe/cli.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
require "thor"
|
2
|
-
|
3
|
-
module Hatetepe
|
4
|
-
class CLI < Thor
|
5
|
-
map "--version" => :version
|
6
|
-
map "-v" => :version
|
7
|
-
|
8
|
-
default_task :start
|
9
|
-
|
10
|
-
desc :version, "Print version information"
|
11
|
-
def version
|
12
|
-
require "hatetepe/version"
|
13
|
-
say Hatetepe::VERSION
|
14
|
-
end
|
15
|
-
|
16
|
-
desc "[start]", "Start a server"
|
17
|
-
method_option :bind, :aliases => "-b", :type => :string,
|
18
|
-
:banner => "Bind to the specified TCP interface (default: 127.0.0.1)"
|
19
|
-
method_option :port, :aliases => "-p", :type => :numeric,
|
20
|
-
:banner => "Bind to the specified port (default: 3000)"
|
21
|
-
method_option :rackup, :aliases => "-r", :type => :string,
|
22
|
-
:banner => "Load specified rackup (.ru) file (default: config.ru)"
|
23
|
-
method_option :env, :aliases => "-e", :type => :string,
|
24
|
-
:banner => "Boot the app in the specified environment (default: development)"
|
25
|
-
method_option :timeout, :aliases => "-t", :type => :numeric,
|
26
|
-
:banner => "Time out connections after the specified admount of seconds (default: see Hatetepe::Server::CONFIG_DEFAULTS)"
|
27
|
-
def start
|
28
|
-
require "hatetepe/server"
|
29
|
-
require "rack"
|
30
|
-
|
31
|
-
config = config_for(options)
|
32
|
-
ENV["RACK_ENV"] = config[:env]
|
33
|
-
|
34
|
-
$stderr << "We're in #{config[:env]}\n"
|
35
|
-
$stderr << "Booting from #{config[:rackup]}\n"
|
36
|
-
|
37
|
-
EM.epoll
|
38
|
-
EM.synchrony do
|
39
|
-
$stderr << "Binding to #{config[:host]}:#{config[:port]}\n"
|
40
|
-
|
41
|
-
trap("INT") { EM.stop }
|
42
|
-
trap("TERM") { EM.stop }
|
43
|
-
Server.start(config)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def config_for(options)
|
50
|
-
rackup = File.expand_path(options[:rackup] || "config.ru")
|
51
|
-
{
|
52
|
-
env: options[:env] || ENV["RACK_ENV"] || "development",
|
53
|
-
host: options[:bind] || "127.0.0.1",
|
54
|
-
port: options[:port] || 3000,
|
55
|
-
timeout: options[:timeout],
|
56
|
-
app: Rack::Builder.parse_file(rackup)[0],
|
57
|
-
rackup: rackup
|
58
|
-
}
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
data/lib/hatetepe/connection.rb
DELETED
@@ -1,73 +0,0 @@
|
|
1
|
-
module Hatetepe
|
2
|
-
module Connection
|
3
|
-
attr_accessor :processing_enabled
|
4
|
-
alias_method :processing_enabled?, :processing_enabled
|
5
|
-
|
6
|
-
def remote_address
|
7
|
-
sockaddr && sockaddr[1]
|
8
|
-
end
|
9
|
-
|
10
|
-
def remote_port
|
11
|
-
sockaddr && sockaddr[0]
|
12
|
-
end
|
13
|
-
|
14
|
-
def sockaddr
|
15
|
-
@sockaddr ||= Socket.unpack_sockaddr_in(get_peername) rescue nil
|
16
|
-
end
|
17
|
-
|
18
|
-
def connection_completed
|
19
|
-
@connected = true
|
20
|
-
end
|
21
|
-
|
22
|
-
def connected?
|
23
|
-
defined?(@connected) && @connected
|
24
|
-
end
|
25
|
-
|
26
|
-
def closed?
|
27
|
-
!!defined?(@closed_by)
|
28
|
-
end
|
29
|
-
|
30
|
-
def closed_by_remote?
|
31
|
-
@closed_by == :remote
|
32
|
-
end
|
33
|
-
|
34
|
-
def closed_by_self?
|
35
|
-
@closed_by == :self
|
36
|
-
end
|
37
|
-
|
38
|
-
def closed_by_timeout?
|
39
|
-
connected? && @closed_by == :timeout
|
40
|
-
end
|
41
|
-
|
42
|
-
def closed_by_connect_timeout?
|
43
|
-
!connected? && @closed_by == :timeout
|
44
|
-
end
|
45
|
-
|
46
|
-
def close_connection(after_writing = false)
|
47
|
-
@closed_by = :self unless closed?
|
48
|
-
super
|
49
|
-
end
|
50
|
-
|
51
|
-
def unbind(reason)
|
52
|
-
unless closed?
|
53
|
-
@closed_by = if reason == Errno::ETIMEDOUT
|
54
|
-
:timeout
|
55
|
-
else
|
56
|
-
:remote
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def comm_inactivity_timeout=(seconds)
|
62
|
-
unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
|
63
|
-
super
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def pending_connect_timeout=(seconds)
|
68
|
-
unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
|
69
|
-
super
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
data/lib/hatetepe/events.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
module Hatetepe
|
2
|
-
module Events
|
3
|
-
def self.included(klass)
|
4
|
-
klass.extend ClassMethods
|
5
|
-
end
|
6
|
-
|
7
|
-
attr_reader :state
|
8
|
-
|
9
|
-
def event(name, *args)
|
10
|
-
send(:"on_#{name}").each {|blk| blk.call *args }
|
11
|
-
end
|
12
|
-
|
13
|
-
def event!(name, *args)
|
14
|
-
@state = name
|
15
|
-
event name, *args
|
16
|
-
end
|
17
|
-
|
18
|
-
module ClassMethods
|
19
|
-
def event(name, *more_names)
|
20
|
-
define_method :"on_#{name}" do |&block|
|
21
|
-
ivar = :"@on_#{name}"
|
22
|
-
store = instance_variable_get(ivar)
|
23
|
-
store ||= instance_variable_set(ivar, [])
|
24
|
-
|
25
|
-
return store unless block
|
26
|
-
store << block
|
27
|
-
end
|
28
|
-
|
29
|
-
define_method(:"#{name}?") { state == name }
|
30
|
-
|
31
|
-
more_names.each &method(:event)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
data/lib/hatetepe/message.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
require "hatetepe/body"
|
2
|
-
|
3
|
-
module Hatetepe
|
4
|
-
class Message
|
5
|
-
attr_accessor :http_version, :headers, :body
|
6
|
-
attr_accessor :connection
|
7
|
-
|
8
|
-
def initialize(headers = {}, body = nil, http_version = "1.1")
|
9
|
-
@headers, @http_version = headers, http_version
|
10
|
-
@body = body || Body.new
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
data/lib/hatetepe/parser.rb
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
require "http/parser"
|
2
|
-
|
3
|
-
require "hatetepe/events"
|
4
|
-
require "hatetepe/request"
|
5
|
-
require "hatetepe/response"
|
6
|
-
|
7
|
-
module Hatetepe
|
8
|
-
class ParserError < StandardError; end
|
9
|
-
|
10
|
-
class Parser
|
11
|
-
include Events
|
12
|
-
|
13
|
-
event :reset
|
14
|
-
event :request, :response
|
15
|
-
event :headers, :body
|
16
|
-
event :trailing_header, :trailing_headers_complete
|
17
|
-
event :complete
|
18
|
-
|
19
|
-
attr_reader :message
|
20
|
-
|
21
|
-
def initialize(&block)
|
22
|
-
initialize_parser
|
23
|
-
reset
|
24
|
-
|
25
|
-
if block
|
26
|
-
block.arity == 0 ? instance_eval(&block) : block.call(self)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def initialize_parser
|
31
|
-
@parser = HTTP::Parser.new.tap do |p|
|
32
|
-
p.on_headers_complete = proc do |headers|
|
33
|
-
headers_complete(p) if @headers_counter.even?
|
34
|
-
@headers_counter += 1
|
35
|
-
nil
|
36
|
-
end
|
37
|
-
|
38
|
-
p.on_body = method(:body)
|
39
|
-
p.on_message_complete = method(:complete)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def reset
|
44
|
-
@parser.reset!
|
45
|
-
event! :reset
|
46
|
-
@message = nil
|
47
|
-
@headers_counter = 0
|
48
|
-
end
|
49
|
-
|
50
|
-
def <<(data)
|
51
|
-
@parser << data
|
52
|
-
rescue HTTP::Parser::Error => e
|
53
|
-
raise Hatetepe::ParserError, e.message, e.backtrace
|
54
|
-
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
def headers_complete(parser)
|
59
|
-
args = [ parser.headers, Body.new, parser.http_version.join(".") ]
|
60
|
-
if parser.http_method
|
61
|
-
@message = Request.new(parser.http_method, parser.request_url, *args)
|
62
|
-
event! :request, @message
|
63
|
-
else
|
64
|
-
@message = Response.new(parser.status_code, *args)
|
65
|
-
event! :response, @message
|
66
|
-
end
|
67
|
-
|
68
|
-
event! :headers, message.headers
|
69
|
-
event! :body, message.body
|
70
|
-
end
|
71
|
-
|
72
|
-
def body(chunk)
|
73
|
-
message.body.write chunk unless message.body.closed_write?
|
74
|
-
end
|
75
|
-
|
76
|
-
def complete
|
77
|
-
message.body.rewind!
|
78
|
-
message.body.close_write unless message.body.closed_write?
|
79
|
-
event! :complete
|
80
|
-
@headers_counter += 1 if @headers_counter.odd?
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module Hatetepe::Server
|
2
|
-
class Pipeline
|
3
|
-
def initialize(app, connection)
|
4
|
-
@requests, @app = [], app
|
5
|
-
end
|
6
|
-
|
7
|
-
def call(request, &respond)
|
8
|
-
begin
|
9
|
-
previous = @requests.last
|
10
|
-
@requests << request
|
11
|
-
@app.call(request) do |response|
|
12
|
-
EM::Synchrony.sync(previous) if previous
|
13
|
-
respond.call(response)
|
14
|
-
end
|
15
|
-
ensure
|
16
|
-
@requests.delete(request)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
module Hatetepe::Server
|
2
|
-
class RackApp
|
3
|
-
def initialize(app, connection)
|
4
|
-
@app, @connection = app, connection
|
5
|
-
end
|
6
|
-
|
7
|
-
def call(request, &respond)
|
8
|
-
env = env_for(request)
|
9
|
-
env["async.callback"] = proc do |response|
|
10
|
-
async_callback(response, &respond)
|
11
|
-
end
|
12
|
-
|
13
|
-
response = [ -1 ]
|
14
|
-
catch :async do
|
15
|
-
response = @app.call(env)
|
16
|
-
end
|
17
|
-
|
18
|
-
async_callback(response, &respond)
|
19
|
-
end
|
20
|
-
|
21
|
-
def async_callback(response, &respond)
|
22
|
-
if response[0] >= 0
|
23
|
-
respond.call(Hatetepe::Response.new(*response))
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def env_for(request)
|
28
|
-
request.to_h.merge({
|
29
|
-
"SERVER_NAME" => @connection.config[:host],
|
30
|
-
"SERVER_PORT" => @connection.config[:port].to_s,
|
31
|
-
"rack.errors" => $stderr,
|
32
|
-
"rack.multithread" => false,
|
33
|
-
"rack.multiprocess" => false,
|
34
|
-
"rack.run_once" => false,
|
35
|
-
"rack.url_scheme" => "http"
|
36
|
-
})
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
require "eventmachine"
|
2
|
-
require "em-synchrony"
|
3
|
-
require "hatetepe/server"
|
4
|
-
require "rack"
|
5
|
-
|
6
|
-
module Rack
|
7
|
-
module Handler
|
8
|
-
class Hatetepe
|
9
|
-
def self.run(app, options = {})
|
10
|
-
options = {
|
11
|
-
:host => options[:Host] || "0.0.0.0",
|
12
|
-
:port => options[:Port] || 8080,
|
13
|
-
:app => app
|
14
|
-
}
|
15
|
-
|
16
|
-
Signal.trap("INT") { EM.stop }
|
17
|
-
Signal.trap("TERM") { EM.stop }
|
18
|
-
|
19
|
-
EM.epoll
|
20
|
-
EM.synchrony { ::Hatetepe::Server.start options }
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.valid_options
|
24
|
-
{
|
25
|
-
"Host=HOST" => "Hostname to listen on (default: 0.0.0.0 / all interfaces)",
|
26
|
-
"Port=PORT" => "Port to listen on (default: 8080)",
|
27
|
-
}
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
register "hatetepe", Rack::Handler::Hatetepe
|
32
|
-
end
|
33
|
-
end
|