hatetepe 0.0.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ require "hatetepe/body"
2
+
3
+ module Hatetepe
4
+ class Message
5
+ attr_accessor :http_version, :headers, :body
6
+
7
+ def initialize(http_version = "1.1")
8
+ @http_version = http_version
9
+ @headers = {}
10
+ @body = Body.new
11
+ end
12
+ end
13
+ end
@@ -1,68 +1,51 @@
1
1
  require "http/parser"
2
2
 
3
+ require "hatetepe/events"
4
+ require "hatetepe/request"
5
+ require "hatetepe/response"
6
+
3
7
  module Hatetepe
4
8
  class ParserError < StandardError; end
5
9
 
6
10
  class Parser
7
- def self.parse(data = [], &block)
8
- message = {}
9
- parser = new do |p|
10
- p.on_request do |*args|
11
- message[:http_method] = args[0]
12
- message[:request_url] = args[1]
13
- message[:http_version] = args[2]
14
- end
15
- p.on_response do |*args|
16
- message[:status] = args[0]
17
- message[:http_version] = args[1]
18
- end
19
- p.on_header do |name, value|
20
- (message[:headers] ||= {})[name] = value
21
- end
22
- p.on_body_chunk do |chunk|
23
- (message[:body] ||= "") << chunk
24
- end
25
- end
26
-
27
- if block
28
- block.arity == 0 ? parser.instance_eval(&block) : block.call(parser)
29
- end
30
-
31
- Array(data).each {|chunk| parser << chunk }
32
- message
33
- end
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
34
20
 
35
21
  def initialize(&block)
36
- @on_request, @on_response = [], []
37
- @on_header, @on_headers_complete = [], []
38
- @on_body_chunk, @on_complete, @on_error = [], [], []
39
- @parser = HTTP::Parser.new
40
-
41
- @parser.on_headers_complete = proc do
42
- if @parser.http_method
43
- on_request.each do |r|
44
- r.call(@parser.http_method, @parser.request_url, @parser.http_version.join("."))
22
+ @parser = HTTP::Parser.new.tap {|p|
23
+ p.on_headers_complete = proc {
24
+ version = p.http_version.join(".")
25
+ if p.http_method
26
+ @message = Request.new(p.http_method, p.request_url, version)
27
+ event! :request, message
28
+ else
29
+ @message = Response.new(p.status_code, version)
30
+ event! :response, message
45
31
  end
46
- else
47
- on_response.each do |r|
48
- r.call(@parser.status_code, @parser.http_version.join("."))
49
- end
50
- end
32
+
33
+ message.headers = p.headers
34
+ event! :headers, message.headers
35
+
36
+ event! :body, message.body
37
+ nil
38
+ }
51
39
 
52
- @parser.headers.each do |header|
53
- on_header.each {|h| h.call(*header) }
54
- end
40
+ p.on_body = proc {|chunk|
41
+ message.body << chunk unless message.body.closed_write?
42
+ }
55
43
 
56
- on_headers_complete.each {|hc| hc.call }
57
- end
58
-
59
- @parser.on_body = proc do |chunk|
60
- on_body_chunk.each {|b| b.call(chunk) }
61
- end
62
-
63
- @parser.on_message_complete = proc do
64
- on_complete.each {|f| f.call }
65
- end
44
+ p.on_message_complete = proc {
45
+ message.body.close_write unless message.body.closed_write?
46
+ event! :complete
47
+ }
48
+ }
66
49
 
67
50
  reset
68
51
 
@@ -73,27 +56,14 @@ module Hatetepe
73
56
 
74
57
  def reset
75
58
  @parser.reset!
76
- end
77
-
78
- [:request, :response, :header, :headers_complete,
79
- :body_chunk, :complete, :error].each do |hook|
80
- define_method :"on_#{hook}" do |&block|
81
- store = instance_variable_get(:"@on_#{hook}")
82
- return store unless block
83
- store << block
84
- end
59
+ event! :reset
60
+ @message = nil
85
61
  end
86
62
 
87
63
  def <<(data)
88
64
  @parser << data
89
- rescue HTTP::Parser::Error => original_error
90
- error = ParserError.new(original_error.message)
91
- error.set_backtrace(original_error.backtrace)
92
- unless on_error.empty?
93
- on_error.each {|e| e.call(error) }
94
- else
95
- raise(error)
96
- end
65
+ rescue HTTP::Parser::Error => e
66
+ raise Hatetepe::ParserError, e.message, e.backtrace
97
67
  end
