kill phantomjs process if timeout (#14606)

This commit is contained in:
Josue Kouka 2017-01-27 06:33:26 +01:00
parent 7b2300d128
commit ecb03255f8
5 changed files with 60 additions and 19 deletions

View File

@ -17,6 +17,7 @@ import os
import json
import subprocess
import logging
import multiprocessing
from django.conf import settings
@ -28,29 +29,43 @@ logger = logging.getLogger(__name__)
def exec_phantom(data, script='do_login.js'):
phantom = subprocess.Popen([
settings.PHANTOM_JS_BINARY,
'--ignore-ssl-errors=yes', '--ssl-protocol=any',
os.path.join(settings.BASE_DIR, 'mandayejs', script)],
close_fds=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
stdout, stderr = phantom.communicate(json.dumps(data))
def run(send_end):
phantom = subprocess.Popen([
settings.PHANTOM_JS_BINARY,
'--ignore-ssl-errors=yes', '--ssl-protocol=any',
os.path.join(settings.BASE_DIR, 'mandayejs', script)],
close_fds=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
stdout, stderr = phantom.communicate(json.dumps(data))
try:
result = json.loads(stdout)
except (ValueError,):
result = {"result": "failure, couldn't decode JSON"}
logger.error(stdout)
try:
result = json.loads(stdout)
except (ValueError,):
result = {"result": "failure, couldn't decode JSON"}
logger.error(stdout)
# only kill process if it's still running
if phantom.poll() is None:
phantom.terminate()
if result.get('stderr'):
logger.warning(result['stderr'])
send_end.send(result)
recv_end, send_end = multiprocessing.Pipe(False)
process = multiprocessing.Process(target=run, args=(send_end,))
process.start()
process.join(settings.PHANTOM_JS_TIMEOUT)
if process.is_alive():
process.terminate()
# Don't log locators, they may contain credentials (passwords)
context = {k: v for k, v in data.items() if k != 'locators'}
logger.error("PhantomJS process timeout, context: %s" % context)
result = {'result': 'timeout'}
else:
result = recv_end.recv()
if result.get('stderr'):
logger.warning(result['stderr'])
return result

View File

@ -155,6 +155,9 @@ def post_login_do(request, *args, **kwargs):
credentials.delete()
messages.error(request, _('wrong user credentials'))
url = resolve_url('associate')
elif result.get('result') == 'timeout':
messages.error(request, _('server took too long to respond'))
url = resolve_url('associate')
elif result.get('result') == 'redirect':
url = result.get('url', '/')
else:

View File

@ -164,6 +164,8 @@ SITE_APP = None
PHANTOM_JS_BINARY = '/usr/bin/phantomjs'
# Default timeout before killing Phantomjs process
PHANTOM_JS_TIMEOUT = 10
JSONFIELD_ENCODER_CLASS = 'django.core.serializers.json.DjangoJSONEncoder'

View File

@ -47,3 +47,6 @@ else:
api_settings.user_settings.update({
'DEFAULT_AUTHENTICATION_CLASSES' : AUTHENTICATION_CLASSES,
})
PHANTOM_JS_TIMEOUT = 0.25

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import json
import time
import mock
import pytest
@ -47,8 +48,11 @@ MOCKED_SITE_LOCATORS = [
class MockedPopen(mock.Mock):
stall_for = False
def communicate(self, data):
if self.stall_for:
time.sleep(13)
return self.expected_output
@ -272,6 +276,20 @@ def test_phantom_js_errors(mocked_popen, caplog):
assert result['url'] == 'https://whatever.com/someurl'
@mock.patch('mandayejs.mandaye.utils.subprocess.Popen')
def test_phantom_js_timeout(mocked_popen, caplog):
mocked_popen.return_value = MockedPopen(expected_output=json.dumps({'whatever': 'whatever'}), stall_for=True)
result = exec_phantom(LOGIN_INFO)
for record in caplog.records():
assert record.levelname == 'ERROR'
assert 'https://whatever.com' in record.message
assert 'tenants/static/js/auth.checker.js' in record.message
assert 'input[type=submit], button' in record.message
assert result['result'] == 'timeout'
@mock.patch('mandayejs.applications.Test.SITE_LOCATORS', MOCKED_SITE_LOCATORS)
def test_credentials_json_encoding(user_john):