Mike Frysinger | a7f08bc | 2019-08-27 19:16:33 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
Mike Frysinger | 8b0fc37 | 2022-09-08 07:24:24 | [diff] [blame] | 2 | # Copyright 2014 The ChromiumOS Authors |
Gilad Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 3 | # 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 | |
| 8 | This module contains patches and add-ons for the stock CherryPy distribution. |
| 9 | Everything in here is compatible with the CherryPy version used in the chroot, |
| 10 | as well as the recent stable version as used (for example) in the lab. This |
| 11 | premise is verified by the corresponding unit tests. |
| 12 | """ |
| 13 | |
Amin Hassani | c5af426 | 2019-11-13 21:37:20 | [diff] [blame] | 14 | from __future__ import print_function |
| 15 | |
Gilad Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 16 | import os |
| 17 | |
Amin Hassani | c5af426 | 2019-11-13 21:37:20 | [diff] [blame] | 18 | import cherrypy # pylint: disable=import-error |
| 19 | |
Gilad Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 20 | |
| 21 | class PortFile(cherrypy.process.plugins.SimplePlugin): |
Jack Rosenthal | 8de609d | 2023-02-09 20:20:35 | [diff] [blame] | 22 | """CherryPy plugin for maintaining a port file via a WSPBus. |
Gilad Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 23 | |
Jack Rosenthal | 8de609d | 2023-02-09 20:20:35 | [diff] [blame] | 24 | 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 Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 31 | |
Jack Rosenthal | 8de609d | 2023-02-09 20:20:35 | [diff] [blame] | 32 | 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 Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 36 | """ |
Gilad Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 37 | |
Jack Rosenthal | 8de609d | 2023-02-09 20:20:35 | [diff] [blame] | 38 | def __init__(self, bus, portfile): |
| 39 | super(PortFile, self).__init__(bus) |
| 40 | self.portfile = portfile |
| 41 | self.stopped = True |
| 42 | self.written = False |
Gilad Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 43 | |
Jack Rosenthal | 8de609d | 2023-02-09 20:20:35 | [diff] [blame] | 44 | @staticmethod |
| 45 | def get_port_from_httpserver(): |
| 46 | """Pulls the actual bound port number from CherryPy's HTTP server. |
Gilad Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 47 | |
Jack Rosenthal | 8de609d | 2023-02-09 20:20:35 | [diff] [blame] | 48 | 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 Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 52 | |
Jack Rosenthal | 8de609d | 2023-02-09 20:20:35 | [diff] [blame] | 53 | 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 Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 63 | |
Jack Rosenthal | 8de609d | 2023-02-09 20:20:35 | [diff] [blame] | 64 | def _check_and_write_port(self): |
| 65 | """Check if a port has been bound, and if so write it to file. |
Gilad Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 66 | |
Jack Rosenthal | 8de609d | 2023-02-09 20:20:35 | [diff] [blame] | 67 | 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 Arnold | 11fbef4 | 2014-02-10 19:04:13 | [diff] [blame] | 70 | |
Jack Rosenthal | 8de609d | 2023-02-09 20:20:35 | [diff] [blame] | 71 | 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) |