wscall: make it possible to store response as attachment (#10559)
This commit is contained in:
parent
53dbcb54ca
commit
e1f9c6b4dc
|
@ -14,7 +14,8 @@ from wcs.roles import Role
|
|||
from wcs.workflows import (Workflow, WorkflowStatusItem,
|
||||
SendmailWorkflowStatusItem, SendSMSWorkflowStatusItem,
|
||||
DisplayMessageWorkflowStatusItem,
|
||||
AbortActionException, WorkflowCriticalityLevel)
|
||||
AbortActionException, WorkflowCriticalityLevel,
|
||||
AttachmentEvolutionPart)
|
||||
from wcs.wf.anonymise import AnonymiseWorkflowStatusItem
|
||||
from wcs.wf.criticality import ModifyCriticalityWorkflowStatusItem, MODE_INC, MODE_DEC, MODE_SET
|
||||
from wcs.wf.dispatch import DispatchWorkflowStatusItem
|
||||
|
@ -637,6 +638,24 @@ def test_webservice_call(pub):
|
|||
assert 'xxx_error_response' not in formdata.workflow_data
|
||||
formdata.workflow_data = None
|
||||
|
||||
# check storing response as attachment
|
||||
item = WebserviceCallStatusItem()
|
||||
item.url = 'http://remote.example.net/xml'
|
||||
item.post = False
|
||||
item.varname = 'xxx'
|
||||
item.response_type = 'attachment'
|
||||
item.record_errors = True
|
||||
item.perform(formdata)
|
||||
assert formdata.workflow_data.get('xxx_status') == 200
|
||||
assert formdata.workflow_data.get('xxx_content_type') == 'text/xml'
|
||||
attachment = formdata.evolution[-1].parts[-1]
|
||||
assert isinstance(attachment, AttachmentEvolutionPart)
|
||||
assert attachment.base_filename == 'xxx.xml'
|
||||
assert attachment.content_type == 'text/xml'
|
||||
attachment.fp.seek(0)
|
||||
assert attachment.fp.read(5) == '<?xml'
|
||||
formdata.workflow_data = None
|
||||
|
||||
item = WebserviceCallStatusItem()
|
||||
item.url = 'http://remote.example.net/400-json'
|
||||
item.post = False
|
||||
|
|
|
@ -224,23 +224,29 @@ class HttpRequestsMocking(object):
|
|||
'headers': headers,
|
||||
'timeout': timeout})
|
||||
|
||||
status, data = {
|
||||
'http://remote.example.net/204': (204, None),
|
||||
'http://remote.example.net/400-json': (400, '{"err": 1, "err_desc": ":("}'),
|
||||
'http://remote.example.net/404': (404, 'page not found'),
|
||||
'http://remote.example.net/404-json': (404, '{"err": 1}'),
|
||||
'http://remote.example.net/500': (500, 'internal server error'),
|
||||
'http://remote.example.net/json': (200, '{"foo": "bar"}'),
|
||||
'http://remote.example.net/xml': (200, '<?xml version="1.0"><foo/>'),
|
||||
}.get(url, (200, ''))
|
||||
status, data, headers = {
|
||||
'http://remote.example.net/204': (204, None, None),
|
||||
'http://remote.example.net/400-json': (400, '{"err": 1, "err_desc": ":("}', None),
|
||||
'http://remote.example.net/404': (404, 'page not found', None),
|
||||
'http://remote.example.net/404-json': (404, '{"err": 1}', None),
|
||||
'http://remote.example.net/500': (500, 'internal server error', None),
|
||||
'http://remote.example.net/json': (200, '{"foo": "bar"}', None),
|
||||
'http://remote.example.net/xml': (200, '<?xml version="1.0"><foo/>',
|
||||
{'content-type': 'text/xml'}),
|
||||
}.get(url, (200, '', {}))
|
||||
|
||||
class FakeResponse(object):
|
||||
def __init__(self, status, data):
|
||||
def __init__(self, status, data, headers):
|
||||
self.status = status
|
||||
self.reason = 'whatever'
|
||||
self.data = data
|
||||
self.headers = headers or {}
|
||||
self.length = len(data or '')
|
||||
|
||||
return FakeResponse(status, data), status, data, None
|
||||
def getheader(self, header):
|
||||
return self.headers.get(header, None)
|
||||
|
||||
return FakeResponse(status, data, headers), status, data, None
|
||||
|
||||
def get_last(self, attribute):
|
||||
return self.requests[-1][attribute]
|
||||
|
|
|
@ -20,6 +20,8 @@ import sys
|
|||
import traceback
|
||||
import xml.etree.ElementTree as ET
|
||||
import collections
|
||||
import mimetypes
|
||||
from StringIO import StringIO
|
||||
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
from qommon.errors import ConnectionError
|
||||
|
@ -27,7 +29,7 @@ from qommon.form import *
|
|||
from qommon.misc import (http_get_page, http_post_request, get_variadic_url,
|
||||
JSONEncoder, json_loads, site_encode)
|
||||
from wcs.workflows import (WorkflowStatusItem, register_item_class,
|
||||
template_on_formdata, AbortActionException)
|
||||
template_on_formdata, AbortActionException, AttachmentEvolutionPart)
|
||||
from wcs.api import sign_url
|
||||
|
||||
TIMEOUT = 30
|
||||
|
@ -90,6 +92,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
|
|||
request_signature_key = None
|
||||
post_data = None
|
||||
_method = None
|
||||
response_type = 'json'
|
||||
|
||||
action_on_4xx = ':stop'
|
||||
action_on_5xx = ':stop'
|
||||
|
@ -120,7 +123,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
|
|||
return ('url', 'post', 'varname', 'request_signature_key', 'post_data',
|
||||
'action_on_4xx', 'action_on_5xx', 'action_on_bad_data',
|
||||
'action_on_network_errors', 'notify_on_errors',
|
||||
'record_errors', 'label', 'method')
|
||||
'record_errors', 'label', 'method', 'response_type')
|
||||
|
||||
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
||||
if 'label' in parameters:
|
||||
|
@ -161,6 +164,14 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
|
|||
})
|
||||
|
||||
form.widgets.append(HtmlWidget(htmltext('<h3>%s</h3>') % _('Response')))
|
||||
response_types = collections.OrderedDict(
|
||||
[('json', _('JSON')), ('attachment', _('Attachment'))])
|
||||
if 'response_type' in parameters:
|
||||
form.add(RadiobuttonsWidget, '%sresponse_type' % prefix,
|
||||
title=_('Response Type'),
|
||||
options=response_types.items(),
|
||||
value=self.response_type,
|
||||
attrs={'data-dynamic-display-parent': 'true'})
|
||||
if 'varname' in parameters:
|
||||
form.add(VarnameWidget, '%svarname' % prefix,
|
||||
title=_('Variable Name'), value=self.varname)
|
||||
|
@ -169,10 +180,17 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
|
|||
error_actions = [(':stop', _('Stop')), (':pass', _('Ignore'))]
|
||||
error_actions.extend([(x.id, _('Jump to %s') % x.name) for x in
|
||||
self.parent.parent.possible_status])
|
||||
for attribute in ('action_on_4xx', 'action_on_5xx', 'action_on_bad_data',
|
||||
'action_on_network_errors'):
|
||||
for attribute in ('action_on_4xx', 'action_on_5xx', 'action_on_network_errors',
|
||||
'action_on_bad_data'):
|
||||
if not attribute in parameters:
|
||||
continue
|
||||
if attribute == 'action_on_bad_data':
|
||||
attrs = {
|
||||
'data-dynamic-display-child-of': '%sresponse_type' % prefix,
|
||||
'data-dynamic-display-value': response_types.get('json'),
|
||||
}
|
||||
else:
|
||||
attrs = {}
|
||||
label = {
|
||||
'action_on_4xx': _('Action on HTTP error 4xx'),
|
||||
'action_on_5xx': _('Action on HTTP error 5xx'),
|
||||
|
@ -182,7 +200,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
|
|||
form.add(SingleSelectWidget, '%s%s' % (prefix, attribute),
|
||||
title=label,
|
||||
value=getattr(self, attribute),
|
||||
options=error_actions)
|
||||
options=error_actions, attrs=attrs)
|
||||
|
||||
if 'notify_on_errors' in parameters:
|
||||
form.add(CheckboxWidget, '%snotify_on_errors' % prefix,
|
||||
|
@ -238,7 +256,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
|
|||
# bytes, to match a country 512kbps DSL line.
|
||||
timeout = TIMEOUT
|
||||
timeout += len(post_data) / 65536
|
||||
response, status, data, authheader = http_post_request(
|
||||
response, status, data, auth_header = http_post_request(
|
||||
url, post_data, headers=headers, timeout=timeout)
|
||||
else:
|
||||
response, status, data, auth_header = http_get_page(
|
||||
|
@ -256,19 +274,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
|
|||
if status in (204, 205):
|
||||
pass # not returning any content
|
||||
elif (status // 100) == 2:
|
||||
try:
|
||||
d = json_loads(data)
|
||||
except (ValueError, TypeError) as e:
|
||||
formdata.update_workflow_data(workflow_data)
|
||||
formdata.store()
|
||||
self.action_on_error(self.action_on_bad_data, formdata,
|
||||
response, data=data, exc_info=sys.exc_info())
|
||||
else:
|
||||
workflow_data['%s_response' % self.varname] = d
|
||||
if isinstance(d.get('data'), dict) and d['data'].get('display_id'):
|
||||
formdata.id_display = d.get('data', {}).get('display_id')
|
||||
elif d.get('display_id'):
|
||||
formdata.id_display = d.get('display_id')
|
||||
self.store_response(formdata, response, data, workflow_data)
|
||||
else: # on error, record data if it is JSON
|
||||
try:
|
||||
d = json_loads(data)
|
||||
|
@ -284,6 +290,35 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
|
|||
if (status // 100) == 5:
|
||||
self.action_on_error(self.action_on_5xx, formdata, response, data=data)
|
||||
|
||||
def store_response(self, formdata, response, data, workflow_data):
|
||||
if self.response_type == 'json':
|
||||
try:
|
||||
d = json_loads(data)
|
||||
except (ValueError, TypeError) as e:
|
||||
formdata.update_workflow_data(workflow_data)
|
||||
formdata.store()
|
||||
self.action_on_error(self.action_on_bad_data, formdata,
|
||||
response, data=data, exc_info=sys.exc_info())
|
||||
else:
|
||||
workflow_data['%s_response' % self.varname] = d
|
||||
if isinstance(d.get('data'), dict) and d['data'].get('display_id'):
|
||||
formdata.id_display = d.get('data', {}).get('display_id')
|
||||
elif d.get('display_id'):
|
||||
formdata.id_display = d.get('display_id')
|
||||
else: # store result as attachment
|
||||
content_type = response.getheader('content-type') or ''
|
||||
if content_type:
|
||||
content_type = content_type.split(';')[0].strip().lower()
|
||||
workflow_data['%s_content_type' % self.varname] = content_type
|
||||
workflow_data['%s_length' % self.varname] = len(data)
|
||||
extension = mimetypes.guess_extension(content_type, strict=False) or ''
|
||||
filename = '%s%s' % (self.varname, extension)
|
||||
fp_content = StringIO(data)
|
||||
attachment = AttachmentEvolutionPart(filename, fp_content,
|
||||
content_type=content_type,
|
||||
varname=self.varname)
|
||||
formdata.evolution[-1].add_part(attachment)
|
||||
|
||||
def action_on_error(self, action, formdata, response=None, data=None, exc_info=None):
|
||||
if action in (':pass', ':stop') and (self.notify_on_errors or self.record_errors):
|
||||
if exc_info:
|
||||
|
|
Loading…
Reference in New Issue