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