gstlal 1.13.0
Loading...
Searching...
No Matches
httpinterface.py
2# Copyright (C) 2011,2012,2014,2016,2018 Kipp Cannon
3#
4# This program is free software; you can redistribute it and/or modify it
5# under the terms of the GNU General Public License as published by the
6# Free Software Foundation; either version 2 of the License, or (at your
7# option) any later version.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12# Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17#
18
19
20"""
21Stuff to help add an http control and query interface to a program.
22"""
23
24
25#
26# =============================================================================
27#
28# Preamble
29#
30# =============================================================================
31#
32
33
34import socket
35import sys
36import threading
37import time
38import warnings
39
40
41try:
42 import bottle
43 if tuple(map(int, bottle.__version__.split("."))) < (0, 13):
44 # FIXME: see
45 # https://git.ligo.org/lscsoft/gstlal/-/merge_requests/146
46 # if the required patch is added to the distro-supplied
47 # bottle before a 0.13 is released, update the version
48 # check to the correct version
49 raise ImportError
50except ImportError:
51 # FIXME: remove after system-wide isntall can be relied on
52 from . import bottle
53from . import servicediscovery
54
55
56#
57# =============================================================================
58#
59# HTTP Interface Helpers
60#
61# =============================================================================
62#
63
64
65class HTTPDServer(object):
66 def __init__(self, host, port, bottle_app, verbose = False):
67 self.host = host
68 self.port = port
69 self.bottle_app = bottle_app
70 self.verbose = verbose
71
72 def __enter__(self):
73 self.httpd = bottle.WSGIRefServer(host = self.host, port = self.port)
74 self.httpd_thread = threading.Thread(target = self.httpd.run, args = (self.bottle_app,))
75 self.httpd_thread.daemon = True
76 self.httpd_thread.start()
77 if self.verbose:
78 print("waiting for http server to start ...", file=sys.stderr)
79 while self.httpd.port == 0:
80 time.sleep(0.25)
81 self.host = self.httpd.host
82 self.port = self.httpd.port
83 if self.verbose:
84 print("started http server on http://%s:%d" % (self.httpd.host, self.httpd.port), file=sys.stderr)
85 return self
86
87 def __exit__(self, exc_type, exc_value, traceback):
88 if self.verbose:
89 print("stopping http server on http://%s:%d ..." % (self.httpd.host, self.httpd.port), file=sys.stderr)
90 try:
91 self.httpd.shutdown()
92 except Exception as e:
93 result = "failed: %s" % str(e)
94 else:
95 result = "done"
96 if self.verbose:
97 print(result, file=sys.stderr)
98 print("killing http server thread ...", file=sys.stderr)
99 # wait 10 seconds, then give up
100 self.httpd_thread.join(10.0)
101 if self.verbose:
102 print("timeout" if self.httpd_thread.is_alive() else "done", file=sys.stderr)
103
104
105class HTTPServers(list):
106 """
107 Utility to start, advertise, track and shutdown http servers on all
108 interfaces. De-advertise and shutdown the servers by deleting this
109 object. Do not allow the object to be garbage collected until you
110 wish the servers to be shutdown.
111
112 Example:
113
114 >>> # save return value in a variable to prevent garbage collection
115 >>> servers = HTTPServers(port = 12345)
116 >>> pass # blah
117 >>> pass # blah
118 >>> pass # blah
119 >>> # shutdown servers by deleting object
120 >>> del servers
121
122 If port = 0 (the default) a port will be assigned randomly.
123 bottle_app should be a Bottle instance. If bottle_app is None (the
124 default) then the current default Bottle application is used.
125 """
126 def __init__(self, port = 0, bottle_app = None, service_name = "www", service_domain = None, service_properties = None, verbose = False, service_discovery = True):
127 if bottle_app is None:
128 bottle_app = bottle.default_app()
129 self.verbose = verbose
130 self.service_discovery = service_discovery
131 if self.service_discovery:
133 else:
134 warnings.warn("disabling service discovery, this web server won't be able to advertise the location of the services it provides.")
135 for (ignored, ignored, ignored, ignored, (host, port)) in socket.getaddrinfo(None, port, socket.AF_INET, socket.SOCK_STREAM, 0, socket.AI_NUMERICHOST | socket.AI_PASSIVE):
136 httpd = HTTPDServer(host, port, bottle_app, verbose = verbose).__enter__()
137 if verbose:
138 print("advertising http server \"%s\" on http://%s:%d ..." % (service_name, httpd.host, httpd.port), file=sys.stderr)
139 if self.service_discovery:
140 service = self.service_publisher.add_service(
141 sname = service_name,
142 sdomain = service_domain,
143 port = httpd.port,
144 properties = service_properties,
145 commit = False
146 )
147 else:
148 service = None
149 if verbose:
150 print("done (%s)" % (".".join((service.sname, service.sdomain)) if service else ""), file=sys.stderr)
151 self.append((httpd, service))
152 if not self:
153 raise ValueError("unable to start servers%s" % (" on port %d" % port if port != 0 else ""))
154 if self.service_discovery:
155 self.service_publisher.commit()
156
157 def __del__(self):
158 if self.verbose:
159 print("de-advertising http server(s) ...", file=sys.stderr)
160 try:
161 if self.service_discovery:
162 self.service_publisher.__exit__(None, None, None)
163 except Exception as e:
164 if self.verbose:
165 print("failed: %s" % str(e), file=sys.stderr)
166 else:
167 if self.verbose:
168 print("done", file=sys.stderr)
169 while self:
170 try:
171 self.pop()[0].__exit__(None, None, None)
172 except Exception as e:
173 if self.verbose:
174 print("failed: %s" % str(e), file=sys.stderr)