aslakhellesoy-bcat 0.6.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.
- data/CONTRIBUTING +13 -0
- data/COPYING +19 -0
- data/INSTALLING +10 -0
- data/README +48 -0
- data/RELEASING +10 -0
- data/Rakefile +83 -0
- data/bcat.gemspec +60 -0
- data/bin/a2h +15 -0
- data/bin/bcat +79 -0
- data/bin/btee +2 -0
- data/contrib/bman +26 -0
- data/lib/bcat.rb +118 -0
- data/lib/bcat/ansi.rb +170 -0
- data/lib/bcat/browser.rb +68 -0
- data/lib/bcat/html.rb +106 -0
- data/lib/bcat/reader.rb +102 -0
- data/lib/bcat/server.rb +113 -0
- data/man/a2h.1 +55 -0
- data/man/a2h.1.ronn +39 -0
- data/man/bcat.1 +226 -0
- data/man/bcat.1.ronn +159 -0
- data/man/btee.1 +226 -0
- data/man/btee.1.ronn +159 -0
- data/man/index.html +195 -0
- data/test/contest.rb +68 -0
- data/test/test_bcat_a2h.rb +19 -0
- data/test/test_bcat_ansi.rb +121 -0
- data/test/test_bcat_browser.rb +12 -0
- data/test/test_bcat_head_parser.rb +56 -0
- metadata +97 -0
data/lib/bcat/ansi.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
class Bcat
|
2
|
+
|
3
|
+
# Converts ANSI color sequences to HTML.
|
4
|
+
#
|
5
|
+
# The ANSI module is based on code from the following libraries:
|
6
|
+
#
|
7
|
+
# ansi2html.sh:
|
8
|
+
# http://github.com/pixelb/scripts/blob/master/scripts/ansi2html.sh
|
9
|
+
#
|
10
|
+
# HTML::FromANSI:
|
11
|
+
# http://cpansearch.perl.org/src/NUFFIN/HTML-FromANSI-2.03/lib/HTML/FromANSI.pm
|
12
|
+
class ANSI
|
13
|
+
ESCAPE = "\x1b"
|
14
|
+
|
15
|
+
# Linux console palette
|
16
|
+
STYLES = {
|
17
|
+
'ef0' => 'color:#000',
|
18
|
+
'ef1' => 'color:#A00',
|
19
|
+
'ef2' => 'color:#0A0',
|
20
|
+
'ef3' => 'color:#A50',
|
21
|
+
'ef4' => 'color:#00A',
|
22
|
+
'ef5' => 'color:#A0A',
|
23
|
+
'ef6' => 'color:#0AA',
|
24
|
+
'ef7' => 'color:#AAA',
|
25
|
+
'ef8' => 'color:#555',
|
26
|
+
'ef9' => 'color:#F55',
|
27
|
+
'ef10' => 'color:#5F5',
|
28
|
+
'ef11' => 'color:#FF5',
|
29
|
+
'ef12' => 'color:#55F',
|
30
|
+
'ef13' => 'color:#F5F',
|
31
|
+
'ef14' => 'color:#5FF',
|
32
|
+
'ef15' => 'color:#FFF',
|
33
|
+
'eb0' => 'background-color:#000',
|
34
|
+
'eb1' => 'background-color:#A00',
|
35
|
+
'eb2' => 'background-color:#0A0',
|
36
|
+
'eb3' => 'background-color:#A50',
|
37
|
+
'eb4' => 'background-color:#00A',
|
38
|
+
'eb5' => 'background-color:#A0A',
|
39
|
+
'eb6' => 'background-color:#0AA',
|
40
|
+
'eb7' => 'background-color:#AAA',
|
41
|
+
'eb8' => 'background-color:#555',
|
42
|
+
'eb9' => 'background-color:#F55',
|
43
|
+
'eb10' => 'background-color:#5F5',
|
44
|
+
'eb11' => 'background-color:#FF5',
|
45
|
+
'eb12' => 'background-color:#55F',
|
46
|
+
'eb13' => 'background-color:#F5F',
|
47
|
+
'eb14' => 'background-color:#5FF',
|
48
|
+
'eb15' => 'background-color:#FFF'
|
49
|
+
}
|
50
|
+
|
51
|
+
##
|
52
|
+
# The default xterm 256 colour palette
|
53
|
+
|
54
|
+
(0..5).each do |red|
|
55
|
+
(0..5).each do |green|
|
56
|
+
(0..5).each do |blue|
|
57
|
+
c = 16 + (red * 36) + (green * 6) + blue
|
58
|
+
r = red > 0 ? red * 40 + 55 : 0
|
59
|
+
g = green > 0 ? green * 40 + 55 : 0
|
60
|
+
b = blue > 0 ? blue * 40 + 55 : 0
|
61
|
+
STYLES["ef#{c}"] = "color:#%2.2x%2.2x%2.2x" % [r, g, b]
|
62
|
+
STYLES["eb#{c}"] = "background-color:#%2.2x%2.2x%2.2x" % [r, g, b]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
(0..23).each do |gray|
|
68
|
+
c = gray+232
|
69
|
+
l = gray*10 + 8
|
70
|
+
STYLES["ef#{c}"] = "color:#%2.2x%2.2x%2.2x" % [l, l, l]
|
71
|
+
STYLES["eb#{c}"] = "background-color:#%2.2x%2.2x%2.2x" % [l, l, l]
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize(input)
|
75
|
+
@input =
|
76
|
+
if input.respond_to?(:to_str)
|
77
|
+
[input]
|
78
|
+
elsif !input.respond_to?(:each)
|
79
|
+
raise ArgumentError, "input must respond to each"
|
80
|
+
else
|
81
|
+
input
|
82
|
+
end
|
83
|
+
@stack = []
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_html
|
87
|
+
buf = []
|
88
|
+
each { |chunk| buf << chunk }
|
89
|
+
buf.join
|
90
|
+
end
|
91
|
+
|
92
|
+
def each
|
93
|
+
buf = ''
|
94
|
+
@input.each do |chunk|
|
95
|
+
buf << chunk
|
96
|
+
tokenize(buf) do |tok, data|
|
97
|
+
case tok
|
98
|
+
when :text
|
99
|
+
yield data
|
100
|
+
when :display
|
101
|
+
case code = data
|
102
|
+
when 0 ; yield reset_styles if @stack.any?
|
103
|
+
when 1 ; yield push_tag("b") # bright
|
104
|
+
when 2 ; #dim
|
105
|
+
when 3, 4 ; yield push_tag("u")
|
106
|
+
when 5, 6 ; yield push_tag("blink")
|
107
|
+
when 7 ; #reverse
|
108
|
+
when 8 ; yield push_style("display:none")
|
109
|
+
when 9 ; yield push_tag("strike")
|
110
|
+
when 30..37 ; yield push_style("ef#{code - 30}")
|
111
|
+
when 40..47 ; yield push_style("eb#{code - 40}")
|
112
|
+
when 90..97 ; yield push_style("ef#{8 + code - 90}")
|
113
|
+
when 100..107 ; yield push_style("eb#{8 + code - 100}")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
yield buf if !buf.empty?
|
119
|
+
yield reset_styles if @stack.any?
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
def push_tag(tag, style=nil)
|
124
|
+
style = STYLES[style] if style && !style.include?(':')
|
125
|
+
@stack.push tag
|
126
|
+
[ "<#{tag}",
|
127
|
+
(" style='#{style}'" if style),
|
128
|
+
">"
|
129
|
+
].join
|
130
|
+
end
|
131
|
+
|
132
|
+
def push_style(style)
|
133
|
+
push_tag "span", style
|
134
|
+
end
|
135
|
+
|
136
|
+
def reset_styles
|
137
|
+
stack, @stack = @stack, []
|
138
|
+
stack.reverse.map { |tag| "</#{tag}>" }.join
|
139
|
+
end
|
140
|
+
|
141
|
+
def tokenize(text)
|
142
|
+
tokens = [
|
143
|
+
# characters to remove completely
|
144
|
+
[/\A\x08+/, lambda { |m| '' }],
|
145
|
+
|
146
|
+
# ansi escape sequences that mess with the display
|
147
|
+
[/\A\x1b\[((?:\d{1,3};?)+|)m/, lambda { |m|
|
148
|
+
m = '0' if m.strip.empty?
|
149
|
+
m.chomp(';').split(';').
|
150
|
+
each { |code| yield :display, code.to_i };
|
151
|
+
'' }],
|
152
|
+
|
153
|
+
# malformed sequences
|
154
|
+
[/\A\x1b\[?[\d;]{0,3}/, lambda { |m| '' }],
|
155
|
+
|
156
|
+
# real text
|
157
|
+
[/\A([^\x1b\x08]+)/m, lambda { |m| yield :text, m; '' }]
|
158
|
+
]
|
159
|
+
|
160
|
+
while (size = text.size) > 0
|
161
|
+
tokens.each do |pattern, sub|
|
162
|
+
while text.sub!(pattern) { sub.call($1) }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
break if text.size == size
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
data/lib/bcat/browser.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
class Bcat
|
2
|
+
class Browser
|
3
|
+
ENVIRONMENT =
|
4
|
+
case `uname`
|
5
|
+
when /Darwin/ ; 'Darwin'
|
6
|
+
when /Linux/, /BSD/ ; 'X11'
|
7
|
+
else 'X11'
|
8
|
+
end
|
9
|
+
|
10
|
+
# browser name -> command mappings
|
11
|
+
COMMANDS = {
|
12
|
+
'Darwin' => {
|
13
|
+
'default' => "open",
|
14
|
+
'safari' => "open -a Safari",
|
15
|
+
'firefox' => "open -a Firefox",
|
16
|
+
'chrome' => "open -a Google\\ Chrome",
|
17
|
+
'chromium' => "open -a Chromium",
|
18
|
+
'opera' => "open -a Opera",
|
19
|
+
'curl' => "curl -s"
|
20
|
+
},
|
21
|
+
|
22
|
+
'X11' => {
|
23
|
+
'default' => "xdg-open",
|
24
|
+
'firefox' => "firefox",
|
25
|
+
'chrome' => "google-chrome",
|
26
|
+
'chromium' => "chromium",
|
27
|
+
'mozilla' => "mozilla",
|
28
|
+
'epiphany' => "epiphany",
|
29
|
+
'curl' => "curl -s"
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
# alternative names for browsers
|
34
|
+
ALIASES = {
|
35
|
+
'google-chrome' => 'chrome',
|
36
|
+
'google chrome' => 'chrome',
|
37
|
+
'gnome' => 'epiphany'
|
38
|
+
}
|
39
|
+
|
40
|
+
def initialize(browser, command=ENV['BCAT_COMMAND'])
|
41
|
+
@browser = browser
|
42
|
+
@command = command
|
43
|
+
end
|
44
|
+
|
45
|
+
def open(url)
|
46
|
+
fork do
|
47
|
+
$stdin.close
|
48
|
+
exec "#{command} #{shell_quote(url)}"
|
49
|
+
exit! 128
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def command
|
54
|
+
@command || browser_command
|
55
|
+
end
|
56
|
+
|
57
|
+
def browser_command(browser=@browser)
|
58
|
+
browser ||= 'default'
|
59
|
+
browser = browser.downcase
|
60
|
+
browser = ALIASES[browser] || browser
|
61
|
+
COMMANDS[ENVIRONMENT][browser]
|
62
|
+
end
|
63
|
+
|
64
|
+
def shell_quote(argument)
|
65
|
+
'"' + argument.to_s.gsub(/([\\"`$])/) { "\\" + $1 } + '"'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/bcat/html.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
class Bcat
|
2
|
+
|
3
|
+
# Parses HTML until the first displayable body character and provides methods
|
4
|
+
# for accessing head and body contents.
|
5
|
+
class HeadParser
|
6
|
+
attr_accessor :buf
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@buf = ''
|
10
|
+
@head = []
|
11
|
+
@body = nil
|
12
|
+
@html = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
# Called to parse new data as it arrives.
|
16
|
+
def feed(data)
|
17
|
+
if complete?
|
18
|
+
@body << data
|
19
|
+
else
|
20
|
+
@buf << data
|
21
|
+
parse(@buf)
|
22
|
+
end
|
23
|
+
complete?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Truthy once the first displayed character of the body has arrived.
|
27
|
+
def complete?
|
28
|
+
!@body.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
# Determine if the input is HTML. This is nil before the first non-whitespace
|
32
|
+
# character is received, true if the first non-whitespace character is a
|
33
|
+
# '<', and false if the first non-whitespace character is something other
|
34
|
+
# than '<'.
|
35
|
+
def html?
|
36
|
+
@html
|
37
|
+
end
|
38
|
+
|
39
|
+
# The head contents without any DOCTYPE, <html>, or <head> tags. This should
|
40
|
+
# consist of only <style>, <script>, <link>, <meta>, <base>, and <title> tags.
|
41
|
+
def head
|
42
|
+
@head.join.gsub(/<\/?(?:html|head|!DOCTYPE).*?>/mi, '')
|
43
|
+
end
|
44
|
+
|
45
|
+
# The current body contents. The <body> tag is guaranteed to be present. If
|
46
|
+
# a <body> was included in the input, it's preserved with original
|
47
|
+
# attributes; otherwise, a <body> tag is inserted. The inject argument can
|
48
|
+
# be used to insert a string as the immediate descendant of the <body> tag.
|
49
|
+
def body(inject=nil)
|
50
|
+
if @body =~ /\A\s*(<body.*?>)(.*)/mi
|
51
|
+
[$1, inject, $2].compact.join("\n")
|
52
|
+
else
|
53
|
+
["<body>", inject, @body].compact.join("\n")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
HEAD_TOKS = [
|
58
|
+
/\A(<!DOCTYPE.*?>)/m,
|
59
|
+
/\A(<title.*?>.*?<\/title>)/mi,
|
60
|
+
/\A(<script.*?>.*?<\/script>)/mi,
|
61
|
+
/\A(<style.*?>.*?<\/style>)/mi,
|
62
|
+
/\A(<(?:html|head|meta|link|base).*?>)/mi,
|
63
|
+
/\A(<\/(?:html|head|meta|link|base|script|style|title)>)/mi,
|
64
|
+
/\A(<!--(.*?)-->)/m
|
65
|
+
]
|
66
|
+
|
67
|
+
BODY_TOKS = [
|
68
|
+
/\A[^<]/,
|
69
|
+
/\A<(?!html|head|meta|link|base|script|style|title).*?>/
|
70
|
+
]
|
71
|
+
|
72
|
+
# Parses buf into head and body parts. Basic approach is to eat anything
|
73
|
+
# possibly body related until we hit text or a body element.
|
74
|
+
def parse(buf=@buf)
|
75
|
+
if @html.nil?
|
76
|
+
if buf =~ /\A\s*[<]/m
|
77
|
+
@html = true
|
78
|
+
elsif buf =~ /\A\s*[^<]/m
|
79
|
+
@html = false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
while !buf.empty?
|
84
|
+
buf.sub!(/\A(\s+)/m) { @head << $1 ; '' }
|
85
|
+
matched =
|
86
|
+
HEAD_TOKS.any? do |tok|
|
87
|
+
buf.sub!(tok) do
|
88
|
+
@head << $1
|
89
|
+
''
|
90
|
+
end
|
91
|
+
end
|
92
|
+
break unless matched
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
if buf.empty?
|
97
|
+
buf
|
98
|
+
elsif BODY_TOKS.any? { |tok| buf =~ tok }
|
99
|
+
@body = buf
|
100
|
+
nil
|
101
|
+
else
|
102
|
+
buf
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/bcat/reader.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
class Bcat
|
4
|
+
# ARGF style multi-file streaming interface. Input is read with IO#readpartial
|
5
|
+
# to avoid buffering.
|
6
|
+
class Reader
|
7
|
+
attr_reader :is_command
|
8
|
+
attr_reader :args
|
9
|
+
attr_reader :fds
|
10
|
+
|
11
|
+
def initialize(is_command, args=[])
|
12
|
+
@is_command = is_command
|
13
|
+
@args = args
|
14
|
+
end
|
15
|
+
|
16
|
+
def open
|
17
|
+
@fds = is_command ? open_command : open_files
|
18
|
+
@buf = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def open_command
|
22
|
+
[IO.popen(args.join(' '), 'rb')]
|
23
|
+
end
|
24
|
+
|
25
|
+
def open_files
|
26
|
+
args.map do |f|
|
27
|
+
if f == '-'
|
28
|
+
$stdin
|
29
|
+
else
|
30
|
+
File.open(f, 'rb')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def each
|
36
|
+
yield @buf.shift while @buf.any?
|
37
|
+
while fd = fds.first
|
38
|
+
fds.shift and next if fd.closed?
|
39
|
+
fd.sync = true
|
40
|
+
begin
|
41
|
+
while buf = fd.readpartial(4096)
|
42
|
+
yield buf
|
43
|
+
end
|
44
|
+
rescue EOFError
|
45
|
+
fd.close
|
46
|
+
end
|
47
|
+
fds.shift
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def sniff
|
52
|
+
@format ||=
|
53
|
+
catch :detect do
|
54
|
+
each do |chunk|
|
55
|
+
@buf << chunk
|
56
|
+
case chunk
|
57
|
+
when /\A\s*</m
|
58
|
+
throw :detect, 'html'
|
59
|
+
when /\A\s*[^<]/m
|
60
|
+
throw :detect, 'text'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
throw :detect, 'text'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Like Reader but writes all input to an output IO object in addition to
|
69
|
+
# yielding to the block.
|
70
|
+
class TeeFilter
|
71
|
+
def initialize(source, out=$stdout)
|
72
|
+
@source = source
|
73
|
+
@out = out
|
74
|
+
end
|
75
|
+
|
76
|
+
def each
|
77
|
+
@source.each do |chunk|
|
78
|
+
yield chunk
|
79
|
+
@out.write chunk
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class TextFilter
|
85
|
+
include Rack::Utils
|
86
|
+
|
87
|
+
def initialize(source, force=false)
|
88
|
+
@source = source
|
89
|
+
@force = force
|
90
|
+
end
|
91
|
+
|
92
|
+
def each
|
93
|
+
yield "<pre>"
|
94
|
+
@source.each do |chunk|
|
95
|
+
chunk = escape_html(chunk)
|
96
|
+
chunk = "<span>#{chunk}</span>" if !chunk.gsub!(/\n/, "<br>")
|
97
|
+
yield chunk
|
98
|
+
end
|
99
|
+
yield "</pre>"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/bcat/server.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'stringio'
|
3
|
+
require 'rack/utils'
|
4
|
+
|
5
|
+
class Bcat
|
6
|
+
# Simple Rack handler based largely on Scott Chacon's kidgloves library:
|
7
|
+
# http://github.com/schacon/kidgloves
|
8
|
+
class Server
|
9
|
+
attr_accessor :app
|
10
|
+
|
11
|
+
def self.run(app, options={}, &block)
|
12
|
+
new(app, options).listen(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(app, options={})
|
16
|
+
@app = app
|
17
|
+
@host = options[:Host] || '0.0.0.0'
|
18
|
+
@port = options[:Port] || 8089
|
19
|
+
end
|
20
|
+
|
21
|
+
def bind(host, port)
|
22
|
+
TCPServer.new(host, port)
|
23
|
+
rescue Errno::EADDRINUSE
|
24
|
+
port += 1
|
25
|
+
retry
|
26
|
+
end
|
27
|
+
|
28
|
+
def listen
|
29
|
+
server = TCPServer.new(@host, @port)
|
30
|
+
|
31
|
+
yield server if block_given?
|
32
|
+
|
33
|
+
loop do
|
34
|
+
socket = server.accept
|
35
|
+
socket.sync = true
|
36
|
+
log "#{socket.peeraddr[2]} (#{socket.peeraddr[3]})"
|
37
|
+
begin
|
38
|
+
req = {}
|
39
|
+
|
40
|
+
# parse the request line
|
41
|
+
request = socket.gets
|
42
|
+
method, path, version = request.split(" ", 3)
|
43
|
+
req["REQUEST_METHOD"] = method
|
44
|
+
info, query = path.split("?")
|
45
|
+
req["PATH_INFO"] = info
|
46
|
+
req["QUERY_STRING"] = query
|
47
|
+
|
48
|
+
# parse the headers
|
49
|
+
while (line = socket.gets)
|
50
|
+
line.strip!
|
51
|
+
break if line.size == 0
|
52
|
+
key, val = line.split(": ")
|
53
|
+
key = key.upcase.gsub('-', '_')
|
54
|
+
key = "HTTP_#{key}" if !%w[CONTENT_TYPE CONTENT_LENGTH].include?(key)
|
55
|
+
req[key] = val
|
56
|
+
end
|
57
|
+
|
58
|
+
# parse the body
|
59
|
+
body =
|
60
|
+
if len = req['CONTENT_LENGTH']
|
61
|
+
socket.read(len.to_i)
|
62
|
+
else
|
63
|
+
''
|
64
|
+
end
|
65
|
+
|
66
|
+
# process the request
|
67
|
+
process_request(req, body, socket)
|
68
|
+
ensure
|
69
|
+
socket.close if not socket.closed?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def log(message)
|
75
|
+
# $stderr.puts message
|
76
|
+
end
|
77
|
+
|
78
|
+
def status_message(code)
|
79
|
+
Rack::Utils::HTTP_STATUS_CODES[code]
|
80
|
+
end
|
81
|
+
|
82
|
+
def process_request(request, input_body, socket)
|
83
|
+
env = {}.replace(request)
|
84
|
+
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
85
|
+
env["QUERY_STRING"] ||= ""
|
86
|
+
env["SCRIPT_NAME"] = ""
|
87
|
+
|
88
|
+
rack_input = StringIO.new(input_body)
|
89
|
+
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
|
90
|
+
|
91
|
+
env.update(
|
92
|
+
"rack.version" => [1,0],
|
93
|
+
"rack.input" => rack_input,
|
94
|
+
"rack.errors" => $stderr,
|
95
|
+
"rack.multithread" => true,
|
96
|
+
"rack.multiprocess" => true,
|
97
|
+
"rack.run_once" => false,
|
98
|
+
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
|
99
|
+
)
|
100
|
+
status, headers, body = app.call(env)
|
101
|
+
begin
|
102
|
+
socket.write("HTTP/1.1 #{status} #{status_message(status)}\r\n")
|
103
|
+
headers.each do |k, vs|
|
104
|
+
vs.split("\n").each { |v| socket.write("#{k}: #{v}\r\n")}
|
105
|
+
end
|
106
|
+
socket.write("\r\n")
|
107
|
+
body.each { |s| socket.write(s) }
|
108
|
+
ensure
|
109
|
+
body.close if body.respond_to? :close
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|