line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package App::htmlcat; |
2
|
1
|
|
|
1
|
|
7
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
58
|
|
3
|
1
|
|
|
1
|
|
8
|
use warnings; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
42
|
|
4
|
1
|
|
|
1
|
|
30
|
use 5.008_001; |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
48
|
|
5
|
1
|
|
|
1
|
|
1578
|
use AnyEvent::Handle; |
|
1
|
|
|
|
|
31986
|
|
|
1
|
|
|
|
|
39
|
|
6
|
1
|
|
|
1
|
|
1182
|
use HTML::FromANSI::Tiny; |
|
1
|
|
|
|
|
1416
|
|
|
1
|
|
|
|
|
9
|
|
7
|
1
|
|
|
1
|
|
1098
|
use HTML::Entities; |
|
1
|
|
|
|
|
8581
|
|
|
1
|
|
|
|
|
87
|
|
8
|
1
|
|
|
1
|
|
905
|
use Data::Section::Simple qw(get_data_section); |
|
1
|
|
|
|
|
486
|
|
|
1
|
|
|
|
|
131
|
|
9
|
1
|
|
|
1
|
|
1925
|
use IO::Socket::INET; |
|
1
|
|
|
|
|
19916
|
|
|
1
|
|
|
|
|
9
|
|
10
|
1
|
|
|
1
|
|
1446
|
use Plack::Runner; |
|
1
|
|
|
|
|
11402
|
|
|
1
|
|
|
|
|
35
|
|
11
|
1
|
|
|
1
|
|
1150
|
use Encode; |
|
1
|
|
|
|
|
10559
|
|
|
1
|
|
|
|
|
1082
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION = '0.01'; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
sub new { |
16
|
0
|
|
|
0
|
1
|
|
my ($class, @args) = @_; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
my $self = bless { |
19
|
|
|
|
|
|
|
args => \@args, |
20
|
|
|
|
|
|
|
clients => {}, |
21
|
|
|
|
|
|
|
ansi => HTML::FromANSI::Tiny->new( |
22
|
|
|
|
|
|
|
auto_reverse => 1, |
23
|
|
|
|
|
|
|
no_plain_tags => 1, |
24
|
0
|
|
|
0
|
|
|
html_encode => sub { encode_entities($_[0], q("&<>)) }, |
25
|
0
|
|
|
|
|
|
), |
26
|
|
|
|
|
|
|
}, $class; |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
$self->{in} = AnyEvent::Handle->new( |
29
|
|
|
|
|
|
|
fh => \*STDIN, |
30
|
|
|
|
|
|
|
on_eof => sub { |
31
|
0
|
|
|
0
|
|
|
my ($handle) = @_; |
32
|
0
|
|
|
|
|
|
exit 0; |
33
|
|
|
|
|
|
|
}, |
34
|
|
|
|
|
|
|
on_error => sub { |
35
|
0
|
|
|
0
|
|
|
my ($handle, $fatal, $message) = @_; |
36
|
0
|
|
|
|
|
|
warn "stdin: $message\n"; |
37
|
0
|
|
|
|
|
|
$self->_broadcast($_[0]{rbuf}); |
38
|
0
|
|
|
|
|
|
exit 1; |
39
|
|
|
|
|
|
|
} |
40
|
0
|
|
|
|
|
|
); |
41
|
|
|
|
|
|
|
|
42
|
0
|
|
|
|
|
|
return $self; |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
sub _on_read_cb { |
46
|
0
|
|
|
0
|
|
|
my $self = shift; |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
return sub { |
49
|
0
|
|
|
0
|
|
|
my ($handle) = @_; |
50
|
0
|
|
|
|
|
|
$self->_broadcast($handle->rbuf); |
51
|
0
|
|
|
|
|
|
$handle->rbuf = ''; |
52
|
0
|
|
|
|
|
|
}; |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
sub _broadcast { |
56
|
0
|
|
|
0
|
|
|
my ($self, $data) = @_; |
57
|
|
|
|
|
|
|
|
58
|
0
|
|
|
|
|
|
open my $fh, '<', \$data; |
59
|
0
|
|
|
|
|
|
while (defined (my $line = <$fh>)) { |
60
|
0
|
|
|
|
|
|
$line = decode_utf8 $line; |
61
|
0
|
|
|
|
|
|
foreach my $client (values %{ $self->{clients} }){ |
|
0
|
|
|
|
|
|
|
62
|
0
|
|
|
|
|
|
$self->_push_line($client->{handle}, $line); |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
} |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
sub _push_line { |
68
|
0
|
|
|
0
|
|
|
my ($self, $handle, $line) = @_; |
69
|
0
|
|
|
|
|
|
$handle->push_write("data:" . Encode::encode("utf-8", scalar $self->{ansi}->html($line) ) ); |
70
|
0
|
|
|
|
|
|
$handle->push_write("\n"); |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
sub as_psgi { |
74
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
return sub { |
77
|
0
|
|
|
0
|
|
|
my $env = shift; |
78
|
|
|
|
|
|
|
|
79
|
0
|
0
|
|
|
|
|
$env->{'psgi.streaming'} or die 'psgi.streaming not supported'; |
80
|
|
|
|
|
|
|
|
81
|
0
|
0
|
|
|
|
|
if ($env->{PATH_INFO} eq '/stream') { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
82
|
|
|
|
|
|
|
return sub { |
83
|
0
|
|
|
|
|
|
my $respond = shift; |
84
|
|
|
|
|
|
|
|
85
|
0
|
|
|
|
|
|
my $remote_addr = $env->{REMOTE_ADDR}; |
86
|
|
|
|
|
|
|
|
87
|
0
|
|
|
|
|
|
my $writer = $respond->([ |
88
|
|
|
|
|
|
|
200, [ |
89
|
|
|
|
|
|
|
'Content-Type' => 'text/event-stream; charset=utf-8', |
90
|
|
|
|
|
|
|
'Cache-Control' => 'no-cache' |
91
|
|
|
|
|
|
|
] |
92
|
|
|
|
|
|
|
]); |
93
|
|
|
|
|
|
|
|
94
|
0
|
|
|
|
|
|
my $io = $env->{'psgix.io'}; |
95
|
|
|
|
|
|
|
my $handle = AnyEvent::Handle->new( |
96
|
|
|
|
|
|
|
fh => $io, |
97
|
|
|
|
|
|
|
on_error => sub { |
98
|
0
|
|
|
|
|
|
my ($handle, $fatal, $message) = @_; |
99
|
0
|
|
|
|
|
|
warn "client [$remote_addr]: $message\n"; |
100
|
0
|
|
|
|
|
|
delete $self->{clients}->{ 0+$io }; |
101
|
0
|
0
|
|
|
|
|
if (keys %{$self->{clients}} == 0) { |
|
0
|
|
|
|
|
|
|
102
|
0
|
|
|
|
|
|
$self->{in}->on_read(); |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
} |
105
|
0
|
|
|
|
|
|
); |
106
|
|
|
|
|
|
|
|
107
|
0
|
|
|
|
|
|
$self->{clients}->{ 0+$io } = { |
108
|
|
|
|
|
|
|
handle => $handle, |
109
|
|
|
|
|
|
|
writer => $writer, # keep reference |
110
|
|
|
|
|
|
|
}; |
111
|
0
|
|
|
|
|
|
$self->{in}->on_read($self->_on_read_cb); |
112
|
0
|
|
|
|
|
|
}; |
113
|
|
|
|
|
|
|
} elsif ($env->{PATH_INFO} eq '/css') { |
114
|
0
|
|
|
|
|
|
return [ 200, [ 'Content-Type' => 'text/css' ], [ $self->{ansi}->css ] ]; |
115
|
|
|
|
|
|
|
} elsif ($env->{PATH_INFO} eq '/js') { |
116
|
0
|
|
|
|
|
|
return [ 200, [ 'Content-Type' => 'text/javascript' ], [ get_data_section('js') ] ]; |
117
|
|
|
|
|
|
|
} elsif ($env->{PATH_INFO} eq '/') { |
118
|
0
|
|
|
|
|
|
return [ 200, [ 'Content-Type' => 'text/html; charset=utf-8' ], [ get_data_section('html') ] ]; |
119
|
|
|
|
|
|
|
} else { |
120
|
0
|
|
|
|
|
|
return [ 404, [], [] ]; |
121
|
|
|
|
|
|
|
} |
122
|
0
|
|
|
|
|
|
}; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub run { |
126
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
127
|
0
|
|
|
|
|
|
my $runner = Plack::Runner->new(app => $self->as_psgi); |
128
|
0
|
|
|
|
|
|
$runner->parse_options( |
129
|
|
|
|
|
|
|
'--env' => 'production', |
130
|
|
|
|
|
|
|
'--port' => _empty_port(), |
131
|
0
|
|
|
|
|
|
@{ $self->{args} } |
132
|
|
|
|
|
|
|
); |
133
|
|
|
|
|
|
|
|
134
|
0
|
0
|
|
|
|
|
if (my $exec = { @{$runner->{options}} }->{exec}) { |
|
0
|
|
|
|
|
|
|
135
|
0
|
|
|
|
|
|
push @{ $runner->{options} }, server_ready => sub { |
136
|
0
|
|
|
0
|
|
|
my ($args) = @_; |
137
|
0
|
|
0
|
|
|
|
my $host = $args->{host} || 'localhost'; |
138
|
0
|
|
0
|
|
|
|
my $proto = $args->{proto} || 'http'; |
139
|
0
|
|
|
|
|
|
system "$exec $proto://$host:$args->{port}/"; |
140
|
0
|
|
|
|
|
|
}; |
141
|
|
|
|
|
|
|
} else { |
142
|
0
|
|
|
|
|
|
push @{ $runner->{options} }, server_ready => sub { |
143
|
0
|
|
|
0
|
|
|
my ($args) = @_; |
144
|
0
|
|
0
|
|
|
|
my $host = $args->{host} || 'localhost'; |
145
|
0
|
|
0
|
|
|
|
my $proto = $args->{proto} || 'http'; |
146
|
0
|
|
|
|
|
|
print STDERR "$0: $proto://$host:$args->{port}/\n"; |
147
|
0
|
|
|
|
|
|
}; |
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
|
150
|
0
|
|
|
|
|
|
$runner->run; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# from Test::TCP |
154
|
|
|
|
|
|
|
sub _empty_port { |
155
|
0
|
|
0
|
0
|
|
|
my $port = $ENV{HTTPCAT_PORT} || 45192 + int(rand() * 1000); |
156
|
|
|
|
|
|
|
|
157
|
0
|
|
|
|
|
|
while ($port++ < 60000) { |
158
|
0
|
|
|
|
|
|
my $remote = IO::Socket::INET->new( |
159
|
|
|
|
|
|
|
Proto => 'tcp', |
160
|
|
|
|
|
|
|
PeerAddr => '127.0.0.1', |
161
|
|
|
|
|
|
|
PeerPort => $port, |
162
|
|
|
|
|
|
|
); |
163
|
|
|
|
|
|
|
|
164
|
0
|
0
|
|
|
|
|
if ($remote) { |
165
|
0
|
|
|
|
|
|
close $remote; |
166
|
|
|
|
|
|
|
} else { |
167
|
0
|
|
|
|
|
|
return $port; |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
0
|
|
|
|
|
|
die 'Could not find empty port'; |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
__DATA__ |