98
68
  end
99
69
  end
@@ -0,0 +1,11 @@
1
+ module Hatetepe
2
+ class Prefork
3
+ def self.run(server)
4
+ prefork = new(server)
5
+ fork {
6
+ prefork.serve
7
+ }
8
+ prefork.manage
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,58 @@
1
+ require "eventmachine"
2
+ require "uri"
3
+
4
+ require "hatetepe/client"
5
+
6
+ module Hatetepe
7
+ class Proxy
8
+ attr_reader :app, :env
9
+
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ @env = env
16
+ env["proxy.start"] = method(:start)
17
+
18
+ app.call env
19
+ end
20
+
21
+ def start(target)
22
+ uri = build_uri(target)
23
+
24
+ env.delete "proxy.start"
25
+ env["proxy.callback"] ||= method(:callback)
26
+
27
+ response = Client.request(verb, uri, headers)
28
+ env["proxy.callback"].call @response, env
29
+ end
30
+
31
+ def callback(response, env)
32
+ response
33
+ end
34
+ end
35
+ end
36
+
37
+ module Hatetepe
38
+ class OldProxy
39
+ attr_reader :env, :target
40
+
41
+ def initialize(env, target)
42
+ client = EM.connect target.host, target.port, Client
43
+ client.request env["rity.request"].verb, env["rity.request"].uri
44
+
45
+ env["proxy.callback"] ||= proc {|response|
46
+ env["proxy.start_reverse"].call response
47
+ }
48
+ env["proxy.start_reverse"] = proc {|response|
49
+ env["stream.start"].call *response[0..1]
50
+ env["stream.send_raw"].call client.requests
51
+ }
52
+ end
53
+
54
+ def initialize(env, target)
55
+ response = Client.request(env["rity.request"])
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,31 @@
1
+ require "hatetepe/message"
2
+
3
+ module Hatetepe
4
+ class Request < Message
5
+ include EM::Deferrable
6
+
7
+ attr_accessor :verb, :uri, :response
8
+
9
+ def initialize(verb, uri, http_version = "1.1")
10
+ @verb, @uri = verb, uri
11
+ super http_version
12
+ end
13
+
14
+ def to_hash
15
+ {
16
+ "rack.version" => [1, 0],
17
+ "hatetepe.request" => self,
18
+ "rack.input" => body,
19
+ "REQUEST_METHOD" => verb.dup,
20
+ "REQUEST_URI" => uri.dup
21
+ }.tap {|h|
22
+ headers.each {|key, value|
23
+ h["HTTP_#{key.upcase.gsub(/[^A-Z_]/, "_")}"] = value
24
+ }
25
+
26
+ h["REQUEST_PATH"], qm, h["QUERY_STRING"] = uri.partition("?")
27
+ h["PATH_INFO"], h["SCRIPT_NAME"] = h["REQUEST_PATH"].dup, ""
28
+ }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ require "hatetepe/message"
2
+
3
+ module Hatetepe
4
+ class Response < Message
5
+ attr_accessor :status
6
+
7
+ def initialize(status, http_version = "1.1")
8
+ @status = status
9
+ super http_version
10
+ end
11
+
12
+ def to_a
13
+ [status, headers, body]
14
+ end
15
+
16
+ def [](i)
17
+ to_a[i]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,98 @@
1
+ require "eventmachine"
2
+ require "em-synchrony"
3
+ require "rack"
4
+
5
+ require "hatetepe/app"
6
+ require "hatetepe/builder"
7
+ require "hatetepe/parser"
8
+ require "hatetepe/request"
9
+
10
+ module Hatetepe
11
+ class Server < EM::Connection
12
+ def self.start(config)
13
+ server = EM.start_server(config[:host], config[:port], self, config)
14
+ #Prefork.run server if config[:prefork]
15
+ end
16
+
17
+ attr_reader :app, :config, :errors
18
+ attr_reader :requests, :parser, :builder
19
+
20
+ def initialize(config)
21
+ @config = config
22
+ @errors = config[:errors] || $stderr
23
+
24
+ @app = Rack::Builder.new.tap {|b|
25
+ b.use Hatetepe::App
26
+ #b.use Hatetepe::Proxy
27
+ b.run config[:app]
28
+ }
29
+
30
+ super
31
+ end
32
+
33
+ def post_init
34
+ @requests = []
35
+ @parser, @builder = Parser.new, Builder.new
36
+
37
+ parser.on_request << requests.method(:<<)
38
+ parser.on_headers << method(:process)
39
+
40
+ builder.on_write << method(:send_data)
41
+ end
42
+
43
+ def receive_data(data)
44
+ parser << data
45
+ rescue ParserError
46
+ close_connection
47
+ rescue Exception => ex
48
+ close_connection_after_writing
49
+ backtrace = ex.backtrace.map {|line| "\t#{line}" }.join("\n")
50
+ errors << "#{ex.class}: #{ex.message}\n#{backtrace}\n"
51
+ errors.flush
52
+ end
53
+
54
+ def process(*)
55
+ previous, request = requests.values_at(-2, -1)
56
+
57
+ env = request.to_hash.tap {|e|
58
+ e["hatetepe.connection"] = self
59
+ e["rack.url_scheme"] = "http"
60
+ e["rack.input"].source = self
61
+ e["rack.errors"] = errors
62
+
63
+ e["rack.multithread"] = false
64
+ e["rack.multiprocess"] = false
65
+ e["rack.run_once"] = false
66
+
67
+ e["SERVER_NAME"] = config[:host].dup
68
+ e["SERVER_PORT"] = String(config[:port])
69
+
70
+ host = e["HTTP_HOST"] || config[:host].dup
71
+ host += ":#{config[:port]}" unless host.include? ":"
72
+ e["HTTP_HOST"] = host
73
+
74
+ e["stream.start"] = proc {|response|
75
+ e.delete "stream.start"
76
+ EM::Synchrony.sync previous if previous
77
+ response[1]["Server"] = "hatetepe/#{VERSION}"
78
+ builder.response response[0..1]
79
+ }
80
+
81
+ e["stream.send"] = builder.method(:body)
82
+
83
+ e["stream.close"] = proc {
84
+ e.delete "stream.send"
85
+ e.delete "stream.close"
86
+
87
+ builder.complete
88
+ requests.delete request
89
+ request.succeed
90
+
91
+ close_connection_after_writing if requests.empty?
92
+ }
93
+ }
94
+
95
+ Fiber.new { app.call env }.resume
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,4 @@
1
+ module Hatetepe
2
+ class ThreadPool
3
+ end
4
+ end
@@ -1,3 +1,3 @@
1
1
  module Hatetepe
