rack-singleshot 0.1.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.
- data/lib/rack/singleshot.rb +132 -0
- data/spec/singleshot_spec.rb +56 -0
- data/spec/spec_helper.rb +8 -0
- metadata +129 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Handler
|
5
|
+
class SingleShot
|
6
|
+
CRLF = "\r\n"
|
7
|
+
|
8
|
+
def self.run(app, options = {})
|
9
|
+
stdin = options.fetch(:stdin, $stdin)
|
10
|
+
stdout = options.fetch(:stdout, $stdout)
|
11
|
+
|
12
|
+
stdin.binmode = true if stdin.respond_to?(:binmode=)
|
13
|
+
stdout.binmode = true if stdout.respond_to?(:binmode=)
|
14
|
+
|
15
|
+
new(app, stdin, stdout).run
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(app, stdin, stdout)
|
19
|
+
@app, @stdin, @stdout = app, stdin, stdout
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
request = read_request
|
24
|
+
|
25
|
+
status, headers, body = @app.call(request)
|
26
|
+
|
27
|
+
write_response(status, headers, body)
|
28
|
+
ensure
|
29
|
+
@stdout.close
|
30
|
+
end
|
31
|
+
|
32
|
+
def read_request
|
33
|
+
buffer, extra = drain(@stdin, CRLF * 2)
|
34
|
+
|
35
|
+
heading, buffer = buffer.split(CRLF, 2)
|
36
|
+
|
37
|
+
verb, path, version = heading.split(' ')
|
38
|
+
|
39
|
+
headers = parse_headers(buffer)
|
40
|
+
|
41
|
+
if length = request_body_length(verb, headers)
|
42
|
+
body = StringIO.new(extra + @stdin.read(length - extra.size))
|
43
|
+
else
|
44
|
+
body = StringIO.new(extra)
|
45
|
+
end
|
46
|
+
|
47
|
+
env_for(verb, path, version, headers, body)
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_response(status, headers, body)
|
51
|
+
@stdout.write(['HTTP/1.1', status, Rack::Utils::HTTP_STATUS_CODES[status.to_i]].join(' ') << CRLF)
|
52
|
+
|
53
|
+
headers.each do |key, values|
|
54
|
+
values.split("\n").each do |value|
|
55
|
+
@stdout.write([key, value].join(": ") << CRLF)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
@stdout.write(CRLF)
|
60
|
+
|
61
|
+
body.each do |chunk|
|
62
|
+
@stdout.write(chunk)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_headers(raw_headers)
|
67
|
+
raw_headers.split(CRLF).inject({}) do |h, pair|
|
68
|
+
key, value = pair.split(": ")
|
69
|
+
h.update(header_key(key) => value)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def request_body_length(verb, headers)
|
74
|
+
return if %w[ POST PUT ].include?(verb.upcase)
|
75
|
+
|
76
|
+
if length = headers['CONTENT_LENGTH']
|
77
|
+
length.to_i
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def drain(socket, stop_at, chunksize = 1024)
|
82
|
+
buffer = ''
|
83
|
+
|
84
|
+
while(chunk = socket.readpartial(chunksize))
|
85
|
+
buffer << chunk
|
86
|
+
|
87
|
+
if buffer.include?(stop_at)
|
88
|
+
buffer, extra = buffer.split(stop_at, 2)
|
89
|
+
|
90
|
+
return buffer, extra
|
91
|
+
end
|
92
|
+
end
|
93
|
+
rescue EOFError
|
94
|
+
return buffer, ''
|
95
|
+
end
|
96
|
+
|
97
|
+
def env_for(verb, path, version, headers, body)
|
98
|
+
env = headers
|
99
|
+
|
100
|
+
scheme = ['yes', 'on', '1'].include?(env['HTTPS']) ? 'https' : 'http'
|
101
|
+
host = env['SERVER_NAME'] || env['HTTP_HOST']
|
102
|
+
|
103
|
+
uri = URI.parse([scheme, '://', host, path].join)
|
104
|
+
|
105
|
+
env.update 'REQUEST_METHOD' => verb
|
106
|
+
env.update 'SCRIPT_NAME' => ''
|
107
|
+
env.update 'PATH_INFO' => uri.path
|
108
|
+
env.update 'QUERY_STRING' => uri.query || ''
|
109
|
+
env.update 'SERVER_NAME' => uri.host
|
110
|
+
env.update 'SERVER_PORT' => uri.port.to_s
|
111
|
+
|
112
|
+
env.update 'rack.version' => Rack::VERSION
|
113
|
+
env.update 'rack.url_scheme' => uri.scheme
|
114
|
+
env.update 'rack.input' => body
|
115
|
+
env.update 'rack.errors' => $stderr
|
116
|
+
env.update 'rack.multithread' => false
|
117
|
+
env.update 'rack.multiprocess' => false
|
118
|
+
env.update 'rack.run_once' => true
|
119
|
+
|
120
|
+
env
|
121
|
+
end
|
122
|
+
|
123
|
+
def header_key(key)
|
124
|
+
key = key.upcase.gsub('-', '_')
|
125
|
+
|
126
|
+
%w[CONTENT_TYPE CONTENT_LENGTH SERVER_NAME].include?(key) ? key : "HTTP_#{key}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
register :singleshot, SingleShot
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Handler::SingleShot do
|
4
|
+
before(:each) do
|
5
|
+
@stdin, @in = IO.pipe
|
6
|
+
@out, @stdout = IO.pipe
|
7
|
+
|
8
|
+
@app = Rack::Lint.new(lambda {|env| [200, {'Content-Type' => 'text/plain'}, []] })
|
9
|
+
@server = Rack::Handler::SingleShot.new(@app, @stdin, @stdout)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can handle a simple request' do
|
13
|
+
@in << <<-REQUEST.gsub("\n", "\r\n")
|
14
|
+
GET / HTTP/1.1
|
15
|
+
Server-Name: localhost
|
16
|
+
|
17
|
+
REQUEST
|
18
|
+
|
19
|
+
@server.run
|
20
|
+
|
21
|
+
@out.read.should == <<-RESPONSE.gsub("\n", "\r\n")
|
22
|
+
HTTP/1.1 200 OK
|
23
|
+
Content-Type: text/plain
|
24
|
+
|
25
|
+
RESPONSE
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "Sinatra App" do
|
29
|
+
|
30
|
+
class App < Sinatra::Base
|
31
|
+
get '/' do
|
32
|
+
'response body'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
before(:each) do
|
37
|
+
@stdin, @in = IO.pipe
|
38
|
+
@out, @stdout = IO.pipe
|
39
|
+
|
40
|
+
@app = Rack::Lint.new(App.new)
|
41
|
+
@server = Rack::Handler::SingleShot.new(@app, @stdin, @stdout)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'can handle a sinatra request' do
|
45
|
+
@in << <<-REQUEST.gsub("\n", "\r\n")
|
46
|
+
GET / HTTP/1.1
|
47
|
+
Server-Name: localhost
|
48
|
+
|
49
|
+
REQUEST
|
50
|
+
|
51
|
+
@server.run
|
52
|
+
|
53
|
+
@out.read.should =~ /response body\Z/
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-singleshot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ben Burkert
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-02-14 00:00:00 +13:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rack
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rake
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: sinatra
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :development
|
76
|
+
version_requirements: *id004
|
77
|
+
description: |
|
78
|
+
Handles a single request to/from STDIN/STDOUT. Exits when the response
|
79
|
+
has finished. Sutable for running via inetd.
|
80
|
+
|
81
|
+
email:
|
82
|
+
- ben@benburkert.com
|
83
|
+
executables: []
|
84
|
+
|
85
|
+
extensions: []
|
86
|
+
|
87
|
+
extra_rdoc_files: []
|
88
|
+
|
89
|
+
files:
|
90
|
+
- ./lib/rack/singleshot.rb
|
91
|
+
- ./spec/singleshot_spec.rb
|
92
|
+
- ./spec/spec_helper.rb
|
93
|
+
has_rdoc: true
|
94
|
+
homepage: http://github.com/benburkert/rack-singleshot
|
95
|
+
licenses: []
|
96
|
+
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 3
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
version: "0"
|
120
|
+
requirements: []
|
121
|
+
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.6.2
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: A single-shot rack handler.
|
127
|
+
test_files:
|
128
|
+
- ./spec/singleshot_spec.rb
|
129
|
+
- ./spec/spec_helper.rb
|