gstlal  1.4.1
httpinterface.py
1 #
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 """
21 Stuff 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 
34 import socket
35 import sys
36 import threading
37 import time
38 
39 
40 from . import bottle
41 from . import servicediscovery
42 
43 
44 #
45 # =============================================================================
46 #
47 # HTTP Interface Helpers
48 #
49 # =============================================================================
50 #
51 
52 
53 class HTTPDServer(object):
54  def __init__(self, host, port, bottle_app, verbose = False):
55  self.host = host
56  self.port = port
57  self.bottle_app = bottle_app
58  self.verbose = verbose
59 
60  def __enter__(self):
61  self.httpd = bottle.WSGIRefServer(host = self.host, port = self.port)
62  self.httpd_thread = threading.Thread(target = self.httpd.run, args = (self.bottle_app,))
63  self.httpd_thread.daemon = True
64  self.httpd_thread.start()
65  if self.verbose:
66  print >>sys.stderr, "waiting for http server to start ..."
67  while self.httpd.port == 0:
68  time.sleep(0.25)
69  self.host = self.httpd.host
70  self.port = self.httpd.port
71  if self.verbose:
72  print >>sys.stderr, "started http server on http://%s:%d" % (self.httpd.host, self.httpd.port)
73  return self
74 
75  def __exit__(self, exc_type, exc_value, traceback):
76  if self.verbose:
77  print >>sys.stderr, "stopping http server on http://%s:%d ..." % (self.httpd.host, self.httpd.port),
78  try:
79  self.httpd.shutdown()
80  except Exception as e:
81  result = "failed: %s" % str(e)
82  else:
83  result = "done"
84  if self.verbose:
85  print >>sys.stderr, result
86  print >>sys.stderr, "killing http server thread ...",
87  # wait 10 seconds, then give up
88  self.httpd_thread.join(10.0)
89  if self.verbose:
90  print >>sys.stderr, "timeout" if self.httpd_thread.is_alive() else "done"
91 
92 
93 class HTTPServers(list):
94  """
95  Utility to start, advertise, track and shutdown http servers on all
96  interfaces. De-advertise and shutdown the servers by deleting this
97  object. Do not allow the object to be garbage collected until you
98  wish the servers to be shutdown.
99 
100  Example:
101 
102  >>> # save return value in a variable to prevent garbage collection
103  >>> servers = HTTPServers(port = 12345)
104  >>> pass # blah
105  >>> pass # blah
106  >>> pass # blah
107  >>> # shutdown servers by deleting object
108  >>> del servers
109 
110  If port = 0 (the default) a port will be assigned randomly.
111  bottle_app should be a Bottle instance. If bottle_app is None (the
112  default) then the current default Bottle application is used.
113  """
114  def __init__(self, port = 0, bottle_app = None, service_name = "www", service_domain = None, service_properties = None, verbose = False):
115  if bottle_app is None:
116  bottle_app = bottle.default_app()
117  self.verbose = verbose
118  self.service_publisher = servicediscovery.Publisher().__enter__()
119  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):
120  httpd = HTTPDServer(host, port, bottle_app, verbose = verbose).__enter__()
121  if verbose:
122  print >>sys.stderr, "advertising http server \"%s\" on http://%s:%d ..." % (service_name, httpd.host, httpd.port),
123  service = self.service_publisher.add_service(
124  sname = service_name,
125  sdomain = service_domain,
126  port = httpd.port,
127  properties = service_properties,
128  commit = False
129  )
130  if verbose:
131  print >>sys.stderr, "done (%s)" % ".".join((service.sname, service.sdomain))
132  self.append((httpd, service))
133  if not self:
134  raise ValueError("unable to start servers%s" % (" on port %d" % port if port != 0 else ""))
135  self.service_publisher.commit()
136 
137  def __del__(self):
138  if self.verbose:
139  print >>sys.stderr, "de-advertising http server(s) ...",
140  try:
141  self.service_publisher.__exit__(None, None, None)
142  except Exception as e:
143  if self.verbose:
144  print >>sys.stderr, "failed: %s" % str(e)
145  else:
146  if self.verbose:
147  print >>sys.stderr, "done"
148  while self:
149  try:
150  self.pop()[0].__exit__(None, None, None)
151  except Exception as e:
152  if self.verbose:
153  print >>sys.stderr, "failed: %s" % str(e)