]> git.proxmox.com Git - pve-manager.git/blame - PVE/HTTPServer.pm
add tls_ctx to proxy requests
[pve-manager.git] / PVE / HTTPServer.pm
CommitLineData
57f93db1
DM
1package PVE::HTTPServer;
2
3use strict;
4use warnings;
5use Socket qw(IPPROTO_TCP TCP_NODELAY SOMAXCONN);
6use POSIX qw(strftime EINTR EAGAIN);
7use Fcntl;
8use File::stat qw();
9use AnyEvent::Strict;
10use AnyEvent::Util qw(guard fh_nonblocking WSAEWOULDBLOCK WSAEINPROGRESS);
11use AnyEvent::Handle;
12use AnyEvent::TLS;
13use AnyEvent::IO;
14use AnyEvent::HTTP;
15use Fcntl ();
16use Compress::Zlib;
17use PVE::SafeSyslog;
18use PVE::INotify;
19use PVE::RPCEnvironment;
20use PVE::REST;
21
22use URI;
23use HTTP::Status qw(:constants);
24use HTTP::Headers;
25use HTTP::Response;
26
27use CGI; # fixme: remove this!
28# DOS attack prevention
29# fixme: remove CGI.pm
30$CGI::DISABLE_UPLOADS = 1; # no uploads
31$CGI::POST_MAX = 1024 * 10; # max 10K posts
32
57f93db1
DM
33use Data::Dumper; # fixme: remove
34
35my $known_methods = {
36 GET => 1,
37 POST => 1,
38 PUT => 1,
39 DELETE => 1,
40};
41
42sub log_request {
43 my ($self, $reqstate) = @_;
44
57f93db1
DM
45 my $loginfo = $reqstate->{log};
46
47 # like apache2 common log format
48 # LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\""
49
50 my $peerip = $reqstate->{peer_host} || '-';
51 my $userid = $loginfo->{userid} || '-';
52 my $content_length = defined($loginfo->{content_length}) ? $loginfo->{content_length} : '-';
53 my $code = $loginfo->{code} || 500;
54 my $requestline = $loginfo->{requestline} || '-';
55 my $timestr = strftime("%d/%b/%Y:%H:%M:%S %z", localtime());
56
57 my $msg = "$peerip - $userid [$timestr] \"$requestline\" $code $content_length\n";
58
c2e9823c 59 $self->write_log($msg);
57f93db1
DM
60}
61
62sub log_aborted_request {
63 my ($self, $reqstate, $error) = @_;
64
65 my $r = $reqstate->{request};
66 return if !$r; # no active request
67
68 if ($error) {
69 syslog("err", "problem with client $reqstate->{peer_host}; $error");
70 }
f91072d5 71
57f93db1
DM
72 $self->log_request($reqstate);
73}
74
75sub client_do_disconnect {
76 my ($self, $reqstate) = @_;
77
78 my $hdl = delete $reqstate->{hdl};
79
80 if (!$hdl) {
81 syslog('err', "detected empty handle");
82 return;
83 }
84
85 #print "close connection $hdl\n";
86
87 shutdown($hdl->{fh}, 1);
88 # clear all handlers
f91072d5 89 $hdl->on_drain(undef);
57f93db1
DM
90 $hdl->on_read(undef);
91 $hdl->on_eof(undef);
92 $self->{conn_count}--;
93
f91072d5 94 print "$$: CLOSE FH" . $hdl->{fh}->fileno() . " CONN$self->{conn_count}\n" if $self->{debug};
57f93db1
DM
95}
96
97sub finish_response {
98 my ($self, $reqstate) = @_;
99
100 my $hdl = $reqstate->{hdl};
101
102 delete $reqstate->{log};
103 delete $reqstate->{request};
104 delete $reqstate->{proto};
105
106 if (!$self->{end_loop} && $reqstate->{keep_alive} > 0) {
107 # print "KEEPALIVE $reqstate->{keep_alive}\n";
f91072d5 108 $hdl->on_read(sub {
57f93db1
DM
109 eval { $self->push_request_header($reqstate); };
110 warn $@ if $@;
111 });
112 } else {
113 $hdl->on_drain (sub {
f91072d5
DM
114 eval {
115 $self->client_do_disconnect($reqstate);
116 };
57f93db1
DM
117 warn $@ if $@;
118 });
119 }
120}
121
122sub response {
123 my ($self, $reqstate, $resp, $mtime, $nocomp) = @_;
124
125 #print "$$: send response: " . Dumper($resp);
126
127 my $code = $resp->code;
128 my $msg = $resp->message || HTTP::Status::status_message($code);
129 ($msg) = $msg =~m/^(.*)$/m;
130 my $content = $resp->content;
131
132 if ($code =~ /^(1\d\d|[23]04)$/) {
133 # make sure content we have no content
134 $content = "";
135 }
136
137 $reqstate->{keep_alive} = 0 if ($code >= 300) || $self->{end_loop};
138
139 $reqstate->{log}->{code} = $code;
140
141 my $res = "HTTP/1.0 $code $msg\015\012";
f91072d5 142
57f93db1
DM
143 my $ctime = time();
144 my $date = HTTP::Date::time2str($ctime);
145 $resp->header('Date' => $date);
146 if ($mtime) {
147 $resp->header('Last-Modified' => HTTP::Date::time2str($mtime));
148 } else {
149 $resp->header('Expires' => $date);
150 $resp->header('Cache-Control' => "max-age=0");
151 $resp->header("Pragma", "no-cache");
152 }
153
154 $resp->header('Server' => "pve-api-daemon/3.0");
155
156 my $content_length;
f91072d5 157 if ($content) {
57f93db1
DM
158
159 $content_length = length($content);
160
161 if (!$nocomp && ($content_length > 1024)) {
162 my $comp = Compress::Zlib::memGzip($content);
163 $resp->header('Content-Encoding', 'gzip');
164 $content = $comp;
165 $content_length = length($content);
166 }
167 $resp->header("Content-Length" => $content_length);
168 $reqstate->{log}->{content_length} = $content_length;
f91072d5 169
57f93db1
DM
170 } else {
171 $resp->remove_header("Content-Length");
172 }
f91072d5 173
57f93db1
DM
174 if ($reqstate->{keep_alive} > 0) {
175 $resp->push_header('Connection' => 'Keep-Alive');
176 } else {
177 $resp->header('Connection' => 'close');
178 }
179
180 $res .= $resp->headers_as_string("\015\012");
f91072d5 181 #print "SEND(without content) $res\n" if $self->{debug};
57f93db1
DM
182
183 $res .= "\015\012";
184 $res .= $content;
185
186 $self->log_request($reqstate, $reqstate->{request});
187
188 $reqstate->{hdl}->push_write($res);
189 $self->finish_response($reqstate);
190}
191
192sub error {
193 my ($self, $reqstate, $code, $msg, $hdr, $content) = @_;
194
195 eval {
f91072d5 196 my $resp = HTTP::Response->new($code, $msg, $hdr, $content);
57f93db1
DM
197 $self->response($reqstate, $resp);
198 };
199 warn $@ if $@;
200}
201
202sub send_file_start {
203 my ($self, $reqstate, $filename) = @_;
204
205 eval {
206 # print "SEND FILE $filename\n";
f91072d5 207 # Note: aio_load() this is not really async unless we use IO::AIO!
57f93db1
DM
208 eval {
209
210 my $fh = IO::File->new($filename, '<') ||
211 die "$!\n";
212 my $stat = File::stat::stat($fh) ||
213 die "$!\n";
f91072d5 214
57f93db1
DM
215 my $data;
216 my $len = sysread($fh, $data, $stat->size);
217 die "got short file\n" if !defined($len) || $len != $stat->size;
218
219 my $ct;
220 if ($filename =~ m/\.css$/) {
221 $ct = 'text/css';
f91072d5 222 } elsif ($filename =~ m/\.js$/) {
57f93db1 223 $ct = 'application/javascript';
f91072d5 224 } elsif ($filename =~ m/\.png$/) {
57f93db1 225 $ct = 'image/png';
f91072d5 226 } elsif ($filename =~ m/\.gif$/) {
57f93db1 227 $ct = 'image/gif';
f91072d5 228 } elsif ($filename =~ m/\.jar$/) {
57f93db1
DM
229 $ct = 'application/java-archive';
230 } else {
231 die "unable to detect content type";
232 }
233
234 my $header = HTTP::Headers->new(Content_Type => $ct);
f91072d5 235 my $resp = HTTP::Response->new(200, "OK", $header, $data);
57f93db1
DM
236 $self->response($reqstate, $resp, $stat->mtime);
237 };
238 if (my $err = $@) {
239 $self->error($reqstate, 501, $err);
240 }
241 };
f91072d5 242
57f93db1
DM
243 warn $@ if $@;
244}
245
246sub proxy_request {
247 my ($self, $reqstate, $r, $clientip, $host, $method, $abs_uri, $ticket, $token, $params) = @_;
248
249 eval {
250 my $target;
251 if ($host eq 'localhost') {
f91072d5 252 $target = "http://$host:85$abs_uri";
57f93db1 253 } else {
f91072d5 254 $target = "https://$host:8006$abs_uri";
57f93db1
DM
255 }
256
257 my $headers = {
258 PVEDisableProxy => 'true',
259 PVEClientIP => $clientip,
260 };
261
262 my $cookie_name = 'PVEAuthCookie';
263
264 $headers->{'cookie'} = PVE::REST::create_auth_cookie($ticket) if $ticket;
265 $headers->{'CSRFPreventionToken'} = $token if $token;
266
267 my $content;
268
269 if ($method eq 'POST' || $method eq 'PUT') {
270 $headers->{'Content-Type'} = 'application/x-www-form-urlencoded';
271 # We use a temporary URI object to format
272 # the application/x-www-form-urlencoded content.
273 my $url = URI->new('http:');
274 $url->query_form(%$params);
275 $content = $url->query;
276 if (defined($content)) {
277 $headers->{'Content-Length'} = length($content);
278 }
279 }
280
57f93db1 281 my $w; $w = http_request(
f91072d5
DM
282 $method => $target,
283 headers => $headers,
284 timeout => 30,
353fef24 285 recurse => 0,
f91072d5 286 body => $content,
353fef24 287 tls_ctx => $self->{tls_ctx},
57f93db1
DM
288 sub {
289 my ($body, $hdr) = @_;
290
291 undef $w;
f91072d5 292
57f93db1
DM
293 eval {
294 my $code = delete $hdr->{Status};
295 my $msg = delete $hdr->{Reason};
296 delete $hdr->{URL};
297 delete $hdr->{HTTPVersion};
298 my $header = HTTP::Headers->new(%$hdr);
299 my $resp = HTTP::Response->new($code, $msg, $header, $body);
300 $self->response($reqstate, $resp, undef, 1);
301 };
302 warn $@ if $@;
303 });
304 };
305 warn $@ if $@;
306}
307
308my $extract_params = sub {
309 my ($r, $method) = @_;
310
311 # NOTE: HTTP::Request::Params return undef instead of ''
312 #my $parser = HTTP::Request::Params->new({req => $r});
313 #my $params = $parser->params;
314
315 my $post_params = {};
316
317 if ($method eq 'PUT' || $method eq 'POST') {
318 $post_params = CGI->new($r->content())->Vars;
319 }
320
321 my $query_params = CGI->new($r->url->query)->Vars;
322 my $params = $post_params || {};
323
324 foreach my $k (keys %{$query_params}) {
325 $params->{$k} = $query_params->{$k};
326 }
327
328 return PVE::Tools::decode_utf8_parameters($params);
329};
330
331sub handle_api2_request {
332 my ($self, $reqstate) = @_;
333
334 eval {
335 my $r = $reqstate->{request};
336 my $method = $r->method();
337 my $path = $r->uri->path();
338
339 my ($rel_uri, $format) = PVE::REST::split_abs_uri($path);
340 if (!$format) {
341 $self->error($reqstate, HTTP_NOT_IMPLEMENTED, "no such uri");
342 return;
343 }
344
345 my $rpcenv = $self->{rpcenv};
346 my $headers = $r->headers;
347
348 my $token = $headers->header('CSRFPreventionToken');
349
350 my $cookie = $headers->header('Cookie');
351
352 my $ticket = PVE::REST::extract_auth_cookie($cookie);
353
354 my $params = &$extract_params($r, $method);
355
356 my $clientip = $headers->header('PVEClientIP');
357
f91072d5 358 $rpcenv->init_request(params => $params);
57f93db1
DM
359
360 my $res = PVE::REST::rest_handler($rpcenv, $clientip, $method, $path, $rel_uri, $ticket, $token);
361
f91072d5 362 # todo: eval { $userid = $rpcenv->get_user(); };
57f93db1
DM
363 my $userid = $rpcenv->{user}; # this is faster
364 $rpcenv->set_user(undef); # clear after request
365
366 $reqstate->{log}->{userid} = $userid;
f91072d5 367
57f93db1
DM
368 if ($res->{proxy}) {
369
370 if ($self->{trusted_env}) {
371 $self->error($reqstate, HTTP_INTERNAL_SERVER_ERROR, "proxy not allowed");
372 return;
f91072d5 373 }
57f93db1 374
f91072d5 375 $self->proxy_request($reqstate, $r, $clientip, $res->{proxy}, $method,
57f93db1
DM
376 $r->uri, $ticket, $token, $res->{proxy_params});
377 return;
378
379 }
380
381 PVE::REST::prepare_response_data($format, $res);
382 my ($raw, $ct) = PVE::REST::format_response_data($format, $res, $path);
383
384 my $resp = HTTP::Response->new($res->{status}, $res->{message});
385 $resp->header("Content-Type" => $ct);
386 $resp->content($raw);
387 $self->response($reqstate, $resp);
f91072d5 388
57f93db1
DM
389 return;
390 };
391 warn $@ if $@;
392}
393
394sub handle_request {
395 my ($self, $reqstate) = @_;
396
397 #print "REQUEST" . Dumper($reqstate->{request});
398
399 eval {
400 my $r = $reqstate->{request};
401 my $method = $r->method();
402 my $path = $r->uri->path();
403
404 # print "REQUEST $path\n";
405
406 if (!$known_methods->{$method}) {
407 my $resp = HTTP::Response->new(HTTP_NOT_IMPLEMENTED, "method '$method' not available");
408 $self->response($reqstate, $resp);
409 return;
410 }
411
f91072d5 412 if ($path =~ m!/api2!) {
57f93db1
DM
413 $self->handle_api2_request($reqstate);
414 return;
415 }
416
417 if ($self->{pages} && ($method eq 'GET') && (my $handler = $self->{pages}->{$path})) {
418 if (ref($handler) eq 'CODE') {
419 my ($resp, $userid) = &$handler($self, $reqstate->{request});
420 $self->response($reqstate, $resp);
421 } elsif (ref($handler) eq 'HASH') {
422 if (my $filename = $handler->{file}) {
423 my $fh = IO::File->new($filename) ||
424 die "unable to open file '$filename' - $!\n";
425 send_file_start($self, $reqstate, $filename);
426 } else {
427 die "internal error - no handler";
428 }
429 } else {
430 die "internal error - no handler";
431 }
432 return;
f91072d5 433 }
57f93db1
DM
434
435 if ($self->{dirs} && ($method eq 'GET')) {
436 # we only allow simple names
437 if ($path =~ m!^(/\S+/)([a-zA-Z0-9\-\_\.]+)$!) {
438 my ($subdir, $file) = ($1, $2);
439 if (my $dir = $self->{dirs}->{$subdir}) {
440 my $filename = "$dir$file";
441 my $fh = IO::File->new($filename) ||
442 die "unable to open file '$filename' - $!\n";
443 send_file_start($self, $reqstate, $filename);
444 return;
445 }
446 }
447 }
448
449 die "no such file '$path'";
450 };
451 if (my $err = $@) {
452 $self->error($reqstate, 501, $err);
453 }
454}
455
456sub unshift_read_header {
457 my ($self, $reqstate, $state) = @_;
458
459 $state = {} if !$state;
460
461 $reqstate->{hdl}->unshift_read(line => sub {
462 my ($hdl, $line) = @_;
463
464 eval {
465 #print "$$: got header: $line\n";
466
467 my $r = $reqstate->{request};
468 if ($line eq '') {
469
470 $r->push_header($state->{key}, $state->{val})
471 if $state->{key};
472
473 my $conn = $r->header('Connection');
474
475 if ($conn) {
476 $reqstate->{keep_alive} = 0 if $conn =~ m/close/oi;
477 } else {
478 if ($reqstate->{proto}->{ver} < 1001) {
479 $reqstate->{keep_alive} = 0;
480 }
481 }
482
483 # how much content to read?
484 my $te = $r->header('Transfer-Encoding');
485 my $len = $r->header('Content-Length');
486 my $pveclientip = $r->header('PVEClientIP');
487
f91072d5 488 # fixme: how can we make PVEClientIP header trusted?
57f93db1
DM
489 if ($self->{trusted_env} && $pveclientip) {
490 $reqstate->{peer_host} = $pveclientip;
491 } else {
492 $r->header('PVEClientIP', $reqstate->{peer_host});
493 }
494
495 if ($te && lc($te) eq 'chunked') {
496 # Handle chunked transfer encoding
497 $self->error($reqstate, 501, "chunked transfer encoding not supported");
498 } elsif ($te) {
499 $self->error($reqstate, 501, "Unknown transfer encoding '$te'");
500 } elsif (defined($len)) {
501 $reqstate->{hdl}->unshift_read (chunk => $len, sub {
502 my ($hdl, $data) = @_;
503 $r->content($data);
504 $self->handle_request($reqstate);
505 });
506 } else {
507 $self->handle_request($reqstate);
508 }
509 } elsif ($line =~ /^([^:\s]+)\s*:\s*(.*)/) {
510 $r->push_header($state->{key}, $state->{val}) if $state->{key};
511 ($state->{key}, $state->{val}) = ($1, $2);
512 $self->unshift_read_header($reqstate, $state);
513 } elsif ($line =~ /^\s+(.*)/) {
514 $state->{val} .= " $1";
515 $self->unshift_read_header($reqstate, $state);
516 } else {
517 $self->error($reqstate, 506, "unable to parse request header");
518 }
519 };
520 warn $@ if $@;
521 });
522};
523
524sub push_request_header {
525 my ($self, $reqstate) = @_;
526
527 eval {
528 $reqstate->{hdl}->push_read(line => sub {
529 my ($hdl, $line) = @_;
530
531 eval {
532 #print "got request header: $line\n";
f91072d5 533
57f93db1
DM
534 $reqstate->{keep_alive}--;
535
536 if ($line =~ /(\S+)\040(\S+)\040HTTP\/(\d+)\.(\d+)/o) {
537 my ($method, $uri, $maj, $min) = ($1, $2, $3, $4);
538
539 if ($maj != 1) {
540 $self->error($reqstate, 506, "http protocol version $maj.$min not supported");
541 return;
542 }
543
544 $self->{request_count}++; # only count valid request headers
545 if ($self->{request_count} >= $self->{max_requests}) {
f91072d5 546 $self->{end_loop} = 1;
57f93db1
DM
547 }
548 $reqstate->{log} = { requestline => $line };
549 $reqstate->{proto}->{maj} = $maj;
550 $reqstate->{proto}->{min} = $min;
551 $reqstate->{proto}->{ver} = $maj*1000+$min;
552 $reqstate->{request} = HTTP::Request->new($method, $uri);
553
554 $self->unshift_read_header($reqstate);
555 } elsif ($line eq '') {
556 # ignore empty lines before requests (browser bugs?)
557 $self->push_request_header($reqstate);
558 } else {
559 $self->error($reqstate, 400, 'bad request');
560 }
561 };
562 warn $@ if $@;
563 });
564 };
565 warn $@ if $@;
566}
567
568sub accept {
569 my ($self) = @_;
570
571 my $clientfh;
572
573 return if $self->{end_loop};
574
575 # we need to m make sure that only one process calls accept
576 while (!flock($self->{lockfh}, Fcntl::LOCK_EX())) {
577 next if $! == EINTR;
578 die "could not get lock on file '$self->{lockfile}' - $!\n";
579 }
580
581 my $again = 0;
582 my $errmsg;
583 eval {
584 while (!$self->{end_loop} &&
585 !defined($clientfh = $self->{socket}->accept()) &&
586 ($! == EINTR)) {};
587
588 if ($self->{end_loop}) {
589 $again = 0;
590 } else {
591 $again = ($! == EAGAIN || $! == WSAEWOULDBLOCK);
592 if (!defined($clientfh)) {
593 $errmsg = "failed to accept connection: $!\n";
594 }
595 }
596 };
597 warn $@ if $@;
598
599 flock($self->{lockfh}, Fcntl::LOCK_UN());
600
601 if (!defined($clientfh)) {
602 return if $again;
603 die $errmsg if $errmsg;
604 }
605
f91072d5 606 fh_nonblocking $clientfh, 1;
57f93db1
DM
607
608 $self->{conn_count}++;
609
57f93db1
DM
610 return $clientfh;
611}
612
613sub wait_end_loop {
614 my ($self) = @_;
615
616 $self->{end_loop} = 1;
617
618 undef $self->{socket_watch};
f91072d5 619
57f93db1
DM
620 if ($self->{conn_count} <= 0) {
621 $self->{end_cond}->send(1);
622 return;
623 }
624
625 # else we need to wait until all open connections gets closed
626 my $w; $w = AnyEvent->timer (after => 1, interval => 1, cb => sub {
627 eval {
f91072d5 628 # todo: test for active connections instead (we can abort idle connections)
57f93db1
DM
629 if ($self->{conn_count} <= 0) {
630 undef $w;
631 $self->{end_cond}->send(1);
632 }
633 };
634 warn $@ if $@;
635 });
636}
f91072d5 637
57f93db1
DM
638sub accept_connections {
639 my ($self) = @_;
640
641 eval {
642
643 while (my $clientfh = $self->accept()) {
644
645 my $reqstate = { keep_alive => $self->{keep_alive} };
646
647 if (my $sin = getpeername($clientfh)) {
648 my ($pport, $phost) = Socket::unpack_sockaddr_in($sin);
649 ($reqstate->{peer_port}, $reqstate->{peer_host}) = ($pport, Socket::inet_ntoa($phost));
650 }
651
652 $reqstate->{hdl} = AnyEvent::Handle->new(
653 fh => $clientfh,
654 rbuf_max => 32768, # fixme: set smaller max read buffer ?
655 timeout => $self->{timeout},
656 linger => 0, # avoid problems with ssh - really needed ?
657 on_eof => sub {
658 my ($hdl) = @_;
659 eval {
660 $self->log_aborted_request($reqstate);
661 $self->client_do_disconnect($reqstate);
662 };
663 if (my $err = $@) { syslog('err', $err); }
664 },
f91072d5 665 on_error => sub {
57f93db1
DM
666 my ($hdl, $fatal, $message) = @_;
667 eval {
668 $self->log_aborted_request($reqstate, $message);
669 $self->client_do_disconnect($reqstate);
670 };
671 if (my $err = $@) { syslog('err', "$err"); }
672 },
673 ($self->{tls_ctx} ? (tls => "accept", tls_ctx => $self->{tls_ctx}) : ()));
674
f91072d5 675 print "$$: ACCEPT FH" . $clientfh->fileno() . " CONN$self->{conn_count}\n" if $self->{debug};
57f93db1
DM
676
677 $self->push_request_header($reqstate);
678 }
679 };
680
681 if (my $err = $@) {
682 syslog('err', $err);
683 $self->{end_loop} = 1;
684 }
685
686 $self->wait_end_loop() if $self->{end_loop};
687}
688
c2e9823c
DM
689# Note: We can't open log file in non-blocking mode and use AnyEvent::Handle,
690# because we write from multiple processes, and that would arbitrarily mix output
f91072d5 691# of all processes.
57f93db1
DM
692sub open_access_log {
693 my ($self, $filename) = @_;
694
695 my $old_mask = umask(0137);;
696 my $logfh = IO::File->new($filename, ">>") ||
697 die "unable to open log file '$filename' - $!\n";
698 umask($old_mask);
699
c2e9823c
DM
700 $logfh->autoflush(1);
701
702 $self->{logfh} = $logfh;
703}
704
705sub write_log {
706 my ($self, $data) = @_;
707
708 return if !defined($self->{logfh}) || !$data;
709
710 my $res = $self->{logfh}->print($data);
711
712 if (!$res) {
713 delete $self->{logfh};
714 syslog('err', "error writing access log");
715 $self->{end_loop} = 1; # terminate asap
716 }
57f93db1
DM
717}
718
353fef24
DM
719sub atfork_handler {
720 my ($self) = @_;
721
722 eval {
723 # something else do to ?
724 close($self->{socket});
725 };
726 warn $@ if $@;
727}
728
57f93db1
DM
729sub new {
730 my ($this, %args) = @_;
731
732 my $class = ref($this) || $this;
733
f91072d5 734 foreach my $req (qw(socket lockfh lockfile)) {
57f93db1
DM
735 die "misssing required argument '$req'" if !defined($args{$req});
736 }
737
738 my $self = bless { %args }, $class;
739
f91072d5
DM
740 # init inotify
741 PVE::INotify::inotify_init();
742
f91072d5 743 $self->{rpcenv} = PVE::RPCEnvironment->init(
353fef24 744 $self->{trusted_env} ? 'priv' : 'pub', atfork => sub { $self-> atfork_handler() });
f91072d5 745
57f93db1
DM
746 fh_nonblocking($self->{socket}, 1);
747
748 $self->{end_loop} = 0;
749 $self->{conn_count} = 0;
750 $self->{request_count} = 0;
751 $self->{timeout} = 5 if !$self->{timeout};
752 $self->{keep_alive} = 0 if !defined($self->{keep_alive});
753 $self->{max_conn} = 800 if !$self->{max_conn};
754 $self->{max_requests} = 8000 if !$self->{max_requests};
755
756 $self->{end_cond} = AnyEvent->condvar;
757
758 if ($self->{ssl}) {
f91072d5 759 $self->{tls_ctx} = AnyEvent::TLS->new(%{$self->{ssl}});
57f93db1
DM
760 }
761
57f93db1 762 $self->open_access_log($self->{logfile}) if $self->{logfile};
f91072d5 763
57f93db1
DM
764 $self->{socket_watch} = AnyEvent->io(fh => $self->{socket}, poll => 'r', cb => sub {
765 eval {
766 if ($self->{conn_count} >= $self->{max_conn}) {
767 my $w; $w = AnyEvent->timer (after => 1, interval => 1, cb => sub {
768 if ($self->{conn_count} < $self->{max_conn}) {
769 undef $w;
770 $self->accept_connections();
771 }
772 });
773 } else {
774 $self->accept_connections();
f91072d5 775 }
57f93db1
DM
776 };
777 warn $@ if $@;
778 });
779
780 $self->{term_watch} = AnyEvent->signal(signal => "TERM", cb => sub {
781 undef $self->{term_watch};
782 $self->wait_end_loop();
783 });
784
f91072d5 785 $self->{quit_watch} = AnyEvent->signal(signal => "QUIT", cb => sub {
57f93db1
DM
786 undef $self->{quit_watch};
787 $self->wait_end_loop();
788 });
789
f91072d5
DM
790 $self->{inotify_poll} = AnyEvent->timer(after => 5, interval => 5, cb => sub {
791 PVE::INotify::poll(); # read inotify events
792 });
793
57f93db1
DM
794 return $self;
795}
796
797sub run {
798 my ($self) = @_;
799
800 $self->{end_cond}->recv;
801}
802
8031;