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.
@@ -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
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'rack/singleshot'
4
+ require 'sinatra'
5
+
6
+ RSpec.configure do |config|
7
+ config.color_enabled = true
8
+ end
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