blob: c3694c34a48f366d6ef1cb68ce60614a02dbc9a9 [file] [log] [blame]
Mike Frysingera7f08bc2019-08-27 19:16:331# -*- coding: utf-8 -*-
Mike Frysinger8b0fc372022-09-08 07:24:242# Copyright 2014 The ChromiumOS Authors
Gilad Arnold11fbef42014-02-10 19:04:133# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Extensions for CherryPy.
7
8This module contains patches and add-ons for the stock CherryPy distribution.
9Everything in here is compatible with the CherryPy version used in the chroot,
10as well as the recent stable version as used (for example) in the lab. This
11premise is verified by the corresponding unit tests.
12"""
13
Amin Hassanic5af4262019-11-13 21:37:2014from __future__ import print_function
15
Gilad Arnold11fbef42014-02-10 19:04:1316import os
17
Amin Hassanic5af4262019-11-13 21:37:2018import cherrypy # pylint: disable=import-error
19
Gilad Arnold11fbef42014-02-10 19:04:1320
21class PortFile(cherrypy.process.plugins.SimplePlugin):
Jack Rosenthal8de609d2023-02-09 20:20:3522 """CherryPy plugin for maintaining a port file via a WSPBus.
Gilad Arnold11fbef42014-02-10 19:04:1323
Jack Rosenthal8de609d2023-02-09 20:20:3524 This is a hack, because we're using arbitrary bus signals (like 'start' and
25 'log') to trigger checking whether the server has already bound the listening
26 socket to a port, in which case we write it to a file. It would work as long
27 as the server (for example) logs the fact that it is up and serving *after*
28 it has bound the port, which happens to be the case. The upside is that we
29 don't have to use ad hoc signals, nor do we need to change the implementaiton
30 of various CherryPy classes (like ServerAdapter) to use such signals.
Gilad Arnold11fbef42014-02-10 19:04:1331
Jack Rosenthal8de609d2023-02-09 20:20:3532 In all other respects, this plugin mirrors the behavior of the stock
33 cherrypy.process.plugins.PIDFile plugin. Note that it will not work correctly
34 in the presence of multiple server threads, nor is it meant to; it will only
35 write the port of the main server instance (cherrypy.server), if present.
Gilad Arnold11fbef42014-02-10 19:04:1336 """
Gilad Arnold11fbef42014-02-10 19:04:1337
Jack Rosenthal8de609d2023-02-09 20:20:3538 def __init__(self, bus, portfile):
39 super(PortFile, self).__init__(bus)
40 self.portfile = portfile
41 self.stopped = True
42 self.written = False
Gilad Arnold11fbef42014-02-10 19:04:1343
Jack Rosenthal8de609d2023-02-09 20:20:3544 @staticmethod
45 def get_port_from_httpserver():
46 """Pulls the actual bound port number from CherryPy's HTTP server.
Gilad Arnold11fbef42014-02-10 19:04:1347
Jack Rosenthal8de609d2023-02-09 20:20:3548 This assumes that cherrypy.server is the main server instance,
49 cherrypy.server.httpserver the underlying HTTP server, and
50 cherrypy.server.httpserver.socket the socket used for serving. These appear
51 to be well accepted conventions throughout recent versions of CherryPy.
Gilad Arnold11fbef42014-02-10 19:04:1352
Jack Rosenthal8de609d2023-02-09 20:20:3553 Returns:
54 The actual bound port; zero if not bound or could not be retrieved.
55 """
56 server_socket = getattr(
57 cherrypy.server, "httpserver", None
58 ) and getattr(cherrypy.server.httpserver, "socket", None)
59 bind_addr = server_socket and server_socket.getsockname()
60 return (
61 bind_addr[1] if (bind_addr and isinstance(bind_addr, tuple)) else 0
62 )
Gilad Arnold11fbef42014-02-10 19:04:1363
Jack Rosenthal8de609d2023-02-09 20:20:3564 def _check_and_write_port(self):
65 """Check if a port has been bound, and if so write it to file.
Gilad Arnold11fbef42014-02-10 19:04:1366
Jack Rosenthal8de609d2023-02-09 20:20:3567 This maintains a flag to denote whether or not the server has started (to
68 avoid doing unnecessary work) and another flag denoting whether a port was
69 already written to file (so it can be removed upon 'stop').
Gilad Arnold11fbef42014-02-10 19:04:1370
Jack Rosenthal8de609d2023-02-09 20:20:3571 IMPORTANT: to avoid infinite recursion, do not emit any bus event (e.g.
72 self.bus.log()) until after setting self.written to True!
73 """
74 if self.stopped or self.written:
75 return
76 port = self.get_port_from_httpserver()
77 if not port:
78 return
79 with open(self.portfile, "w") as f:
80 f.write(str(port))
81 self.written = True
82 self.bus.log("Port %r written to %r." % (port, self.portfile))
83
84 def start(self):
85 self.stopped = False
86 self._check_and_write_port()
87
88 start.priority = 50
89
90 def log(self, _msg, _level):
91 self._check_and_write_port()
92
93 def stop(self):
94 """Removes the port file.
95
96 IMPORTANT: to avoid re-writing the port file via other signals (e.g.
97 self.bus.log()) be sure to set self.stopped to True before setting
98 self.written to False!
99 """
100 self.stopped = True
101 if self.written:
102 self.written = False
103 try:
104 os.remove(self.portfile)
105 self.bus.log("Port file removed: %r." % self.portfile)
106 except (KeyboardInterrupt, SystemExit):
107 raise
108 except Exception:
109 self.bus.log("Failed to remove port file: %r." % self.portfile)