hatetepe 0.0.4 → 0.2.0

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