0001-kill-phantomjs-process-if-timeout-14606.patch
mandayejs/mandaye/utils.py | ||
---|---|---|
17 | 17 |
import json |
18 | 18 |
import subprocess |
19 | 19 |
import logging |
20 |
import multiprocessing |
|
20 | 21 | |
21 | 22 |
from django.conf import settings |
22 | 23 | |
... | ... | |
28 | 29 | |
29 | 30 | |
30 | 31 |
def exec_phantom(data, script='do_login.js'): |
31 |
phantom = subprocess.Popen([ |
|
32 |
settings.PHANTOM_JS_BINARY, |
|
33 |
'--ignore-ssl-errors=yes', '--ssl-protocol=any', |
|
34 |
os.path.join(settings.BASE_DIR, 'mandayejs', script)], |
|
35 |
close_fds=True, |
|
36 |
stdin=subprocess.PIPE, |
|
37 |
stdout=subprocess.PIPE |
|
38 |
) |
|
39 | 32 | |
40 |
stdout, stderr = phantom.communicate(json.dumps(data)) |
|
33 |
def run(send_end): |
|
34 |
phantom = subprocess.Popen([ |
|
35 |
settings.PHANTOM_JS_BINARY, |
|
36 |
'--ignore-ssl-errors=yes', '--ssl-protocol=any', |
|
37 |
os.path.join(settings.BASE_DIR, 'mandayejs', script)], |
|
38 |
close_fds=True, |
|
39 |
stdin=subprocess.PIPE, |
|
40 |
stdout=subprocess.PIPE |
|
41 |
) |
|
42 |
stdout, stderr = phantom.communicate(json.dumps(data)) |
|
43 | ||
44 |
try: |
|
45 |
result = json.loads(stdout) |
|
46 |
except (ValueError,): |
|
47 |
result = {"result": "failure, couldn't decode JSON"} |
|
48 |
logger.error(stdout) |
|
49 | ||
50 |
if result.get('stderr'): |
|
51 |
logger.warning(result['stderr']) |
|
52 | ||
53 |
send_end.send(result) |
|
54 | ||
55 |
recv_end, send_end = multiprocessing.Pipe(False) |
|
56 |
process = multiprocessing.Process(target=run, args=(send_end,)) |
|
57 |
process.start() |
|
58 |
process.join(settings.PHANTOM_JS_TIMEOUT) |
|
59 | ||
60 |
if process.is_alive(): |
|
61 |
process.terminate() |
|
62 |
# Don't log locators, they may contain credentials (passwords) |
|
63 |
context = {k: v for k, v in data.items() if k != 'locators'} |
|
64 |
logger.error("PhantomJS process timeout, context: %s" % context) |
|
65 |
result = {'result': 'timeout'} |
|
66 |
else: |
|
67 |
result = recv_end.recv() |
|
41 | 68 | |
42 |
try: |
|
43 |
result = json.loads(stdout) |
|
44 |
except (ValueError,): |
|
45 |
result = {"result": "failure, couldn't decode JSON"} |
|
46 |
logger.error(stdout) |
|
47 | ||
48 |
# only kill process if it's still running |
|
49 |
if phantom.poll() is None: |
|
50 |
phantom.terminate() |
|
51 | ||
52 |
if result.get('stderr'): |
|
53 |
logger.warning(result['stderr']) |
|
54 | 69 |
return result |
55 | 70 | |
56 | 71 |
mandayejs/mandaye/views.py | ||
---|---|---|
155 | 155 |
credentials.delete() |
156 | 156 |
messages.error(request, _('wrong user credentials')) |
157 | 157 |
url = resolve_url('associate') |
158 |
elif result.get('result') == 'timeout': |
|
159 |
messages.error(request, _('server took too long to respond')) |
|
160 |
url = resolve_url('associate') |
|
158 | 161 |
elif result.get('result') == 'redirect': |
159 | 162 |
url = result.get('url', '/') |
160 | 163 |
else: |
mandayejs/settings.py | ||
---|---|---|
164 | 164 | |
165 | 165 |
PHANTOM_JS_BINARY = '/usr/bin/phantomjs' |
166 | 166 | |
167 |
# Default timeout before killing Phantomjs process |
|
168 |
PHANTOM_JS_TIMEOUT = 10 |
|
167 | 169 | |
168 | 170 |
JSONFIELD_ENCODER_CLASS = 'django.core.serializers.json.DjangoJSONEncoder' |
169 | 171 |
tests/settings.py | ||
---|---|---|
47 | 47 |
api_settings.user_settings.update({ |
48 | 48 |
'DEFAULT_AUTHENTICATION_CLASSES' : AUTHENTICATION_CLASSES, |
49 | 49 |
}) |
50 | ||
51 | ||
52 |
PHANTOM_JS_TIMEOUT = 0.25 |
tests/test_mandayejs.py | ||
---|---|---|
1 | 1 |
# -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 |
import json |
4 |
import time |
|
4 | 5 |
import mock |
5 | 6 |
import pytest |
6 | 7 | |
... | ... | |
47 | 48 | |
48 | 49 | |
49 | 50 |
class MockedPopen(mock.Mock): |
51 |
stall_for = False |
|
50 | 52 | |
51 | 53 |
def communicate(self, data): |
54 |
if self.stall_for: |
|
55 |
time.sleep(13) |
|
52 | 56 |
return self.expected_output |
53 | 57 | |
54 | 58 | |
... | ... | |
272 | 276 |
assert result['url'] == 'https://whatever.com/someurl' |
273 | 277 | |
274 | 278 | |
279 |
@mock.patch('mandayejs.mandaye.utils.subprocess.Popen') |
|
280 |
def test_phantom_js_timeout(mocked_popen, caplog): |
|
281 |
mocked_popen.return_value = MockedPopen(expected_output=json.dumps({'whatever': 'whatever'}), stall_for=True) |
|
282 |
result = exec_phantom(LOGIN_INFO) |
|
283 | ||
284 |
for record in caplog.records(): |
|
285 |
assert record.levelname == 'ERROR' |
|
286 |
assert 'https://whatever.com' in record.message |
|
287 |
assert 'tenants/static/js/auth.checker.js' in record.message |
|
288 |
assert 'input[type=submit], button' in record.message |
|
289 | ||
290 |
assert result['result'] == 'timeout' |
|
291 | ||
292 | ||
275 | 293 |
@mock.patch('mandayejs.applications.Test.SITE_LOCATORS', MOCKED_SITE_LOCATORS) |
276 | 294 |
def test_credentials_json_encoding(user_john): |
277 | 295 | |
278 |
- |