2
- VERSION = "0.0.4"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,33 @@
1
+ require "eventmachine"
2
+ require "hatetepe/server"
3
+
4
+ module Rack
5
+ module Handler
6
+ class Hatetepe
7
+ def self.run(app, options = {})
8
+ options = {
9
+ :host => options[:Host] || "0.0.0.0",
10
+ :port => options[:Port] || 8080,
11
+ :app => app
12
+ }
13
+
14
+ Signal.trap("INT") { EM.stop }
15
+ Signal.trap("TERM") { EM.stop }
16
+
17
+ EM.run {
18
+ EM.epoll
19
+
20
+ server = ::Hatetepe::Server.start options
21
+ yield server if block_given?
22
+ }
23
+ end
24
+
25
+ def self.valid_options
26
+ {
27
+ "Host=HOST" => "Hostname to listen on (default: 0.0.0.0 / all interfaces)",
28
+ "Port=PORT" => "Port to listen on (default: 8080)",
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,169 @@
1
+ require "spec_helper"
2
+ require "hatetepe/cli"
3
+ require "socket"
4
+
5
+ describe "start command" do
6
+ def hook_event_loop(&block)
7
+ EM.spec_hooks << block
8
+ end
9
+
10
+ def add_stop_timer(timeout)
11
+ hook_event_loop do
12
+ EM.add_timer(timeout) { EM.stop }
13
+ end
14
+ end
15
+
16
+ before do
17
+ $stderr = StringIO.new ""
18
+
19
+ FakeFS.activate!
20
+ File.open("config.ru", "w") do |f|
21
+ f.write %q{run proc {|e| [200, {"Content-Type" => "text/plain"}, [e["REQUEST_URI"]]] }}
22
+ end
23
+ File.open("config2.ru", "w") do |f|
24
+ f.write %q{run proc {|e| [200, {"Content-Type" => "text/plain"}, ["config2.ru loaded"]] }}
25
+ end
26
+ end
27
+
28
+ after do
29
+ $stderr = STDERR
30
+
31
+ FakeFS.deactivate!
32
+ FakeFS::FileSystem.clear
33
+ end
34
+
35
+ it "starts an instance of Rity" do
36
+ add_stop_timer 0.05
37
+ hook_event_loop do
38
+ Socket.tcp("127.0.0.1", 3000) {|*| }
39
+ end
40
+ Hatetepe::CLI.start %w{}
41
+
42
+ $stderr.string.should include("127.0.0.1:3000")
43
+ end
44
+
45
+ it "answers HTTP requests" do
46
+ add_stop_timer 0.02
47
+ hook_event_loop do
48
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
49
+ response = EM::Synchrony.sync(request)
50
+
51
+ response.response_header.status.should == 200
52
+ response.response_header["CONTENT_TYPE"].should == "text/plain"
53
+ response.response.should == "/"
54
+ end
55
+ Hatetepe::CLI.start %w{}
56
+ end
57
+
58
+ describe "--port option" do
59
+ it "changes the listen port" do
60
+ add_stop_timer 0.05
61
+ hook_event_loop do
62
+ Socket.tcp("127.0.0.1", 3001) {|*| }
63
+ end
64
+ Hatetepe::CLI.start %w{--port=3001}
65
+
66
+ $stderr.string.should include(":3001")
67
+ end
68
+
69
+ it "has an alias: -p" do
70
+ add_stop_timer 0.05
71
+ hook_event_loop do
72
+ Socket.tcp("127.0.0.1", 3002) {|*| }
73
+ end
74
+ Hatetepe::CLI.start %w{-p 3002}
75
+
76
+ $stderr.string.should include(":3002")
77
+ end
78
+ end
79
+
80
+ describe "--bind option" do
81
+ it "changes the listen interface" do
82
+ add_stop_timer 0.05
83
+ hook_event_loop do
84
+ Socket.tcp("127.0.0.2", 3000) {|*| }
85
+ end
86
+ Hatetepe::CLI.start %w{--bind=127.0.0.2}
87
+
88
+ $stderr.string.should include("127.0.0.2:")
89
+ end
90
+
91
+ it "has an alias: -b" do
92
+ add_stop_timer 0.05
93
+ hook_event_loop do
94
+ Socket.tcp("127.0.0.3", 3000) {|*| }
95
+ end
96
+ Hatetepe::CLI.start %w{-b 127.0.0.3}
97
+
98
+ $stderr.string.should include("127.0.0.3:")
99
+ end
100
+ end
101
+
102
+ describe "--rackup option" do
103
+ it "changes the rackup file that'll be loaded" do
104
+ add_stop_timer 0.05
105
+ hook_event_loop do
106
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
107
+ response = EM::Synchrony.sync(request)
108
+ response.response.should include("config2.ru")
109
+ end
110
+ Hatetepe::CLI.start %w{--rackup=config2.ru}
111
+ end
112
+
113
+ it "has an alias: -r" do
114
+ add_stop_timer 0.05
115
+ hook_event_loop do
116
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
117
+ response = EM::Synchrony.sync(request)
118
+ response.response.should include("config2.ru")
119
+ end
120
+ Hatetepe::CLI.start %w{-r config2.ru}
121
+ end
122
+ end
123
+
124
+ describe "--quiet option" do
125
+ it "discards all output" do
126
+ pending
127
+
128
+ add_stop_timer 0.05
129
+ Hatetepe::CLI.start %w{--quiet}
130
+
131
+ $stderr.string.should be_empty
132
+ end
133
+
134
+ it "has an alias: -q" do
135
+ pending
136
+
137
+ add_stop_timer 0.05
138
+ Hatetepe::CLI.start %w{-q}
139
+
140
+ $stderr.string.should be_empty
141
+ end
142
+ end
143
+
144
+ describe "--verbose option" do
145
+ it "prints debugging data" do
146
+ pending
147
+
148
+ add_stop_timer 0.05
149
+ hook_event_loop do
150
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
151
+ end
152
+ Hatetepe::CLI.start %w{--verbose}
153
+
154
+ $stderr.string.split("\n").size.should > 10
155
+ end
156
+
157
+ it "has an alias: -V" do
158
+ pending
159
+
160
+ add_stop_timer 0.05
161
+ hook_event_loop do
162
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
163
+ end
164
+ Hatetepe::CLI.start %w{-V}
165
+
166
+ $stderr.string.split("\n").size.should > 10
167
+ end
168
+ end
169
+ end