1 | #! perl # optional depends=tcp |
1 | #! perl # optional depends=tcp |
2 | |
2 | |
3 | # http server - this tried to speak enough of http 1.1 (and 1.0) |
3 | # http server - this tries to speak enough of http 1.1 (and 1.0) |
4 | # to work with browsers. it does not even attempt to be a complete |
4 | # to work with browsers. it does not even attempt to be a complete |
5 | # implementation, although it should be mostly correct for that it does. |
5 | # implementation, although it should be mostly correct for that it does. |
6 | |
6 | |
7 | sub send { |
7 | sub send { |
8 | my $self = $_[0]; |
8 | my $self = $_[0]; |
… | |
… | |
17 | |
17 | |
18 | $self->{ww} = AE::io $self->{fh}, 1, sub { |
18 | $self->{ww} = AE::io $self->{fh}, 1, sub { |
19 | my $len = syswrite $self->{fh}, $self->{wbuf}; |
19 | my $len = syswrite $self->{fh}, $self->{wbuf}; |
20 | substr $self->{wbuf}, 0, $len, ""; |
20 | substr $self->{wbuf}, 0, $len, ""; |
21 | |
21 | |
|
|
22 | delete $self->{ww} unless $len; # in case of errors, stop |
22 | $self->{ww}->stop unless length $self->{wbuf}; |
23 | delete $self->{ww} unless length $self->{wbuf}; |
23 | } if length $self->{wbuf}; |
24 | } if length $self->{wbuf}; |
24 | } |
25 | } |
25 | } |
26 | } |
26 | |
27 | |
27 | sub fatal { |
28 | sub fatal { |
28 | my ($self) = @_; |
29 | my ($self) = @_; |
29 | |
30 | |
30 | $self->send ("HTTP/1.1 500 internal error\015\012"); |
31 | $self->send ("HTTP/1.1 500 internal error\015\012"); |
|
|
32 | } |
|
|
33 | |
|
|
34 | sub respond_304 { |
|
|
35 | $_[0]->send ("HTTP/1.1 304 cache\015\012" |
|
|
36 | . $_[0]{ohdr} |
|
|
37 | . "\015\012"); |
31 | } |
38 | } |
32 | |
39 | |
33 | sub respond { |
40 | sub respond { |
34 | $_[0]->send ("HTTP/1.1 $_[1]\015\012" |
41 | $_[0]->send ("HTTP/1.1 $_[1]\015\012" |
35 | . "content-length: " . (0 + length $_[2]) . "\015\012" |
42 | . "content-length: " . (0 + length $_[2]) . "\015\012" |
36 | . "access-control-allow-origin: *\015\012" |
43 | . "access-control-allow-origin: *\015\012" |
37 | . $_[0]{ohdr} |
44 | . $_[0]{ohdr} |
38 | . "$_[3]\015\012" . ($_[0]{give_head} ? "" : $_[2])); |
45 | . "$_[3]\015\012" . ($_[0]{give_head} ? "" : $_[2])); |
39 | } |
46 | } |
40 | |
47 | |
41 | my $cache_headers = "cache-control: max-age=8640000\015\012" |
48 | my $cache_headers = "cache-control: public, max-age=8640000\015\012" |
42 | . "etag: \"0\"\015\012"; |
49 | . "etag: \"0\"\015\012"; |
43 | |
50 | |
|
|
51 | my $revalidate_headers = "cache-control: public, max-age=60, must-revalidate\015\012"; |
|
|
52 | |
|
|
53 | my $nocache_headers = "cache-control: no-cache, max-age=0\015\012"; |
|
|
54 | |
44 | sub content_type { |
55 | sub content_type($$) { |
45 | return "content-type: image/png\015\012" if $_[0] =~ /^\x89PNG/; |
56 | return "content-type: image/png\015\012" if $_[1] =~ /^\x89PNG/; |
46 | return "content-type: image/jpeg\015\012" if $_[0] =~ /^......JFIF/s; |
57 | return "content-type: image/jpeg\015\012" if $_[1] =~ /^......JFIF/s; |
47 | return "content-type: audio/wav\015\012" if $_[0] =~ /^RIFF/; |
58 | return "content-type: audio/wav\015\012" if $_[1] =~ /^RIFF/; |
48 | return "content-type: audio/ogg\015\012" if $_[0] =~ /^OggS/; |
59 | return "content-type: audio/ogg\015\012" if $_[1] =~ /^OggS/; |
|
|
60 | return "content-type: text/css; charset=utf-8\015\012" if $_[0] =~ /\.css$/; |
|
|
61 | return "content-type: application/javascript; charset=utf-8\015\012" if $_[0] =~ /\.js$/; |
49 | return "content-type: text/html\015\012" if $_[0] =~ /^</; |
62 | return "content-type: text/html; charset=utf-8\015\012" if $_[1] =~ /^</; |
50 | |
63 | |
51 | "content-type: text/plain\015\012" |
64 | "content-type: text/plain; charset=utf-8\015\012" |
52 | } |
65 | } |
53 | |
66 | |
54 | sub handle_con { |
67 | sub handle_con { |
55 | my ($self) = @_; |
68 | my ($self) = @_; |
56 | |
69 | |
… | |
… | |
70 | $http = $3; |
83 | $http = $3; |
71 | |
84 | |
72 | $uri =~ s%^http://[^/]*%%i; # just in case |
85 | $uri =~ s%^http://[^/]*%%i; # just in case |
73 | $uri =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr hex $1/ge; # %-decode |
86 | $uri =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr hex $1/ge; # %-decode |
74 | |
87 | |
|
|
88 | if (0) { |
75 | # my %hdr; |
89 | my %hdr; |
76 | # |
90 | |
77 | # $hdr{lc $1} .= ",$2" |
91 | $hdr{lc $1} .= ",$2" |
78 | # while $req =~ /\G |
92 | while $req =~ /\G |
79 | # ([^:\000-\037]*): |
93 | ([^:\000-\037]*): |
80 | # [\011\040]* |
94 | [\011\040]* |
81 | # ((?: [^\012]+ | \012[\011\040] )*) |
95 | ((?: [^\012]+ | \012[\011\040] )*) |
82 | # \012 |
96 | \012 |
83 | # /gxc; |
97 | /gxc; |
84 | # |
98 | |
85 | # $req =~ /\G$/ |
99 | $req =~ /\G$/ |
86 | # or return $self->respond ("400 bad request"); |
100 | or return $self->respond ("400 bad request"); |
87 | # |
101 | |
88 | # # remove the "," prefix we added to all headers above |
102 | # remove the "," prefix we added to all headers above |
89 | # substr $_, 0, 1, "" |
103 | substr $_, 0, 1, "" |
90 | # for values %hdr; |
104 | for values %hdr; |
|
|
105 | } |
91 | |
106 | |
92 | if ($http == 1.0) { |
107 | if ($http == 1.0) { |
93 | if ($req =~ /^connection\s*:\s*keep-alive/mi) { |
108 | if ($req =~ /^connection\s*:\s*keep-alive/mi) { |
94 | $self->{ohdr} = "connection: keep-alive\015\012"; |
109 | $self->{ohdr} = "connection: keep-alive\015\012"; |
95 | } else { |
110 | } else { |
… | |
… | |
112 | $self->send ("HTTP/1.1 100 go on\015\012") |
127 | $self->send ("HTTP/1.1 100 go on\015\012") |
113 | if $req =~ /^expect:.*\b100-continue\b/i; |
128 | if $req =~ /^expect:.*\b100-continue\b/i; |
114 | |
129 | |
115 | if ($uri =~ m%^/([0-9a-f]+)(M?)$%) { # faces |
130 | if ($uri =~ m%^/([0-9a-f]+)(M?)$%) { # faces |
116 | my $want_meta = $2; |
131 | my $want_meta = $2; |
117 | my $idx = $cf::FACEHASH{pack "H*", $1}; |
132 | my $idx = $cf::face::HASH{pack "H*", $1}; |
118 | |
133 | |
119 | $idx |
134 | $idx |
120 | or do { $self->respond ("404 illegal face name"), next }; |
135 | or do { $self->respond ("404 illegal face name"), next }; |
121 | |
136 | |
122 | if ($req =~ /if-none-match/i) { # dirtiest hack evar |
137 | if ($req =~ /if-none-match/i) { # dirtiest hack evar |
123 | $self->respond ("304 not modified", "", $cache_headers); |
138 | $self->respond_304; |
124 | next; |
139 | next; |
125 | } |
140 | } |
126 | |
141 | |
127 | my $type = cf::face::get_type $idx, 1; |
142 | my $type = cf::face::get_type $idx; |
128 | my $data = cf::face::get_data $idx, 1; |
143 | my $data = cf::face::get_data $idx; |
129 | |
144 | |
130 | (my $meta, $data) = unpack "(w/a*)*", $data |
145 | (my $meta, $data) = unpack "(w/a*)*", $data |
131 | if $type & 1; |
146 | if $type & 1; |
132 | |
147 | |
133 | if ($want_meta) { |
148 | if ($want_meta) { |
134 | if ($type & 1) { |
149 | if ($type & 1) { |
135 | $self->respond ("200 OK", $meta, "content-type: text/plain\015\012" . $cache_headers); |
150 | $self->respond ("200 OK", $meta, "content-type: text/plain; charset=utf-8\015\012" . $cache_headers); |
136 | } else { |
151 | } else { |
137 | $self->respond ("404 type $type has no metadata"); |
152 | $self->respond ("404 type $type has no metadata"); |
138 | } |
153 | } |
139 | } else { |
154 | } else { |
140 | $self->respond ("200 OK", $data, (content_type $data) . $cache_headers); |
155 | $self->respond ("200 OK", $data, (content_type $uri, $data) . $cache_headers); |
141 | } |
156 | } |
142 | |
157 | |
143 | } elsif (my $idx = (cf::face::find "res/http$uri") || (cf::face::find "res/http${uri}index.html")) { |
158 | } elsif (my $idx = (cf::face::find "res/http$uri") || (cf::face::find "res/http${uri}index.html")) { |
144 | # TODO: use etag (shudder) |
|
|
145 | my $data = cf::face::get_data $idx, 1; |
159 | my $data = cf::face::get_data $idx; |
|
|
160 | my $csum = unpack "H*", cf::face::get_csum $idx; |
|
|
161 | my $hdr = "etag: \"$csum\"\015\012" . $revalidate_headers; |
|
|
162 | |
|
|
163 | if ($req =~ /if-none-match: "$csum"/i) { # dirtiest hack evar |
|
|
164 | $self->respond_304; |
|
|
165 | next; |
|
|
166 | } |
|
|
167 | |
146 | $self->respond ("200 OK", $data, (content_type $data) . $cache_headers); |
168 | $self->respond ("200 OK", $data, (content_type $uri, $data) . $hdr); |
147 | |
169 | |
148 | } elsif (cf::face::find "res/http$uri/index.html") { |
170 | } elsif (cf::face::find "res/http$uri/index.html") { |
149 | $self->respond ("302 dirslash", "", "location: $uri/\015\012"); |
171 | $self->respond ("302 dirslash", "", "location: $uri/\015\012" . $nocache_headers); |
150 | |
172 | |
151 | } elsif ($uri eq "/debug") { # for debugging |
173 | } elsif ($uri eq "/debug") { # for debugging |
152 | my @body = "<html><body>"; |
174 | my @body = <<EOF; |
153 | |
175 | <html><head><style> |
|
|
176 | th:nth-child(1) { text-align: right; } |
|
|
177 | th:nth-child(2) { text-align: left; } |
|
|
178 | th:nth-child(3) { text-align: right; } |
|
|
179 | th:nth-child(4) { text-align: left; } |
|
|
180 | th:nth-child(5) { text-align: left; } |
|
|
181 | td:nth-child(1) { text-align: right; font-family: monospace;} |
|
|
182 | td:nth-child(2) { text-align: left; font-family: monospace;} |
|
|
183 | td:nth-child(3) { text-align: right; font-family: monospace;} |
|
|
184 | td:nth-child(4) { text-align: left; } |
|
|
185 | td:nth-child(5) { text-align: left; } |
|
|
186 | </style><body> |
|
|
187 | EOF |
154 | for my $type (6, 5, 4, 3, 2, 1, 0) { |
188 | for my $type (6, 5, 4, 3, 2, 1, 0) { |
155 | push @body, "<h1>$type</h1>"; |
189 | push @body, "<h1>$type</h1><table><tr><th>#</th><th>csum</th><th>size</th><th>name</th><th>meta</th></tr>"; |
156 | |
190 | |
157 | for (1 .. cf::face::faces_size - 1) { |
191 | for (1 .. cf::face::faces_size - 1) { |
158 | cf::cede_to_tick; |
192 | cf::cede_to_tick; |
159 | |
193 | |
160 | next if $type != cf::face::get_type $_; |
194 | next if $type != cf::face::get_type $_; |
161 | my $name = cf::face::get_name $_; |
195 | my $name = cf::face::get_name $_; |
162 | my $id = unpack "H*", cf::face::get_chksum $_, 1; |
196 | my $id = unpack "H*", cf::face::get_csum $_, 0; |
163 | push @body, "$_ <a href='$id'>$name ($id)</a>"; |
197 | push @body, "<tr><td>$_</td><td>$id</td><td>$cf::face::SIZE[0][$_]</td><td><a href='$id'>$name</a></td>"; |
164 | push @body, " <a href='${id}M'>(meta)</a>" if $type & 1; |
198 | push @body, "<td><a href='${id}M'>meta</a>" if $type & 1; |
165 | push @body, "<br>"; |
199 | push @body, "</tr>"; |
166 | } |
200 | } |
|
|
201 | |
|
|
202 | push @body, "</table>"; |
167 | } |
203 | } |
168 | |
204 | |
169 | push @body, "</body></html>"; |
205 | push @body, "</body></html>"; |
170 | |
206 | |
171 | my $body = join "", @body; |
207 | my $body = join "", @body; |
172 | utf8::encode $body; |
208 | utf8::encode $body; |
173 | $self->respond ("200 OK", $body, "content-type: text/html\015\012"); |
209 | $self->respond ("200 OK", $body, "content-type: text/html; charset=utf-8\015\012"); |
174 | |
210 | |
175 | } else { |
211 | } else { |
176 | $self->respond ("404 not found"); |
212 | $self->respond ("404 not found"); |
177 | } |
213 | } |
178 | |
214 | |
… | |
… | |
204 | $self->{async} = Coro::async_pool { |
240 | $self->{async} = Coro::async_pool { |
205 | $Coro::current->nice (4); |
241 | $Coro::current->nice (4); |
206 | $Coro::current->{desc} = "http $self->{id}"; |
242 | $Coro::current->{desc} = "http $self->{id}"; |
207 | |
243 | |
208 | $self->handle_con; |
244 | $self->handle_con; |
209 | |
|
|
210 | # this is ultra-ugly, but overall, fast |
|
|
211 | # Coro::AnyEvent::writable $self->{fh} |
|
|
212 | # while length $self->{wbuf}; |
|
|
213 | |
|
|
214 | # %$self = (); |
|
|
215 | }; |
245 | }; |
216 | }; |
246 | }; |
217 | |
247 | |
218 | cf::register_exticmd http_faceurl => sub { |
248 | cf::register_exticmd http_faceurl => sub { |
219 | my ($ns) = @_; |
249 | my ($ns) = @_; |