gorack 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 67a4d34be3bfd03e255e040979e2ddde2a298b1e
4
+ data.tar.gz: c3157d2ac3b1b68040bf0187c07130a341cdf02a
5
+ SHA512:
6
+ metadata.gz: 8dd0c7e0f664a96a157eb91a884deb1c4bbc3fd9c7078d104f4be89282fe8a7a0f97805c447e62a135bb6531f096bdd8a9399a080e25a4611d412aef325cce29
7
+ data.tar.gz: 8845b1c955eb2e4daa39bcdc1535a28fed5b576ce4235c3186eaae6740806c37c8a1c14de00d67ed317eddde12999a6ef96403b362a0a6555a4e1cf2229ea84d
data/bin/gorack ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # vim: set ft=ruby:
4
+
5
+
6
+ def exec(exec_path, args)
7
+ path = File.expand_path(File.join(File.expand_path('../', __FILE__), exec_path))
8
+ Kernel.exec(path, *args)
9
+ end
10
+
11
+ case `uname`
12
+ when /darwin/i then exec("../libexec/darwin_gorack", ARGV)
13
+ #when /linux/i then exec("../libexec/linux_gorack", ARGV)
14
+ else
15
+ raise 'Oops, unsupported platform'
16
+ end
17
+
data/gorack.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env gem build
2
+ # encoding: utf-8
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "gorack"
6
+ s.version = "0.0.1"
7
+ s.authors = ["http://github.com/gmarik"]
8
+ s.homepage = "http://github.com/gmarik/example"
9
+ s.summary = "Golang HTTP frontend server for Ruby's Rack Applications"
10
+ s.description = "#{s.summary}. "
11
+ # s.cert_chain = nil
12
+ s.email = "Z21hcmlrQGdtYWlsLmNvbQ==\n".unpack('m').first
13
+ s.has_rdoc = false
14
+
15
+ # files
16
+ s.files = `find . -type f`.split("\n")
17
+
18
+ s.executables = Dir["bin/*"].map(&File.method(:basename))
19
+ # s.default_executable = "example"
20
+ s.require_paths = ["lib"]
21
+
22
+ # Ruby version
23
+ s.required_ruby_version = ::Gem::Requirement.new("~> 2.0")
24
+
25
+ # dependencies
26
+ s.add_dependency('rack')
27
+ # development dependencies (add_development_dependency)
28
+ end
data/lib/gorack.rb ADDED
@@ -0,0 +1,121 @@
1
+ require 'stringio'
2
+ require 'rack'
3
+ require 'rack/builder'
4
+
5
+ module Gorack
6
+ class Server
7
+
8
+ DELIM = "\0" # response/request delimiter
9
+
10
+ attr_accessor :master_io, :app, :logger
11
+ def initialize(master_sock, app, app_options, logger)
12
+ @master_io = master_sock
13
+ @app, @app_options = app, app_options
14
+ @logger = logger
15
+ @pids = []
16
+ end
17
+
18
+ def run
19
+ logger.info("Accepting connections")
20
+ loop {
21
+ accept do |reader, writer|
22
+ add_pid Process.fork { handle(reader, writer) }
23
+ end
24
+
25
+ reap
26
+ }
27
+ end
28
+
29
+ def exit
30
+ logger.info("Exiting")
31
+ Process.waitall unless @pids.empty?
32
+ end
33
+
34
+ def accept(&block)
35
+ pipe = master_io.recv_io, master_io.recv_io
36
+ if block
37
+ pipe.each {|p| p.set_encoding(Encoding::BINARY) }
38
+ block.call(*pipe)
39
+ pipe.each(&:close)
40
+ end
41
+ pipe
42
+ end
43
+
44
+ def add_pid(pid)
45
+ @pids.push(pid)
46
+ end
47
+
48
+ def reap
49
+ new_pids = []
50
+ while pid = @pids.pop
51
+ Process.waitpid(pid, Process::WNOHANG) or new_pids.push(pid)
52
+ end
53
+ @pids = new_pids
54
+ end
55
+
56
+ def handle(reader, writer)
57
+ begin
58
+ request_env, body_reader = read_request(reader)
59
+
60
+ rack_env = {
61
+ "rack.version" => 1,
62
+ "rack.input" => body_reader,
63
+ "rack.errors" => STDERR,
64
+ "rack.multithread" => false,
65
+ "rack.multiprocess" => true,
66
+ "rack.run_once" => false,
67
+ "rack.url_scheme" => ["yes", "on", "1"].include?(request_env["HTTPS"]) ? "https" : "http"
68
+ }.merge(request_env)
69
+
70
+ status, headers, body = app.call(rack_env)
71
+
72
+ rescue => e
73
+ logger.info("ERROR: " + e.message)
74
+ logger.info("ERROR: " + e.backtrace.join("\n"))
75
+ status = 500
76
+ headers = {'Content-Type' => 'text/plain' }
77
+ body = ["Internal Server Error"]
78
+ end
79
+
80
+ write_response(writer, [status, headers, body])
81
+ end
82
+
83
+ private
84
+
85
+ def write_response(writer, resp)
86
+ status, headers, body = *resp
87
+
88
+ writer.write("#{status}#{DELIM}")
89
+ writer.write(headers.map {|k, v| "#{k}: #{v}#{DELIM}"}.join(''))
90
+ writer.write(DELIM)
91
+ # TODO: use IO.copy_stream
92
+ body.each(&writer.method(:write))
93
+ ensure
94
+ writer.close
95
+ body.close if body.respond_to?(:close)
96
+ end
97
+
98
+ def read_request(reader)
99
+ eol = eoh = false
100
+ buf = StringIO.new
101
+
102
+ while not eoh do
103
+ break if reader.eof?
104
+ buf.write(char = reader.read(1))
105
+ # TODO: write test for this
106
+ eoh = eol && char == DELIM
107
+ eol = char == DELIM
108
+ end
109
+
110
+ lines = buf.string.split(DELIM)
111
+ env = Hash[*lines.flat_map {|l| l.split(": ", 2)}]
112
+
113
+ # reader is at body start or EOF
114
+ # TODO: this assumes that body is not a stream
115
+ body = StringIO.new(reader.read)
116
+
117
+ [env, body]
118
+ end
119
+
120
+ end
121
+ end
Binary file
data/libexec/gorack ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set ft=ruby:
3
+
4
+ STDOUT.sync = STDERR.sync = true
5
+
6
+ require 'rack'
7
+ require 'socket'
8
+ require 'logger'
9
+ require_relative '../lib/gorack.rb'
10
+
11
+ logger = Logger.new(STDOUT)
12
+ logger.formatter = proc do |severity, datetime, progname, msg|
13
+ "[PID:#{Process.pid}] #{msg}\n"
14
+ end
15
+
16
+ app, app_options = Rack::Builder.parse_file(ARGV[0])
17
+ server = Gorack::Server.new(
18
+ UNIXSocket.for_fd(3), # IO descriptor inherited from parent process
19
+ app,
20
+ app_options,
21
+ logger
22
+ )
23
+
24
+ ppid = Process.pid
25
+
26
+ at_exit { server.exit if ppid == Process.pid }
27
+
28
+ server.run
@@ -0,0 +1,10 @@
1
+
2
+ run Proc.new { |env|
3
+ IO.copy_stream(env['rack.input'], sio = StringIO.new)
4
+
5
+ headers = {
6
+ 'X-This' => 'a messsage',
7
+ }
8
+
9
+ [201, headers, [sio.string]]
10
+ }
data/test/echo.ru ADDED
@@ -0,0 +1,8 @@
1
+ # vim: set ft=ruby:
2
+
3
+ run Proc.new {|env|
4
+ IO.copy_stream(env['rack.input'], body = StringIO.new)
5
+ except = [/rack\./]
6
+ env2 = env.delete_if {|k, v| except.any? {|ex| ex === k}}
7
+ [200, {}, [env.inspect, body.string] ]
8
+ }
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gorack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - http://github.com/gmarik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: 'Golang HTTP frontend server for Ruby''s Rack Applications. '
28
+ email: gmarik@gmail.com
29
+ executables:
30
+ - gorack
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - "./bin/gorack"
35
+ - "./gorack.gemspec"
36
+ - "./lib/gorack.rb"
37
+ - "./libexec/darwin_gorack"
38
+ - "./libexec/gorack"
39
+ - "./test/config_test.ru"
40
+ - "./test/echo.ru"
41
+ - bin/gorack
42
+ homepage: http://github.com/gmarik/example
43
+ licenses: []
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 2.2.2
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Golang HTTP frontend server for Ruby's Rack Applications
65
+ test_files: []
66
+ has_rdoc: false