rack-singleshot 0.1.0

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