test_http.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. import base64
  2. import os
  3. import stat
  4. import sys
  5. import socket
  6. import tempfile
  7. import unittest
  8. from supervisor.compat import as_bytes
  9. from supervisor.compat import as_string
  10. from supervisor.compat import sha1
  11. from supervisor.tests.base import DummySupervisor
  12. from supervisor.tests.base import PopulatedDummySupervisor
  13. from supervisor.tests.base import DummyRPCInterfaceFactory
  14. from supervisor.tests.base import DummyPConfig
  15. from supervisor.tests.base import DummyOptions
  16. from supervisor.tests.base import DummyRequest
  17. from supervisor.http import NOT_DONE_YET
  18. class HandlerTests:
  19. def _makeOne(self, supervisord):
  20. return self._getTargetClass()(supervisord)
  21. def test_match(self):
  22. class FakeRequest:
  23. def __init__(self, uri):
  24. self.uri = uri
  25. supervisor = DummySupervisor()
  26. handler = self._makeOne(supervisor)
  27. self.assertEqual(handler.match(FakeRequest(handler.path)), True)
  28. class LogtailHandlerTests(HandlerTests, unittest.TestCase):
  29. def _getTargetClass(self):
  30. from supervisor.http import logtail_handler
  31. return logtail_handler
  32. def test_handle_request_stdout_logfile_none(self):
  33. options = DummyOptions()
  34. pconfig = DummyPConfig(options, 'process1', '/bin/process1', priority=1,
  35. stdout_logfile='/tmp/process1.log')
  36. supervisord = PopulatedDummySupervisor(options, 'process1', pconfig)
  37. handler = self._makeOne(supervisord)
  38. request = DummyRequest('/logtail/process1', None, None, None)
  39. handler.handle_request(request)
  40. self.assertEqual(request._error, 410)
  41. def test_handle_request_stdout_logfile_missing(self):
  42. options = DummyOptions()
  43. pconfig = DummyPConfig(options, 'foo', 'foo', 'it/is/missing')
  44. supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)
  45. handler = self._makeOne(supervisord)
  46. request = DummyRequest('/logtail/foo', None, None, None)
  47. handler.handle_request(request)
  48. self.assertEqual(request._error, 410)
  49. def test_handle_request(self):
  50. f = tempfile.NamedTemporaryFile()
  51. t = f.name
  52. options = DummyOptions()
  53. pconfig = DummyPConfig(options, 'foo', 'foo', stdout_logfile=t)
  54. supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)
  55. handler = self._makeOne(supervisord)
  56. request = DummyRequest('/logtail/foo', None, None, None)
  57. handler.handle_request(request)
  58. self.assertEqual(request._error, None)
  59. from supervisor.medusa import http_date
  60. self.assertEqual(request.headers['Last-Modified'],
  61. http_date.build_http_date(os.stat(t)[stat.ST_MTIME]))
  62. self.assertEqual(request.headers['Content-Type'], 'text/plain')
  63. self.assertEqual(len(request.producers), 1)
  64. self.assertEqual(request._done, True)
  65. class MainLogTailHandlerTests(HandlerTests, unittest.TestCase):
  66. def _getTargetClass(self):
  67. from supervisor.http import mainlogtail_handler
  68. return mainlogtail_handler
  69. def test_handle_request_stdout_logfile_none(self):
  70. supervisor = DummySupervisor()
  71. handler = self._makeOne(supervisor)
  72. request = DummyRequest('/mainlogtail', None, None, None)
  73. handler.handle_request(request)
  74. self.assertEqual(request._error, 410)
  75. def test_handle_request_stdout_logfile_missing(self):
  76. supervisor = DummySupervisor()
  77. supervisor.options.logfile = '/not/there'
  78. request = DummyRequest('/mainlogtail', None, None, None)
  79. handler = self._makeOne(supervisor)
  80. handler.handle_request(request)
  81. self.assertEqual(request._error, 410)
  82. def test_handle_request(self):
  83. supervisor = DummySupervisor()
  84. f = tempfile.NamedTemporaryFile()
  85. t = f.name
  86. supervisor.options.logfile = t
  87. handler = self._makeOne(supervisor)
  88. request = DummyRequest('/mainlogtail', None, None, None)
  89. handler.handle_request(request)
  90. self.assertEqual(request._error, None)
  91. from supervisor.medusa import http_date
  92. self.assertEqual(request.headers['Last-Modified'],
  93. http_date.build_http_date(os.stat(t)[stat.ST_MTIME]))
  94. self.assertEqual(request.headers['Content-Type'], 'text/plain')
  95. self.assertEqual(len(request.producers), 1)
  96. self.assertEqual(request._done, True)
  97. class TailFProducerTests(unittest.TestCase):
  98. def _getTargetClass(self):
  99. from supervisor.http import tail_f_producer
  100. return tail_f_producer
  101. def _makeOne(self, request, filename, head):
  102. return self._getTargetClass()(request, filename, head)
  103. def test_handle_more(self):
  104. request = DummyRequest('/logtail/foo', None, None, None)
  105. from supervisor import http
  106. f = tempfile.NamedTemporaryFile()
  107. f.write(as_bytes('a' * 80))
  108. f.flush()
  109. producer = self._makeOne(request, f.name, 80)
  110. result = producer.more()
  111. self.assertEqual(result, as_bytes('a' * 80))
  112. f.write(as_bytes('w' * 100))
  113. f.flush()
  114. result = producer.more()
  115. self.assertEqual(result, as_bytes('w' * 100))
  116. result = producer.more()
  117. self.assertEqual(result, http.NOT_DONE_YET)
  118. f.truncate(0)
  119. f.flush()
  120. result = producer.more()
  121. self.assertEqual(result, '==> File truncated <==\n')
  122. def test_handle_more_fd_closed(self):
  123. request = DummyRequest('/logtail/foo', None, None, None)
  124. f = tempfile.NamedTemporaryFile()
  125. f.write(as_bytes('a' * 80))
  126. f.flush()
  127. producer = self._makeOne(request, f.name, 80)
  128. producer.file.close()
  129. result = producer.more()
  130. self.assertEqual(result, producer.more())
  131. def test_handle_more_follow_file_recreated(self):
  132. request = DummyRequest('/logtail/foo', None, None, None)
  133. f = tempfile.NamedTemporaryFile()
  134. f.write(as_bytes('a' * 80))
  135. f.flush()
  136. producer = self._makeOne(request, f.name, 80)
  137. result = producer.more()
  138. self.assertEqual(result, as_bytes('a' * 80))
  139. f.close()
  140. f2 = open(f.name, 'wb')
  141. try:
  142. f2.write(as_bytes('b' * 80))
  143. f2.close()
  144. result = producer.more()
  145. finally:
  146. os.unlink(f2.name)
  147. self.assertEqual(result, as_bytes('b' * 80))
  148. def test_handle_more_follow_file_gone(self):
  149. request = DummyRequest('/logtail/foo', None, None, None)
  150. filename = tempfile.mktemp()
  151. with open(filename, 'wb') as f:
  152. f.write(as_bytes('a' * 80))
  153. try:
  154. producer = self._makeOne(request, f.name, 80)
  155. finally:
  156. os.unlink(f.name)
  157. result = producer.more()
  158. self.assertEqual(result, as_bytes('a' * 80))
  159. with open(filename, 'wb') as f:
  160. f.write(as_bytes('b' * 80))
  161. try:
  162. result = producer.more() # should open in new file
  163. self.assertEqual(result, as_bytes('b' * 80))
  164. finally:
  165. os.unlink(f.name)
  166. class DeferringChunkedProducerTests(unittest.TestCase):
  167. def _getTargetClass(self):
  168. from supervisor.http import deferring_chunked_producer
  169. return deferring_chunked_producer
  170. def _makeOne(self, producer, footers=None):
  171. return self._getTargetClass()(producer, footers)
  172. def test_more_not_done_yet(self):
  173. wrapped = DummyProducer(NOT_DONE_YET)
  174. producer = self._makeOne(wrapped)
  175. self.assertEqual(producer.more(), NOT_DONE_YET)
  176. def test_more_string(self):
  177. wrapped = DummyProducer('hello')
  178. producer = self._makeOne(wrapped)
  179. self.assertEqual(producer.more(), '5\r\nhello\r\n')
  180. def test_more_nodata(self):
  181. wrapped = DummyProducer()
  182. producer = self._makeOne(wrapped, footers=['a', 'b'])
  183. self.assertEqual(producer.more(), '0\r\na\r\nb\r\n\r\n')
  184. def test_more_nodata_footers(self):
  185. wrapped = DummyProducer('')
  186. producer = self._makeOne(wrapped, footers=['a', 'b'])
  187. self.assertEqual(producer.more(), '0\r\na\r\nb\r\n\r\n')
  188. def test_more_nodata_nofooters(self):
  189. wrapped = DummyProducer('')
  190. producer = self._makeOne(wrapped)
  191. self.assertEqual(producer.more(), '0\r\n\r\n')
  192. def test_more_noproducer(self):
  193. producer = self._makeOne(None)
  194. self.assertEqual(producer.more(), '')
  195. class DeferringCompositeProducerTests(unittest.TestCase):
  196. def _getTargetClass(self):
  197. from supervisor.http import deferring_composite_producer
  198. return deferring_composite_producer
  199. def _makeOne(self, producers):
  200. return self._getTargetClass()(producers)
  201. def test_more_not_done_yet(self):
  202. wrapped = DummyProducer(NOT_DONE_YET)
  203. producer = self._makeOne([wrapped])
  204. self.assertEqual(producer.more(), NOT_DONE_YET)
  205. def test_more_string(self):
  206. wrapped1 = DummyProducer('hello')
  207. wrapped2 = DummyProducer('goodbye')
  208. producer = self._makeOne([wrapped1, wrapped2])
  209. self.assertEqual(producer.more(), 'hello')
  210. self.assertEqual(producer.more(), 'goodbye')
  211. self.assertEqual(producer.more(), '')
  212. def test_more_nodata(self):
  213. wrapped = DummyProducer()
  214. producer = self._makeOne([wrapped])
  215. self.assertEqual(producer.more(), '')
  216. class DeferringGlobbingProducerTests(unittest.TestCase):
  217. def _getTargetClass(self):
  218. from supervisor.http import deferring_globbing_producer
  219. return deferring_globbing_producer
  220. def _makeOne(self, producer, buffer_size=1<<16):
  221. return self._getTargetClass()(producer, buffer_size)
  222. def test_more_not_done_yet(self):
  223. wrapped = DummyProducer(NOT_DONE_YET)
  224. producer = self._makeOne(wrapped)
  225. self.assertEqual(producer.more(), NOT_DONE_YET)
  226. def test_more_string(self):
  227. wrapped = DummyProducer('hello', 'there', 'guy')
  228. producer = self._makeOne(wrapped, buffer_size=1)
  229. self.assertEqual(producer.more(), 'hello')
  230. wrapped = DummyProducer('hello', 'there', 'guy')
  231. producer = self._makeOne(wrapped, buffer_size=50)
  232. self.assertEqual(producer.more(), 'hellothereguy')
  233. def test_more_nodata(self):
  234. wrapped = DummyProducer()
  235. producer = self._makeOne(wrapped)
  236. self.assertEqual(producer.more(), '')
  237. class DeferringHookedProducerTests(unittest.TestCase):
  238. def _getTargetClass(self):
  239. from supervisor.http import deferring_hooked_producer
  240. return deferring_hooked_producer
  241. def _makeOne(self, producer, function):
  242. return self._getTargetClass()(producer, function)
  243. def test_more_not_done_yet(self):
  244. wrapped = DummyProducer(NOT_DONE_YET)
  245. producer = self._makeOne(wrapped, None)
  246. self.assertEqual(producer.more(), NOT_DONE_YET)
  247. def test_more_string(self):
  248. wrapped = DummyProducer('hello')
  249. L = []
  250. def callback(bytes):
  251. L.append(bytes)
  252. producer = self._makeOne(wrapped, callback)
  253. self.assertEqual(producer.more(), 'hello')
  254. self.assertEqual(L, [])
  255. producer.more()
  256. self.assertEqual(L, [5])
  257. def test_more_nodata(self):
  258. wrapped = DummyProducer()
  259. L = []
  260. def callback(bytes):
  261. L.append(bytes)
  262. producer = self._makeOne(wrapped, callback)
  263. self.assertEqual(producer.more(), '')
  264. self.assertEqual(L, [0])
  265. def test_more_noproducer(self):
  266. producer = self._makeOne(None, None)
  267. self.assertEqual(producer.more(), '')
  268. class Test_deferring_http_request(unittest.TestCase):
  269. def _getTargetClass(self):
  270. from supervisor.http import deferring_http_request
  271. return deferring_http_request
  272. def _makeOne(
  273. self,
  274. channel=None,
  275. req='GET / HTTP/1.0',
  276. command='GET',
  277. uri='/',
  278. version='1.0',
  279. header=(),
  280. ):
  281. return self._getTargetClass()(
  282. channel, req, command, uri, version, header
  283. )
  284. def _makeChannel(self):
  285. class Channel:
  286. closed = False
  287. def close_when_done(self):
  288. self.closed = True
  289. def push_with_producer(self, producer):
  290. self.producer = producer
  291. return Channel()
  292. def test_done_http_10_nokeepalive(self):
  293. channel = self._makeChannel()
  294. inst = self._makeOne(channel=channel, version='1.0')
  295. inst.done()
  296. self.assertTrue(channel.closed)
  297. def test_done_http_10_keepalive_no_content_length(self):
  298. channel = self._makeChannel()
  299. inst = self._makeOne(
  300. channel=channel,
  301. version='1.0',
  302. header=['Connection: Keep-Alive'],
  303. )
  304. inst.done()
  305. self.assertTrue(channel.closed)
  306. def test_done_http_10_keepalive_and_content_length(self):
  307. channel = self._makeChannel()
  308. inst = self._makeOne(
  309. channel=channel,
  310. version='1.0',
  311. header=['Connection: Keep-Alive'],
  312. )
  313. inst.reply_headers['Content-Length'] = 1
  314. inst.done()
  315. self.assertEqual(inst['Connection'], 'Keep-Alive')
  316. self.assertFalse(channel.closed)
  317. def test_done_http_11_connection_close(self):
  318. channel = self._makeChannel()
  319. inst = self._makeOne(
  320. channel=channel,
  321. version='1.1',
  322. header=['Connection: close']
  323. )
  324. inst.done()
  325. self.assertTrue(channel.closed)
  326. def test_done_http_11_unknown_transfer_encoding(self):
  327. channel = self._makeChannel()
  328. inst = self._makeOne(
  329. channel=channel,
  330. version='1.1',
  331. )
  332. inst.reply_headers['Transfer-Encoding'] = 'notchunked'
  333. inst.done()
  334. self.assertTrue(channel.closed)
  335. def test_done_http_11_chunked_transfer_encoding(self):
  336. channel = self._makeChannel()
  337. inst = self._makeOne(
  338. channel=channel,
  339. version='1.1',
  340. )
  341. inst.reply_headers['Transfer-Encoding'] = 'chunked'
  342. inst.done()
  343. self.assertFalse(channel.closed)
  344. def test_done_http_11_use_chunked(self):
  345. channel = self._makeChannel()
  346. inst = self._makeOne(
  347. channel=channel,
  348. version='1.1',
  349. )
  350. inst.use_chunked = True
  351. inst.done()
  352. self.assertTrue('Transfer-Encoding' in inst)
  353. self.assertFalse(channel.closed)
  354. def test_done_http_11_wo_content_length_no_te_no_use_chunked_close(self):
  355. channel = self._makeChannel()
  356. inst = self._makeOne(
  357. channel=channel,
  358. version='1.1',
  359. )
  360. inst.use_chunked = False
  361. inst.done()
  362. self.assertTrue(channel.closed)
  363. def test_done_http_09(self):
  364. channel = self._makeChannel()
  365. inst = self._makeOne(
  366. channel=channel,
  367. version=None,
  368. )
  369. inst.done()
  370. self.assertTrue(channel.closed)
  371. class EncryptedDictionaryAuthorizedTests(unittest.TestCase):
  372. def _getTargetClass(self):
  373. from supervisor.http import encrypted_dictionary_authorizer
  374. return encrypted_dictionary_authorizer
  375. def _makeOne(self, dict):
  376. return self._getTargetClass()(dict)
  377. def test_authorize_baduser(self):
  378. authorizer = self._makeOne({})
  379. self.assertFalse(authorizer.authorize(('foo', 'bar')))
  380. def test_authorize_gooduser_badpassword(self):
  381. authorizer = self._makeOne({'foo':'password'})
  382. self.assertFalse(authorizer.authorize(('foo', 'bar')))
  383. def test_authorize_gooduser_goodpassword(self):
  384. authorizer = self._makeOne({'foo':'password'})
  385. self.assertTrue(authorizer.authorize(('foo', 'password')))
  386. def test_authorize_gooduser_goodpassword_with_colon(self):
  387. authorizer = self._makeOne({'foo':'pass:word'})
  388. self.assertTrue(authorizer.authorize(('foo', 'pass:word')))
  389. def test_authorize_gooduser_badpassword_sha(self):
  390. password = '{SHA}' + sha1(as_bytes('password')).hexdigest()
  391. authorizer = self._makeOne({'foo':password})
  392. self.assertFalse(authorizer.authorize(('foo', 'bar')))
  393. def test_authorize_gooduser_goodpassword_sha(self):
  394. password = '{SHA}' + sha1(as_bytes('password')).hexdigest()
  395. authorizer = self._makeOne({'foo':password})
  396. self.assertTrue(authorizer.authorize(('foo', 'password')))
  397. class SupervisorAuthHandlerTests(unittest.TestCase):
  398. def _getTargetClass(self):
  399. from supervisor.http import supervisor_auth_handler
  400. return supervisor_auth_handler
  401. def _makeOne(self, dict, handler):
  402. return self._getTargetClass()(dict, handler)
  403. def test_ctor(self):
  404. handler = self._makeOne({'a':1}, None)
  405. from supervisor.http import encrypted_dictionary_authorizer
  406. self.assertEqual(handler.authorizer.__class__,
  407. encrypted_dictionary_authorizer)
  408. def test_handle_request_authorizes_good_credentials(self):
  409. request = DummyRequest('/logtail/process1', None, None, None)
  410. encoded = base64.b64encode(as_bytes("user:password"))
  411. request.header = ["Authorization: Basic %s" % as_string(encoded)]
  412. handler = DummyHandler()
  413. auth_handler = self._makeOne({'user':'password'}, handler)
  414. auth_handler.handle_request(request)
  415. self.assertTrue(handler.handled_request)
  416. def test_handle_request_authorizes_good_password_with_colon(self):
  417. request = DummyRequest('/logtail/process1', None, None, None)
  418. # password contains colon
  419. encoded = base64.b64encode(as_bytes("user:pass:word"))
  420. request.header = ["Authorization: Basic %s" % as_string(encoded)]
  421. handler = DummyHandler()
  422. auth_handler = self._makeOne({'user':'pass:word'}, handler)
  423. auth_handler.handle_request(request)
  424. self.assertTrue(handler.handled_request)
  425. def test_handle_request_does_not_authorize_bad_credentials(self):
  426. request = DummyRequest('/logtail/process1', None, None, None)
  427. encoded = base64.b64encode(as_bytes("wrong:wrong"))
  428. request.header = ["Authorization: Basic %s" % as_string(encoded)]
  429. handler = DummyHandler()
  430. auth_handler = self._makeOne({'user':'password'}, handler)
  431. auth_handler.handle_request(request)
  432. self.assertFalse(handler.handled_request)
  433. class TopLevelFunctionTests(unittest.TestCase):
  434. def _make_http_servers(self, sconfigs):
  435. options = DummyOptions()
  436. options.server_configs = sconfigs
  437. options.rpcinterface_factories = [('dummy',DummyRPCInterfaceFactory,{})]
  438. supervisord = DummySupervisor()
  439. from supervisor.http import make_http_servers
  440. servers = make_http_servers(options, supervisord)
  441. try:
  442. for config, s in servers:
  443. s.close()
  444. socketfile = config.get('file')
  445. if socketfile is not None:
  446. os.unlink(socketfile)
  447. finally:
  448. from asyncore import socket_map
  449. socket_map.clear()
  450. return servers
  451. def test_make_http_servers_socket_type_error(self):
  452. config = {'family':999, 'host':'localhost', 'port':17735,
  453. 'username':None, 'password':None,
  454. 'section':'inet_http_server'}
  455. try:
  456. self._make_http_servers([config])
  457. self.fail('nothing raised')
  458. except ValueError as exc:
  459. self.assertEqual(exc.args[0], 'Cannot determine socket type 999')
  460. def test_make_http_servers_noauth(self):
  461. socketfile = tempfile.mktemp()
  462. inet = {'family':socket.AF_INET, 'host':'localhost', 'port':17735,
  463. 'username':None, 'password':None, 'section':'inet_http_server'}
  464. unix = {'family':socket.AF_UNIX, 'file':socketfile, 'chmod':448, # 0700 in Py2, 0o700 in Py3
  465. 'chown':(-1, -1), 'username':None, 'password':None,
  466. 'section':'unix_http_server'}
  467. servers = self._make_http_servers([inet, unix])
  468. self.assertEqual(len(servers), 2)
  469. inetdata = servers[0]
  470. self.assertEqual(inetdata[0], inet)
  471. server = inetdata[1]
  472. idents = [
  473. 'Supervisor XML-RPC Handler',
  474. 'Logtail HTTP Request Handler',
  475. 'Main Logtail HTTP Request Handler',
  476. 'Supervisor Web UI HTTP Request Handler',
  477. 'Default HTTP Request Handler'
  478. ]
  479. self.assertEqual([x.IDENT for x in server.handlers], idents)
  480. unixdata = servers[1]
  481. self.assertEqual(unixdata[0], unix)
  482. server = unixdata[1]
  483. self.assertEqual([x.IDENT for x in server.handlers], idents)
  484. def test_make_http_servers_withauth(self):
  485. socketfile = tempfile.mktemp()
  486. inet = {'family':socket.AF_INET, 'host':'localhost', 'port':17736,
  487. 'username':'username', 'password':'password',
  488. 'section':'inet_http_server'}
  489. unix = {'family':socket.AF_UNIX, 'file':socketfile, 'chmod':448, # 0700 in Py2, 0o700 in Py3
  490. 'chown':(-1, -1), 'username':'username', 'password':'password',
  491. 'section':'unix_http_server'}
  492. servers = self._make_http_servers([inet, unix])
  493. self.assertEqual(len(servers), 2)
  494. from supervisor.http import supervisor_auth_handler
  495. for config, server in servers:
  496. for handler in server.handlers:
  497. self.assertTrue(isinstance(handler, supervisor_auth_handler),
  498. handler)
  499. class DummyHandler:
  500. def __init__(self):
  501. self.handled_request = False
  502. def handle_request(self, request):
  503. self.handled_request = True
  504. class DummyProducer:
  505. def __init__(self, *data):
  506. self.data = list(data)
  507. def more(self):
  508. if self.data:
  509. return self.data.pop(0)
  510. else:
  511. return ''
  512. def test_suite():
  513. return unittest.findTestCases(sys.modules[__name__])
  514. if __name__ == '__main__':
  515. unittest.main(defaultTest='test_suite')