http.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. import os
  2. import stat
  3. import time
  4. import sys
  5. import supervisor.medusa.text_socket as socket
  6. import errno
  7. import pwd
  8. import weakref
  9. from supervisor.compat import urllib
  10. from supervisor.compat import sha1
  11. from supervisor.compat import as_bytes
  12. from supervisor.medusa import asyncore_25 as asyncore
  13. from supervisor.medusa import http_date
  14. from supervisor.medusa import http_server
  15. from supervisor.medusa import producers
  16. from supervisor.medusa import filesys
  17. from supervisor.medusa import default_handler
  18. from supervisor.medusa.auth_handler import auth_handler
  19. class NOT_DONE_YET:
  20. pass
  21. class deferring_chunked_producer:
  22. """A producer that implements the 'chunked' transfer coding for HTTP/1.1.
  23. Here is a sample usage:
  24. request['Transfer-Encoding'] = 'chunked'
  25. request.push (
  26. producers.chunked_producer (your_producer)
  27. )
  28. request.done()
  29. """
  30. def __init__ (self, producer, footers=None):
  31. self.producer = producer
  32. self.footers = footers
  33. self.delay = 0.1
  34. def more (self):
  35. if self.producer:
  36. data = self.producer.more()
  37. if data is NOT_DONE_YET:
  38. return NOT_DONE_YET
  39. elif data:
  40. return '%x\r\n%s\r\n' % (len(data), data)
  41. else:
  42. self.producer = None
  43. if self.footers:
  44. return '\r\n'.join(['0'] + self.footers) + '\r\n\r\n'
  45. else:
  46. return '0\r\n\r\n'
  47. else:
  48. return ''
  49. class deferring_composite_producer:
  50. """combine a fifo of producers into one"""
  51. def __init__ (self, producers):
  52. self.producers = producers
  53. self.delay = 0.1
  54. def more (self):
  55. while len(self.producers):
  56. p = self.producers[0]
  57. d = p.more()
  58. if d is NOT_DONE_YET:
  59. return NOT_DONE_YET
  60. if d:
  61. return d
  62. else:
  63. self.producers.pop(0)
  64. else:
  65. return ''
  66. class deferring_globbing_producer:
  67. """
  68. 'glob' the output from a producer into a particular buffer size.
  69. helps reduce the number of calls to send(). [this appears to
  70. gain about 30% performance on requests to a single channel]
  71. """
  72. def __init__ (self, producer, buffer_size=1<<16):
  73. self.producer = producer
  74. self.buffer = ''
  75. self.buffer_size = buffer_size
  76. self.delay = 0.1
  77. def more (self):
  78. while len(self.buffer) < self.buffer_size:
  79. data = self.producer.more()
  80. if data is NOT_DONE_YET:
  81. return NOT_DONE_YET
  82. if data:
  83. try:
  84. self.buffer = self.buffer + data
  85. except TypeError:
  86. self.buffer = as_bytes(self.buffer) + as_bytes(data)
  87. else:
  88. break
  89. r = self.buffer
  90. self.buffer = ''
  91. return r
  92. class deferring_hooked_producer:
  93. """
  94. A producer that will call <function> when it empties,.
  95. with an argument of the number of bytes produced. Useful
  96. for logging/instrumentation purposes.
  97. """
  98. def __init__ (self, producer, function):
  99. self.producer = producer
  100. self.function = function
  101. self.bytes = 0
  102. self.delay = 0.1
  103. def more (self):
  104. if self.producer:
  105. result = self.producer.more()
  106. if result is NOT_DONE_YET:
  107. return NOT_DONE_YET
  108. if not result:
  109. self.producer = None
  110. self.function (self.bytes)
  111. else:
  112. self.bytes += len(result)
  113. return result
  114. else:
  115. return ''
  116. class deferring_http_request(http_server.http_request):
  117. """ The medusa http_request class uses the default set of producers in
  118. medusa.producers. We can't use these because they don't know anything
  119. about deferred responses, so we override various methods here. This was
  120. added to support tail -f like behavior on the logtail handler """
  121. def done(self, *arg, **kw):
  122. """ I didn't want to override this, but there's no way around
  123. it in order to support deferreds - CM
  124. finalize this transaction - send output to the http channel"""
  125. # ----------------------------------------
  126. # persistent connection management
  127. # ----------------------------------------
  128. # --- BUCKLE UP! ----
  129. connection = http_server.get_header(http_server.CONNECTION,self.header)
  130. connection = connection.lower()
  131. close_it = 0
  132. wrap_in_chunking = 0
  133. globbing = 1
  134. if self.version == '1.0':
  135. if connection == 'keep-alive':
  136. if not 'Content-Length' in self:
  137. close_it = 1
  138. else:
  139. self['Connection'] = 'Keep-Alive'
  140. else:
  141. close_it = 1
  142. elif self.version == '1.1':
  143. if connection == 'close':
  144. close_it = 1
  145. elif not 'Content-Length' in self:
  146. if 'Transfer-Encoding' in self:
  147. if not self['Transfer-Encoding'] == 'chunked':
  148. close_it = 1
  149. elif self.use_chunked:
  150. self['Transfer-Encoding'] = 'chunked'
  151. wrap_in_chunking = 1
  152. # globbing slows down tail -f output, so only use it if
  153. # we're not in chunked mode
  154. globbing = 0
  155. else:
  156. close_it = 1
  157. elif self.version is None:
  158. # Although we don't *really* support http/0.9 (because
  159. # we'd have to use \r\n as a terminator, and it would just
  160. # yuck up a lot of stuff) it's very common for developers
  161. # to not want to type a version number when using telnet
  162. # to debug a server.
  163. close_it = 1
  164. outgoing_header = producers.simple_producer(self.build_reply_header())
  165. if close_it:
  166. self['Connection'] = 'close'
  167. if wrap_in_chunking:
  168. outgoing_producer = deferring_chunked_producer(
  169. deferring_composite_producer(self.outgoing)
  170. )
  171. # prepend the header
  172. outgoing_producer = deferring_composite_producer(
  173. [outgoing_header, outgoing_producer]
  174. )
  175. else:
  176. # prepend the header
  177. self.outgoing.insert(0, outgoing_header)
  178. outgoing_producer = deferring_composite_producer(self.outgoing)
  179. # hook logging into the output
  180. outgoing_producer = deferring_hooked_producer(outgoing_producer,
  181. self.log)
  182. if globbing:
  183. outgoing_producer = deferring_globbing_producer(outgoing_producer)
  184. self.channel.push_with_producer(outgoing_producer)
  185. self.channel.current_request = None
  186. if close_it:
  187. self.channel.close_when_done()
  188. def log (self, bytes):
  189. """ We need to override this because UNIX domain sockets return
  190. an empty string for the addr rather than a (host, port) combination """
  191. if self.channel.addr:
  192. host = self.channel.addr[0]
  193. port = self.channel.addr[1]
  194. else:
  195. host = 'localhost'
  196. port = 0
  197. self.channel.server.logger.log (
  198. host,
  199. '%d - - [%s] "%s" %d %d\n' % (
  200. port,
  201. self.log_date_string (time.time()),
  202. self.request,
  203. self.reply_code,
  204. bytes
  205. )
  206. )
  207. def cgi_environment(self):
  208. env = {}
  209. # maps request some headers to environment variables.
  210. # (those that don't start with 'HTTP_')
  211. header2env= {'content-length' : 'CONTENT_LENGTH',
  212. 'content-type' : 'CONTENT_TYPE',
  213. 'connection' : 'CONNECTION_TYPE'}
  214. workdir = os.getcwd()
  215. (path, params, query, fragment) = self.split_uri()
  216. if params:
  217. path = path + params # undo medusa bug!
  218. while path and path[0] == '/':
  219. path = path[1:]
  220. if '%' in path:
  221. path = http_server.unquote(path)
  222. if query:
  223. query = query[1:]
  224. server = self.channel.server
  225. env['REQUEST_METHOD'] = self.command.upper()
  226. env['SERVER_PORT'] = str(server.port)
  227. env['SERVER_NAME'] = server.server_name
  228. env['SERVER_SOFTWARE'] = server.SERVER_IDENT
  229. env['SERVER_PROTOCOL'] = "HTTP/" + self.version
  230. env['channel.creation_time'] = self.channel.creation_time
  231. env['SCRIPT_NAME'] = ''
  232. env['PATH_INFO'] = '/' + path
  233. env['PATH_TRANSLATED'] = os.path.normpath(os.path.join(
  234. workdir, env['PATH_INFO']))
  235. if query:
  236. env['QUERY_STRING'] = query
  237. env['GATEWAY_INTERFACE'] = 'CGI/1.1'
  238. if self.channel.addr:
  239. env['REMOTE_ADDR'] = self.channel.addr[0]
  240. else:
  241. env['REMOTE_ADDR'] = '127.0.0.1'
  242. for header in self.header:
  243. key,value=header.split(":",1)
  244. key=key.lower()
  245. value=value.strip()
  246. if key in header2env and value:
  247. env[header2env.get(key)]=value
  248. else:
  249. key='HTTP_%s' % ("_".join(key.split( "-"))).upper()
  250. if value and key not in env:
  251. env[key]=value
  252. return env
  253. def get_server_url(self):
  254. """ Functionality that medusa's http request doesn't have; set an
  255. attribute named 'server_url' on the request based on the Host: header
  256. """
  257. default_port={'http': '80', 'https': '443'}
  258. environ = self.cgi_environment()
  259. if (environ.get('HTTPS') in ('on', 'ON') or
  260. environ.get('SERVER_PORT_SECURE') == "1"):
  261. # XXX this will currently never be true
  262. protocol = 'https'
  263. else:
  264. protocol = 'http'
  265. if 'HTTP_HOST' in environ:
  266. host = environ['HTTP_HOST'].strip()
  267. hostname, port = urllib.splitport(host)
  268. else:
  269. hostname = environ['SERVER_NAME'].strip()
  270. port = environ['SERVER_PORT']
  271. if port is None or default_port[protocol] == port:
  272. host = hostname
  273. else:
  274. host = hostname + ':' + port
  275. server_url = '%s://%s' % (protocol, host)
  276. if server_url[-1:]=='/':
  277. server_url=server_url[:-1]
  278. return server_url
  279. class deferring_http_channel(http_server.http_channel):
  280. # use a 4096-byte buffer size instead of the default 65536-byte buffer in
  281. # order to spew tail -f output faster (speculative)
  282. ac_out_buffer_size = 4096
  283. delay = False
  284. writable_check = time.time()
  285. def writable(self, t=time.time):
  286. now = t()
  287. if self.delay:
  288. # we called a deferred producer via this channel (see refill_buffer)
  289. last_writable_check = self.writable_check
  290. elapsed = now - last_writable_check
  291. if elapsed > self.delay:
  292. self.writable_check = now
  293. return True
  294. else:
  295. return False
  296. # It is possible that self.ac_out_buffer is equal b''
  297. # and in Python3 b'' is not equal ''. This cause
  298. # http_server.http_channel.writable(self) is always True.
  299. # To avoid this case, we need to force self.ac_out_buffer = ''
  300. if len(self.ac_out_buffer) == 0:
  301. self.ac_out_buffer = ''
  302. return http_server.http_channel.writable(self)
  303. def refill_buffer (self):
  304. """ Implement deferreds """
  305. while 1:
  306. if len(self.producer_fifo):
  307. p = self.producer_fifo.first()
  308. # a 'None' in the producer fifo is a sentinel,
  309. # telling us to close the channel.
  310. if p is None:
  311. if not self.ac_out_buffer:
  312. self.producer_fifo.pop()
  313. self.close()
  314. return
  315. elif isinstance(p, str):
  316. self.producer_fifo.pop()
  317. self.ac_out_buffer += p
  318. return
  319. data = p.more()
  320. if data is NOT_DONE_YET:
  321. self.delay = p.delay
  322. return
  323. elif data:
  324. try:
  325. self.ac_out_buffer = self.ac_out_buffer + data
  326. except TypeError:
  327. self.ac_out_buffer = as_bytes(self.ac_out_buffer) + as_bytes(data)
  328. self.delay = False
  329. return
  330. else:
  331. self.producer_fifo.pop()
  332. else:
  333. return
  334. def found_terminator (self):
  335. """ We only override this to use 'deferring_http_request' class
  336. instead of the normal http_request class; it sucks to need to override
  337. this """
  338. if self.current_request:
  339. self.current_request.found_terminator()
  340. else:
  341. header = self.in_buffer
  342. self.in_buffer = ''
  343. lines = header.split('\r\n')
  344. # --------------------------------------------------
  345. # crack the request header
  346. # --------------------------------------------------
  347. while lines and not lines[0]:
  348. # as per the suggestion of http-1.1 section 4.1, (and
  349. # Eric Parker <eparker@zyvex.com>), ignore a leading
  350. # blank lines (buggy browsers tack it onto the end of
  351. # POST requests)
  352. lines = lines[1:]
  353. if not lines:
  354. self.close_when_done()
  355. return
  356. request = lines[0]
  357. command, uri, version = http_server.crack_request (request)
  358. header = http_server.join_headers (lines[1:])
  359. # unquote path if necessary (thanks to Skip Montanaro for pointing
  360. # out that we must unquote in piecemeal fashion).
  361. rpath, rquery = http_server.splitquery(uri)
  362. if '%' in rpath:
  363. if rquery:
  364. uri = http_server.unquote (rpath) + '?' + rquery
  365. else:
  366. uri = http_server.unquote (rpath)
  367. r = deferring_http_request (self, request, command, uri, version,
  368. header)
  369. self.request_counter.increment()
  370. self.server.total_requests.increment()
  371. if command is None:
  372. self.log_info ('Bad HTTP request: %s' % repr(request), 'error')
  373. r.error (400)
  374. return
  375. # --------------------------------------------------
  376. # handler selection and dispatch
  377. # --------------------------------------------------
  378. for h in self.server.handlers:
  379. if h.match (r):
  380. try:
  381. self.current_request = r
  382. # This isn't used anywhere.
  383. # r.handler = h # CYCLE
  384. h.handle_request (r)
  385. except:
  386. self.server.exceptions.increment()
  387. (file, fun, line), t, v, tbinfo = \
  388. asyncore.compact_traceback()
  389. self.server.log_info(
  390. 'Server Error: %s, %s: file: %s line: %s' %
  391. (t,v,file,line),
  392. 'error')
  393. try:
  394. r.error (500)
  395. except:
  396. pass
  397. return
  398. # no handlers, so complain
  399. r.error (404)
  400. class supervisor_http_server(http_server.http_server):
  401. channel_class = deferring_http_channel
  402. ip = None
  403. def prebind(self, sock, logger_object):
  404. """ Override __init__ to do logger setup earlier so it can
  405. go to our logger object instead of stdout """
  406. from supervisor.medusa import logger
  407. if not logger_object:
  408. logger_object = logger.file_logger(sys.stdout)
  409. logger_object = logger.unresolving_logger(logger_object)
  410. self.logger = logger_object
  411. asyncore.dispatcher.__init__ (self)
  412. self.set_socket(sock)
  413. self.handlers = []
  414. sock.setblocking(0)
  415. self.set_reuse_addr()
  416. def postbind(self):
  417. from supervisor.medusa.counter import counter
  418. from supervisor.medusa.http_server import VERSION_STRING
  419. self.listen(1024)
  420. self.total_clients = counter()
  421. self.total_requests = counter()
  422. self.exceptions = counter()
  423. self.bytes_out = counter()
  424. self.bytes_in = counter()
  425. self.log_info (
  426. 'Medusa (V%s) started at %s'
  427. '\n\tHostname: %s'
  428. '\n\tPort:%s'
  429. '\n' % (
  430. VERSION_STRING,
  431. time.ctime(time.time()),
  432. self.server_name,
  433. self.port,
  434. )
  435. )
  436. def log_info(self, message, type='info'):
  437. ip = ''
  438. if getattr(self, 'ip', None) is not None:
  439. ip = self.ip
  440. self.logger.log(ip, message)
  441. class supervisor_af_inet_http_server(supervisor_http_server):
  442. """ AF_INET version of supervisor HTTP server """
  443. def __init__(self, ip, port, logger_object):
  444. self.ip = ip
  445. self.port = port
  446. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  447. self.prebind(sock, logger_object)
  448. self.bind((ip, port))
  449. if not ip:
  450. self.log_info('Computing default hostname', 'warning')
  451. hostname = socket.gethostname()
  452. try:
  453. ip = socket.gethostbyname(hostname)
  454. except socket.error:
  455. raise ValueError(
  456. 'Could not determine IP address for hostname %s, '
  457. 'please try setting an explicit IP address in the "port" '
  458. 'setting of your [inet_http_server] section. For example, '
  459. 'instead of "port = 9001", try "port = 127.0.0.1:9001."'
  460. % hostname)
  461. try:
  462. self.server_name = socket.gethostbyaddr (ip)[0]
  463. except socket.error:
  464. self.log_info('Cannot do reverse lookup', 'warning')
  465. self.server_name = ip # use the IP address as the "hostname"
  466. self.postbind()
  467. class supervisor_af_unix_http_server(supervisor_http_server):
  468. """ AF_UNIX version of supervisor HTTP server """
  469. def __init__(self, socketname, sockchmod, sockchown, logger_object):
  470. self.ip = socketname
  471. self.port = socketname
  472. # XXX this is insecure. We really should do something like
  473. # http://developer.apple.com/samplecode/CFLocalServer/listing6.html
  474. # (see also http://developer.apple.com/technotes/tn2005/tn2083.html#SECUNIXDOMAINSOCKETS)
  475. # but it would be very inconvenient for the user to need to get all
  476. # the directory setup right.
  477. tempname = "%s.%d" % (socketname, os.getpid())
  478. try:
  479. os.unlink(tempname)
  480. except OSError:
  481. pass
  482. while 1:
  483. sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  484. try:
  485. sock.bind(tempname)
  486. os.chmod(tempname, sockchmod)
  487. try:
  488. # hard link
  489. os.link(tempname, socketname)
  490. except OSError:
  491. # Lock contention, or stale socket.
  492. used = self.checkused(socketname)
  493. if used:
  494. # cooperate with 'openhttpserver' in supervisord
  495. raise socket.error(errno.EADDRINUSE)
  496. # Stale socket -- delete, sleep, and try again.
  497. msg = "Unlinking stale socket %s\n" % socketname
  498. sys.stderr.write(msg)
  499. try:
  500. os.unlink(socketname)
  501. except:
  502. pass
  503. sock.close()
  504. time.sleep(.3)
  505. continue
  506. else:
  507. try:
  508. os.chown(socketname, sockchown[0], sockchown[1])
  509. except OSError as why:
  510. if why.args[0] == errno.EPERM:
  511. msg = ('Not permitted to chown %s to uid/gid %s; '
  512. 'adjust "sockchown" value in config file or '
  513. 'on command line to values that the '
  514. 'current user (%s) can successfully chown')
  515. raise ValueError(msg % (socketname,
  516. repr(sockchown),
  517. pwd.getpwuid(
  518. os.geteuid())[0],
  519. ),
  520. )
  521. else:
  522. raise
  523. self.prebind(sock, logger_object)
  524. break
  525. finally:
  526. try:
  527. os.unlink(tempname)
  528. except OSError:
  529. pass
  530. self.server_name = '<unix domain socket>'
  531. self.postbind()
  532. def checkused(self, socketname):
  533. s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  534. try:
  535. s.connect(socketname)
  536. s.send("GET / HTTP/1.0\r\n\r\n")
  537. s.recv(1)
  538. s.close()
  539. except socket.error:
  540. return False
  541. else:
  542. return True
  543. class tail_f_producer:
  544. def __init__(self, request, filename, head):
  545. self.file = open(filename, 'rb')
  546. self.request = weakref.ref(request)
  547. self.delay = 0.1
  548. sz = self.fsize()
  549. if sz >= head:
  550. self.sz = sz - head
  551. else:
  552. self.sz = 0
  553. def __del__(self):
  554. if self.file:
  555. self.file.close()
  556. def more(self):
  557. try:
  558. newsz = self.fsize()
  559. except OSError:
  560. # file descriptor was closed
  561. return ''
  562. bytes_added = newsz - self.sz
  563. if bytes_added < 0:
  564. self.sz = 0
  565. return "==> File truncated <==\n"
  566. if bytes_added > 0:
  567. self.file.seek(-bytes_added, 2)
  568. bytes = self.file.read(bytes_added)
  569. self.sz = newsz
  570. return bytes
  571. return NOT_DONE_YET
  572. def fsize(self):
  573. return os.fstat(self.file.fileno())[stat.ST_SIZE]
  574. class logtail_handler:
  575. IDENT = 'Logtail HTTP Request Handler'
  576. path = '/logtail'
  577. def __init__(self, supervisord):
  578. self.supervisord = supervisord
  579. def match(self, request):
  580. return request.uri.startswith(self.path)
  581. def handle_request(self, request):
  582. if request.command != 'GET':
  583. request.error (400) # bad request
  584. return
  585. path, params, query, fragment = request.split_uri()
  586. if '%' in path:
  587. path = http_server.unquote(path)
  588. # strip off all leading slashes
  589. while path and path[0] == '/':
  590. path = path[1:]
  591. path, process_name_and_channel = path.split('/', 1)
  592. try:
  593. process_name, channel = process_name_and_channel.split('/', 1)
  594. except ValueError:
  595. # no channel specified, default channel to stdout
  596. process_name = process_name_and_channel
  597. channel = 'stdout'
  598. from supervisor.options import split_namespec
  599. group_name, process_name = split_namespec(process_name)
  600. group = self.supervisord.process_groups.get(group_name)
  601. if group is None:
  602. request.error(404) # not found
  603. return
  604. process = group.processes.get(process_name)
  605. if process is None:
  606. request.error(404) # not found
  607. return
  608. logfile = getattr(process.config, '%s_logfile' % channel, None)
  609. if logfile is None or not os.path.exists(logfile):
  610. # XXX problematic: processes that don't start won't have a log
  611. # file and we probably don't want to go into fatal state if we try
  612. # to read the log of a process that did not start.
  613. request.error(410) # gone
  614. return
  615. mtime = os.stat(logfile)[stat.ST_MTIME]
  616. request['Last-Modified'] = http_date.build_http_date(mtime)
  617. request['Content-Type'] = 'text/plain'
  618. # the lack of a Content-Length header makes the outputter
  619. # send a 'Transfer-Encoding: chunked' response
  620. request.push(tail_f_producer(request, logfile, 1024))
  621. request.done()
  622. class mainlogtail_handler:
  623. IDENT = 'Main Logtail HTTP Request Handler'
  624. path = '/mainlogtail'
  625. def __init__(self, supervisord):
  626. self.supervisord = supervisord
  627. def match(self, request):
  628. return request.uri.startswith(self.path)
  629. def handle_request(self, request):
  630. if request.command != 'GET':
  631. request.error (400) # bad request
  632. return
  633. logfile = self.supervisord.options.logfile
  634. if logfile is None or not os.path.exists(logfile):
  635. request.error(410) # gone
  636. return
  637. mtime = os.stat(logfile)[stat.ST_MTIME]
  638. request['Last-Modified'] = http_date.build_http_date(mtime)
  639. request['Content-Type'] = 'text/plain'
  640. # the lack of a Content-Length header makes the outputter
  641. # send a 'Transfer-Encoding: chunked' response
  642. request.push(tail_f_producer(request, logfile, 1024))
  643. request.done()
  644. def make_http_servers(options, supervisord):
  645. servers = []
  646. class LogWrapper:
  647. def log(self, msg):
  648. if msg.endswith('\n'):
  649. msg = msg[:-1]
  650. options.logger.trace(msg)
  651. wrapper = LogWrapper()
  652. for config in options.server_configs:
  653. family = config['family']
  654. if family == socket.AF_INET:
  655. host, port = config['host'], config['port']
  656. hs = supervisor_af_inet_http_server(host, port,
  657. logger_object=wrapper)
  658. elif family == socket.AF_UNIX:
  659. socketname = config['file']
  660. sockchmod = config['chmod']
  661. sockchown = config['chown']
  662. hs = supervisor_af_unix_http_server(socketname,sockchmod, sockchown,
  663. logger_object=wrapper)
  664. else:
  665. raise ValueError('Cannot determine socket type %r' % family)
  666. from supervisor.xmlrpc import supervisor_xmlrpc_handler
  667. from supervisor.xmlrpc import SystemNamespaceRPCInterface
  668. from supervisor.web import supervisor_ui_handler
  669. subinterfaces = []
  670. for name, factory, d in options.rpcinterface_factories:
  671. try:
  672. inst = factory(supervisord, **d)
  673. except:
  674. import traceback; traceback.print_exc()
  675. raise ValueError('Could not make %s rpc interface' % name)
  676. subinterfaces.append((name, inst))
  677. options.logger.info('RPC interface %r initialized' % name)
  678. subinterfaces.append(('system',
  679. SystemNamespaceRPCInterface(subinterfaces)))
  680. xmlrpchandler = supervisor_xmlrpc_handler(supervisord, subinterfaces)
  681. tailhandler = logtail_handler(supervisord)
  682. maintailhandler = mainlogtail_handler(supervisord)
  683. uihandler = supervisor_ui_handler(supervisord)
  684. here = os.path.abspath(os.path.dirname(__file__))
  685. templatedir = os.path.join(here, 'ui')
  686. filesystem = filesys.os_filesystem(templatedir)
  687. defaulthandler = default_handler.default_handler(filesystem)
  688. username = config['username']
  689. password = config['password']
  690. if username:
  691. # wrap the xmlrpc handler and tailhandler in an authentication
  692. # handler
  693. users = {username:password}
  694. xmlrpchandler = supervisor_auth_handler(users, xmlrpchandler)
  695. tailhandler = supervisor_auth_handler(users, tailhandler)
  696. maintailhandler = supervisor_auth_handler(users, maintailhandler)
  697. uihandler = supervisor_auth_handler(users, uihandler)
  698. defaulthandler = supervisor_auth_handler(users, defaulthandler)
  699. else:
  700. options.logger.critical(
  701. 'Server %r running without any HTTP '
  702. 'authentication checking' % config['section'])
  703. # defaulthandler must be consulted last as its match method matches
  704. # everything, so it's first here (indicating last checked)
  705. hs.install_handler(defaulthandler)
  706. hs.install_handler(uihandler)
  707. hs.install_handler(maintailhandler)
  708. hs.install_handler(tailhandler)
  709. hs.install_handler(xmlrpchandler) # last for speed (first checked)
  710. servers.append((config, hs))
  711. return servers
  712. class encrypted_dictionary_authorizer:
  713. def __init__ (self, dict):
  714. self.dict = dict
  715. def authorize(self, auth_info):
  716. username, password = auth_info
  717. if username in self.dict:
  718. stored_password = self.dict[username]
  719. if stored_password.startswith('{SHA}'):
  720. password_hash = sha1(as_bytes(password)).hexdigest()
  721. return stored_password[5:] == password_hash
  722. else:
  723. return stored_password == password
  724. else:
  725. return False
  726. class supervisor_auth_handler(auth_handler):
  727. def __init__(self, dict, handler, realm='default'):
  728. auth_handler.__init__(self, dict, handler, realm)
  729. # override the authorizer with one that knows about SHA hashes too
  730. self.authorizer = encrypted_dictionary_authorizer(dict)