def handle(self): payload = self.request.input.get('payload') if payload: payload = payload_from_request(self.cid, payload.decode('base64'), self.request.input.data_format, self.request.input.transport) id = self.request.input.get('id') name = self.request.input.get('name') channel = self.request.input.get('channel') data_format = self.request.input.get('data_format') transport = self.request.input.get('transport') expiration = self.request.input.get('expiration') or BROKER.DEFAULT_EXPIRATION if name and id: raise ZatoException('Cannot accept both id:[{}] and name:[{}]'.format(id, name)) if self.request.input.get('async'): if id: impl_name = self.server.service_store.id_to_impl_name[id] name = self.server.service_store.service_data(impl_name)['name'] response = self.invoke_async(name, payload, channel, data_format, transport, expiration) else: func, id_ = (self.invoke, name) if name else (self.invoke_by_id, id) response = func(id_, payload, channel, data_format, transport, serialize=True) if isinstance(response, basestring): if response: self.response.payload.response = response.encode('base64') if response else ''
def update_handle(self, set_response_func, service, raw_request, channel, data_format, transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs): payload = payload_from_request(cid, raw_request, data_format, transport) service.update(service, channel, server, broker_client, worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, kwargs.get('wsgi_environ', {}), job_type=kwargs.get('job_type')) service.pre_handle() service.call_hooks('before') service.handle() service.call_hooks('after') service.post_handle() service.call_hooks('finalize') return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
def handle(self, _channel=CHANNEL.WEBSPHERE_MQ, ts_format='YYYYMMDDHHmmssSS'): request = loads(self.request.raw_request) msg = request['msg'] service_name = request['service_name'] # Make MQ-level attributes easier to handle correlation_id = unhexlify( msg['correlation_id']) if msg['correlation_id'] else None expiration = datetime_from_ms( msg['expiration']) if msg['expiration'] else None timestamp = '{}{}'.format(msg['put_date'], msg['put_time']) timestamp = arrow_get(timestamp, ts_format).replace(tzinfo='UTC').datetime data = payload_from_request(self.cid, msg['text'], request['data_format'], None) self.invoke(service_name, data, _channel, wmq_ctx={ 'msg_id': unhexlify(msg['msg_id']), 'correlation_id': correlation_id, 'timestamp': timestamp, 'put_time': msg['put_time'], 'put_date': msg['put_date'], 'expiration': expiration, 'reply_to': msg['reply_to'], })
def handle(self): payload = payload_from_request(self.request.input.payload, self.request.input.data_format, self.request.input.transport) response = self.invoke_by_id(self.request.input.id, payload, self.request.input.data_format, self.request.input.transport, serialize=True) self.response.payload.response = response
def handle(self): payload = self.request.input.get('payload') if payload: payload = payload_from_request(self.cid, payload.decode('base64'), self.request.input.data_format, self.request.input.transport) id = self.request.input.get('id') name = self.request.input.get('name') pid = self.request.input.get('pid') channel = self.request.input.get('channel') data_format = self.request.input.get('data_format') transport = self.request.input.get('transport') expiration = self.request.input.get( 'expiration') or BROKER.DEFAULT_EXPIRATION if name and id: raise ZatoException( 'Cannot accept both id:`{}` and name:`{}`'.format(id, name)) if self.request.input.get('async'): if id: impl_name = self.server.service_store.id_to_impl_name[id] name = self.server.service_store.service_data( impl_name)['name'] # If PID is given on input it means we must invoke this particular server process by it ID if pid: response = self.server.invoke_by_pid(name, payload, pid) else: response = self.invoke_async(name, payload, channel, data_format, transport, expiration) else: # Same as above in async branch if pid: response = self.server.invoke(name, payload, pid=pid, data_format=data_format) else: func, id_ = (self.invoke, name) if name else (self.invoke_by_id, id) response = func(id_, payload, channel, data_format, transport, serialize=True) if isinstance(response, basestring): if response: self.response.payload.response = response.encode( 'base64') if response else ''
def update_handle(self, set_response_func, service, raw_request, channel, data_format, transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs): wsgi_environ = kwargs.get('wsgi_environ', {}) payload = wsgi_environ.get('zato.request.payload') # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates # to False so we'd be parsing the payload again superfluously. if not isinstance(payload, ObjectifiedElement) and not payload: payload = payload_from_request(cid, raw_request, data_format, transport) job_type = kwargs.get('job_type') channel_params = kwargs.get('channel_params', {}) merge_channel_params = kwargs.get('merge_channel_params', True) params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT) serialize = kwargs.get('serialize') as_bunch = kwargs.get('as_bunch') channel_item = kwargs.get('channel_item') service.update(service, channel, server, broker_client, worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, wsgi_environ, job_type=job_type, channel_params=channel_params, merge_channel_params=merge_channel_params, params_priority=params_priority, in_reply_to=wsgi_environ.get('zato.request_ctx.in_reply_to', None), environ=kwargs.get('environ')) # It's possible the call will be completely filtered out if service.accept(): # Assume everything goes fine e, exc_formatted = None, None try: service.pre_handle() service.call_hooks('before') service.validate_input() self._invoke(service, channel) service.validate_output() service.call_hooks('after') service.post_handle() service.call_hooks('finalize') except Exception, e: exc_formatted = format_exc(e) logger.warn(exc_formatted) finally:
def update_handle(self, set_response_func, service, raw_request, channel, data_format, transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs): wsgi_environ = kwargs.get('wsgi_environ', {}) payload = wsgi_environ.get('zato.request.payload') # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates # to False so we'd be parsing the payload again superfluously. if not isinstance(payload, ObjectifiedElement) and not payload: payload = payload_from_request(cid, raw_request, data_format, transport) job_type = kwargs.get('job_type') channel_params = kwargs.get('channel_params', {}) merge_channel_params = kwargs.get('merge_channel_params', True) params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT) serialize = kwargs.get('serialize') as_bunch = kwargs.get('as_bunch') channel_item = kwargs.get('channel_item') service.update(service, channel, server, broker_client, worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, wsgi_environ, job_type=job_type, channel_params=channel_params, merge_channel_params=merge_channel_params, params_priority=params_priority) # Depending on whether this is a pass-through service or not we invoke # the target services or the one we have in hand right now. Note that # hooks are always invoked for the one we have a handle to right now # even if it's a pass-through one. service.pre_handle() service.call_hooks('before') if service.passthrough_to: sio = getattr(service, 'SimpleIO', None) return self.invoke(service.passthrough_to, payload, channel, data_format, transport, serialize, as_bunch, sio=sio, from_passthrough=True, passthrough_request=self.request, set_response_func=set_response_func, channel_item=channel_item) else: service.validate_input() service.handle() service.validate_output() service.call_hooks('after') service.post_handle() service.call_hooks('finalize') return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
def handle(self): payload = self.request.input.get('payload') if payload: payload = b64decode(payload) payload = payload_from_request(self.cid, payload, self.request.input.data_format, self.request.input.transport) id = self.request.input.get('id') name = self.request.input.get('name') pid = self.request.input.get('pid') or 0 all_pids = self.request.input.get('all_pids') timeout = self.request.input.get('timeout') or None channel = self.request.input.get('channel') data_format = self.request.input.get('data_format') transport = self.request.input.get('transport') expiration = self.request.input.get('expiration') or BROKER.DEFAULT_EXPIRATION if name and id: raise ZatoException('Cannot accept both id:`{}` and name:`{}`'.format(id, name)) if self.request.input.get('async'): if id: impl_name = self.server.service_store.id_to_impl_name[id] name = self.server.service_store.service_data(impl_name)['name'] # If PID is given on input it means we must invoke this particular server process by it ID if pid and pid != self.server.pid: response = self.server.invoke_by_pid(name, payload, pid) else: response = self.invoke_async(name, payload, channel, data_format, transport, expiration) else: # Same as above in async branch, except in async there was no all_pids if all_pids: args = (name, payload, timeout) if timeout else (name, payload) response = dumps(self.server.invoke_all_pids(*args)) else: if pid and pid != self.server.pid: response = self.server.invoke(name, payload, pid=pid, data_format=data_format) else: func, id_ = (self.invoke, name) if name else (self.invoke_by_id, id) response = func(id_, payload, channel, data_format, transport, serialize=True) if isinstance(response, basestring): if response: response = response if isinstance(response, bytes) else response.encode('utf8') self.response.payload.response = b64encode(response) if response else ''
def update_handle(self, set_response_func, service, raw_request, channel, data_format, transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs): wsgi_environ = kwargs.get('wsgi_environ', {}) payload = wsgi_environ.get('zato.request.payload') # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates # to False so we'd be parsing the payload again superfluously. if not isinstance(payload, ObjectifiedElement) and not payload: payload = payload_from_request(cid, raw_request, data_format, transport) job_type = kwargs.get('job_type') channel_params = kwargs.get('channel_params', {}) merge_channel_params = kwargs.get('merge_channel_params', True) params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT) service.update(service, channel, server, broker_client, worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, wsgi_environ, job_type=job_type, channel_params=channel_params, merge_channel_params=merge_channel_params, params_priority=params_priority, in_reply_to=wsgi_environ.get('zato.request_ctx.in_reply_to', None), environ=kwargs.get('environ')) # It's possible the call will be completely filtered out if service.accept(): # Assume everything goes fine e, exc_formatted = None, None try: service.pre_handle() service.call_hooks('before') service.validate_input() self._invoke(service, channel) service.validate_output() service.call_hooks('after') service.post_handle() service.call_hooks('finalize') except Exception, e: exc_formatted = format_exc(e) logger.warn(exc_formatted) finally:
def init(self, cid, path_info, request, headers, transport, data_format): logger.debug('[{0}] request:[{1}] headers:[{2}]'.format(cid, request, headers)) if transport == 'soap': # HTTP headers are all uppercased at this point. soap_action = headers.get('HTTP_SOAPACTION') if not soap_action: raise BadRequest(cid, 'Client did not send the SOAPAction header') # SOAP clients may send an empty header, i.e. SOAPAction: "", # as opposed to not sending the header at all. soap_action = soap_action.lstrip('"').rstrip('"') if not soap_action: raise BadRequest(cid, 'Client sent an empty SOAPAction header') else: soap_action = '' _soap_actions = self.http_soap.getall(path_info) for _soap_action_info in _soap_actions: # TODO: Remove the call to .keys() when this pull request is merged in # https://github.com/dsc/bunch/pull/4 if soap_action in _soap_action_info.keys(): service_info = _soap_action_info[soap_action] break else: msg = '[{0}] Could not find the service config for URL:[{1}], SOAP action:[{2}]'.format( cid, path_info, soap_action) logger.warn(msg) raise NotFound(cid, msg) logger.debug('[{0}] impl_name:[{1}]'.format(cid, service_info.impl_name)) if(logger.isEnabledFor(TRACE1)): buff = StringIO() pprint(self.server.service_store.services, stream=buff) logger.log(TRACE1, '[{0}] service_store.services:[{1}]'.format(cid, buff.getvalue())) buff.close() service_data = self.server.service_store.service_data(service_info.impl_name) return payload_from_request(request, data_format, transport), service_info, service_data
def update_handle(self, set_response_func, service, raw_request, channel, data_format, transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs): payload = payload_from_request(cid, raw_request, data_format, transport) job_type = kwargs.get('job_type') channel_params = kwargs.get('channel_params', {}) merge_channel_params = kwargs.get('merge_channel_params', True) params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT) wsgi_environ = kwargs.get('wsgi_environ', {}) serialize = kwargs.get('serialize') as_bunch = kwargs.get('as_bunch') channel_item = kwargs.get('channel_item') service.update(service, channel, server, broker_client, worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, wsgi_environ, job_type=job_type, channel_params=channel_params, merge_channel_params=merge_channel_params, params_priority=params_priority) # Depending on whether this is a pass-through service or not we invoke # the target services or the one we have in hand right now. Note that # hooks are always invoked for the one we have a handle to right now # even if it's a pass-through one. service.pre_handle() service.call_hooks('before') if service.passthrough_to: sio = getattr(service, 'SimpleIO', None) return self.invoke(service.passthrough_to, payload, channel, data_format, transport, serialize, as_bunch, sio=sio, from_passthrough=True, passthrough_request=self.request, set_response_func=set_response_func, channel_item=channel_item) else: service.handle() service.call_hooks('after') service.post_handle() service.call_hooks('finalize') return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
def handle(self): payload = self.request.input.get('payload') if payload: payload = payload_from_request(self.cid, payload.decode('base64'), self.request.input.data_format, self.request.input.transport) id = self.request.input.get('id') name = self.request.input.get('name') channel = self.request.input.get('channel') data_format = self.request.input.get('data_format') transport = self.request.input.get('transport') expiration = self.request.input.get( 'expiration') or BROKER.DEFAULT_EXPIRATION if name and id: raise ZatoException( 'Cannot accept both id:[{}] and name:[{}]'.format(id, name)) if self.request.input.get('async'): if id: impl_name = self.server.service_store.id_to_impl_name[id] name = self.server.service_store.service_data( impl_name)['name'] response = self.invoke_async(name, payload, channel, data_format, transport, expiration) else: func, id_ = (self.invoke, name) if name else (self.invoke_by_id, id) response = func(id_, payload, channel, data_format, transport, serialize=True) if isinstance(response, basestring): if response: self.response.payload.response = response.encode( 'base64') if response else ''
def update_handle(self, set_response_func, service, raw_request, channel, data_format, transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs): payload = payload_from_request(cid, raw_request, data_format, transport) job_type = kwargs.get('job_type') channel_params = kwargs.get('channel_params', {}) merge_channel_params = kwargs.get('merge_channel_params', True) params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT) wsgi_environ = kwargs.get('wsgi_environ', {}) serialize = kwargs.get('serialize') as_bunch = kwargs.get('as_bunch') service.update(service, channel, server, broker_client, worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, wsgi_environ, job_type=job_type, channel_params=channel_params, merge_channel_params=merge_channel_params, params_priority=params_priority) # Depending on whether this is a pass-through service or not we invoke # the target services or the one we have in hand right now. Note that # hooks are always invoked for the one we have a handle to right now # even if it's a pass-through one. service.pre_handle() service.call_hooks('before') if service.passthrough_to: sio = getattr(service, 'SimpleIO', None) return self.invoke(service.passthrough_to, raw_request, channel, data_format, transport, serialize, as_bunch, sio=sio, from_passthrough=True, passthrough_request=self.request, set_response_func=set_response_func) else: service.handle() service.call_hooks('after') service.post_handle() service.call_hooks('finalize') return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
def update_handle(self, set_response_func, service, raw_request, channel, data_format, transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs): payload = payload_from_request(cid, raw_request, data_format, transport) service.update(service, channel, server, broker_client, worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, kwargs.get('wsgi_environ', {}), job_type=kwargs.get('job_type'), channel_params=kwargs.get('channel_params', {}), merge_channel_params=kwargs.get('merge_channel_params', True), params_priority=kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT)) service.pre_handle() service.call_hooks('before') service.handle() service.call_hooks('after') service.post_handle() service.call_hooks('finalize') return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
def test_handle_security_xpath_sec(self): test_data = [ [True, rand_string(), rand_string()], [False, rand_string(), rand_string()], [True, rand_string(), None], [False, rand_string(), None] ] username_expr = "//*[local-name()='mydoc']/@user" for is_valid, valid_username, password in test_data: password_expr = "//*[local-name()='mydoc']/@password" if password else None xml_username = valid_username if is_valid else rand_string() cid = rand_string() ud = url_data.URLData(None, []) sec_def = Bunch(username=valid_username, password=password, username_expr=username_expr, password_expr=password_expr) xml = """<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:foo="http://foo.example.com"> <soapenv:Header/> <soapenv:Body> <foo:mydoc user="******" password="******"/> </soapenv:Body> </soapenv:Envelope>""".format(xml_username, password) payload = payload_from_request(cid, xml, DATA_FORMAT.XML, URL_TYPE.SOAP) if is_valid: result = ud._handle_security_xpath_sec(cid, sec_def, None, None, {'zato.request.payload':payload}) self.assertEqual(result, True) else: try: ud._handle_security_xpath_sec(cid, sec_def, None, None, {'zato.request.payload':payload}) except Unauthorized: pass else: self.fail('Expected Unauthorized, `{}`, `{}`, `{}`, `{}`, `{}`'.format( is_valid, valid_username, xml_username, password, xml))
def handle(self): payload = self.request.input.get('payload') if payload: payload = b64decode(payload) payload = payload_from_request(self.cid, payload, self.request.input.data_format, self.request.input.transport) id = self.request.input.get('id') name = self.request.input.get('name') pid = self.request.input.get('pid') or 0 all_pids = self.request.input.get('all_pids') timeout = self.request.input.get('timeout') or None channel = self.request.input.get('channel') data_format = self.request.input.get('data_format') transport = self.request.input.get('transport') expiration = self.request.input.get( 'expiration') or BROKER.DEFAULT_EXPIRATION if name and id: raise ZatoException( 'Cannot accept both id:`{}` and name:`{}`'.format(id, name)) if self.request.input.get('async'): if id: impl_name = self.server.service_store.id_to_impl_name[id] name = self.server.service_store.service_data( impl_name)['name'] # If PID is given on input it means we must invoke this particular server process by it ID if pid and pid != self.server.pid: response = self.server.invoke_by_pid(name, payload, pid) else: response = self.invoke_async(name, payload, channel, data_format, transport, expiration) else: # This branch the same as above in async branch, except in async there was no all_pids # It is possible that we were given the all_pids flag on input but we know # ourselves that there is only one process, the current one, so we can just # invoke it directly instead of going through IPC. if all_pids and self.server.fs_server_config.main.gunicorn_workers > 1: use_all_pids = True else: use_all_pids = False if use_all_pids: args = (name, payload, timeout) if timeout else (name, payload) response = dumps(self.server.invoke_all_pids(*args)) else: if pid and pid != self.server.pid: response = self.server.invoke(name, payload, pid=pid, data_format=data_format) else: func, id_ = (self.invoke, name) if name else (self.invoke_by_id, id) response = func(id_, payload, channel, data_format, transport, serialize=True) if isinstance(response, basestring): if response: response = response if isinstance( response, bytes) else response.encode('utf8') self.response.payload.response = b64encode( response) if response else ''
def dispatch(self, cid, req_timestamp, wsgi_environ, worker_store): """ Base method for dispatching incoming HTTP/SOAP messages. If the security configuration is one of the technical account or HTTP basic auth, the security validation is being performed. Otherwise, that step is postponed until a concrete transport-specific handler is invoked. """ # Needed in later steps path_info = wsgi_environ['PATH_INFO'] soap_action = wsgi_environ.get('HTTP_SOAPACTION', '') # Fix up SOAP action - turns "my:soap:action" into my:soap:action, # that is, strips it out of surrounding quotes, if any. if soap_action: soap_action = self._handle_quotes_soap_action(soap_action) # Can we recognize this combination of URL path and SOAP action at all? # This gives us the URL info and security data - but note that here # we still haven't validated credentials, only matched the URL. # Credentials are checked in a call to self.url_data.check_security url_match, channel_item = self.url_data.match(path_info, soap_action) if channel_item: logger.debug('url_match:`%r`, channel_item:`%r`', url_match, sorted(channel_item.items())) # This is needed in parallel.py's on_wsgi_request wsgi_environ['zato.http.channel_item'] = channel_item payload = wsgi_environ['wsgi.input'].read() # OK, we can possibly handle it if url_match not in (None, False): # This is a synchronous call so that whatever happens next we are always # able to have at least initial audit log of requests. if channel_item['audit_enabled']: self.url_data.audit_set_request(cid, channel_item, payload, wsgi_environ) try: # Raise 404 if the channel is inactive if not channel_item['is_active']: logger.warn('url_data:`%s` is not active, raising NotFound', sorted(url_match.items())) raise NotFound(cid, 'Channel inactive') expected_method = channel_item['method'] if expected_method: actual_method = wsgi_environ['REQUEST_METHOD'] if expected_method != actual_method: logger.warn( 'Expected `%s` instead of `%s` for `%s`', expected_method, actual_method, channel_item['url_path']) raise MethodNotAllowed(cid, 'Method `{}` is not allowed here'.format(actual_method)) # Need to read security info here so we know if POST needs to be # parsed. If so, we do it here and reuse it in other places # so it doesn't have to be parsed two or more times. sec = self.url_data.url_sec[channel_item['match_target']] if sec.sec_def != ZATO_NONE and sec.sec_def.sec_type == SEC_DEF_TYPE.OAUTH: post_data = QueryDict(payload, encoding='utf-8') else: post_data = {} # This is handy if someone invoked URLData's OAuth API manually wsgi_environ['zato.oauth.post_data'] = post_data # Eagerly parse the request but only if we expect XPath-based credentials. The request will be re-used # in later steps, it won't be parsed twice or more. if sec.sec_def != ZATO_NONE and sec.sec_def.sec_type == SEC_DEF_TYPE.XPATH_SEC: wsgi_environ['zato.request.payload'] = payload_from_request( cid, payload, channel_item.data_format, channel_item.transport) # Will raise an exception on any security violation self.url_data.check_security( sec, cid, channel_item, path_info, payload, wsgi_environ, post_data, worker_store) # OK, no security exception at that point means we can finally # invoke the service. response = self.request_handler.handle(cid, url_match, channel_item, wsgi_environ, payload, worker_store, self.simple_io_config, post_data) # Got response from the service so we can construct response headers now self.add_response_headers(wsgi_environ, response) # Return the payload to the client return response.payload except Exception, e: _format_exc = format_exc(e) status = _status_internal_server_error if isinstance(e, ClientHTTPError): response = e.msg status_code = e.status # TODO: Refactor this series of if/else's into a lookup dict. if isinstance(e, Unauthorized): status = _status_unauthorized wsgi_environ['zato.http.response.headers']['WWW-Authenticate'] = e.challenge elif isinstance(e, BadRequest): status = _status_bad_request elif isinstance(e, NotFound): status = _status_not_found elif isinstance(e, MethodNotAllowed): status = _status_method_not_allowed elif isinstance(e, Forbidden): status = _status_forbidden elif isinstance(e, TooManyRequests): status = _status_too_many_requests else: status_code = INTERNAL_SERVER_ERROR response = _format_exc # TODO: This should be configurable. Some people may want such # things to be on DEBUG whereas for others ERROR will make most sense # in given circumstances. logger.error('Caught an exception, cid:`%s`, status_code:`%s`, _format_exc:`%s`', cid, status_code, _format_exc) try: error_wrapper = get_client_error_wrapper(channel_item['transport'], channel_item['data_format']) except KeyError: # It's OK. Apparently it's neither 'soap' nor json' if logger.isEnabledFor(TRACE1): msg = 'No client error wrapper for transport:`{}`, data_format:`{}`'.format( channel_item.get('transport'), channel_item.get('data_format')) logger.log(TRACE1, msg) else: response = error_wrapper(cid, response) wsgi_environ['zato.http.response.status'] = status return response
def update_handle(self, set_response_func, service, raw_request, channel, data_format, transport, server, broker_client, worker_store, cid, simple_io_config, _utcnow=datetime.utcnow, _call_hook_with_service=call_hook_with_service, _call_hook_no_service=call_hook_no_service, _CHANNEL_SCHEDULER=CHANNEL.SCHEDULER, _pattern_channels=(CHANNEL.FANOUT_CALL, CHANNEL.PARALLEL_EXEC_CALL), *args, **kwargs): wsgi_environ = kwargs.get('wsgi_environ', {}) payload = wsgi_environ.get('zato.request.payload') # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates # to False so we'd be parsing the payload again superfluously. if not isinstance(payload, ObjectifiedElement) and not payload: payload = payload_from_request(cid, raw_request, data_format, transport) job_type = kwargs.get('job_type') channel_params = kwargs.get('channel_params', {}) merge_channel_params = kwargs.get('merge_channel_params', True) params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT) service.update(service, channel, server, broker_client, worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, wsgi_environ, job_type=job_type, channel_params=channel_params, merge_channel_params=merge_channel_params, params_priority=params_priority, in_reply_to=wsgi_environ.get( 'zato.request_ctx.in_reply_to', None), environ=kwargs.get('environ'), wmq_ctx=kwargs.get('wmq_ctx')) # It's possible the call will be completely filtered out. The uncommonly looking not self.accept shortcuts # if ServiceStore replaces self.accept with None in the most common case of this method's not being # implemented by user services. if (not self.accept) or service.accept(): # Assume everything goes fine e, exc_formatted = None, None try: if service.server.component_enabled.stats: service.usage = service.kvdb.conn.incr('{}{}'.format( KVDB.SERVICE_USAGE, service.name)) service.invocation_time = _utcnow() # All hooks are optional so we check if they have not been replaced with None by ServiceStore. # Call before job hooks if any are defined and we are called from the scheduler if service._has_before_job_hooks and self.channel.type == _CHANNEL_SCHEDULER: for elem in service._before_job_hooks: if elem: _call_hook_with_service(elem, service) # Called before .handle - catches exceptions if service.before_handle: _call_hook_no_service(service.before_handle) # Called before .handle - does not catch exceptions if service.validate_input: service.validate_input() # This is the place where the service is invoked self._invoke(service, channel) # Called after .handle - does not catch exceptions if service.validate_output: service.validate_output() # Called after .handle - catches exceptions if service.after_handle: _call_hook_no_service(service.after_handle) # Call after job hooks if any are defined and we are called from the scheduler if service._has_after_job_hooks and self.channel.type == _CHANNEL_SCHEDULER: for elem in service._after_job_hooks: if elem: _call_hook_with_service(elem, service) # Internal method - always defined and called service.post_handle() # Optional, almost never overridden. if service.finalize_handle: _call_hook_no_service(service.finalize_handle) except Exception, e: exc_formatted = format_exc(e) logger.warn(exc_formatted) finally:
def dispatch(self, cid, req_timestamp, wsgi_environ, worker_store, _status_response=status_response, no_url_match=(None, False), _response_404=response_404, _has_debug=_has_debug, _http_soap_action='HTTP_SOAPACTION', _stringio=StringIO, _gzipfile=GzipFile): """ Base method for dispatching incoming HTTP/SOAP messages. If the security configuration is one of the technical account or HTTP basic auth, the security validation is being performed. Otherwise, that step is postponed until a concrete transport-specific handler is invoked. """ # Needed in later steps path_info = wsgi_environ['PATH_INFO'].decode('utf-8') if _http_soap_action in wsgi_environ: soap_action = self._handle_quotes_soap_action(wsgi_environ[_http_soap_action]) else: soap_action = '' # Can we recognize this combination of URL path and SOAP action at all? # This gives us the URL info and security data - but note that here # we still haven't validated credentials, only matched the URL. # Credentials are checked in a call to self.url_data.check_security url_match, channel_item = self.url_data.match(path_info, soap_action, bool(soap_action)) if _has_debug and channel_item: logger.debug('url_match:`%r`, channel_item:`%r`', url_match, sorted(channel_item.items())) # This is needed in parallel.py's on_wsgi_request wsgi_environ['zato.channel_item'] = channel_item payload = wsgi_environ['wsgi.input'].read() # OK, we can possibly handle it if url_match not in no_url_match: try: # Raise 404 if the channel is inactive if not channel_item['is_active']: logger.warn('url_data:`%s` is not active, raising NotFound', sorted(url_match.items())) raise NotFound(cid, 'Channel inactive') expected_method = channel_item['method'] if expected_method: actual_method = wsgi_environ['REQUEST_METHOD'] if expected_method != actual_method: logger.warn( 'Expected `%s` instead of `%s` for `%s`', expected_method, actual_method, channel_item['url_path']) raise MethodNotAllowed(cid, 'Method `{}` is not allowed here'.format(actual_method)) # Need to read security info here so we know if POST needs to be # parsed. If so, we do it here and reuse it in other places # so it doesn't have to be parsed two or more times. post_data = {} sec = self.url_data.url_sec[channel_item['match_target']] if sec.sec_def != ZATO_NONE or sec.sec_use_rbac is True: if sec.sec_def != ZATO_NONE: if sec.sec_def.sec_type == SEC_DEF_TYPE.OAUTH: post_data.update(QueryDict(payload, encoding='utf-8')) # Eagerly parse the request but only if we expect XPath-based credentials. The request will be re-used # in later steps, it won't be parsed twice or more. elif sec.sec_def.sec_type == SEC_DEF_TYPE.XPATH_SEC: wsgi_environ['zato.request.payload'] = payload_from_request( cid, payload, channel_item.data_format, channel_item.transport) # Will raise an exception on any security violation self.url_data.check_security( sec, cid, channel_item, path_info, payload, wsgi_environ, post_data, worker_store) # This is handy if someone invoked URLData's OAuth API manually wsgi_environ['zato.oauth.post_data'] = post_data # OK, no security exception at that point means we can finally invoke the service. response = self.request_handler.handle(cid, url_match, channel_item, wsgi_environ, payload, worker_store, self.simple_io_config, post_data, path_info, soap_action) wsgi_environ['zato.http.response.headers']['Content-Type'] = response.content_type wsgi_environ['zato.http.response.headers'].update(response.headers) wsgi_environ['zato.http.response.status'] = _status_response[response.status_code] if channel_item['content_encoding'] == 'gzip': s = _stringio() with _gzipfile(fileobj=s, mode='w') as f: f.write(response.payload) response.payload = s.getvalue() s.close() wsgi_environ['zato.http.response.headers']['Content-Encoding'] = 'gzip' # Finally return payload to the client return response.payload except Exception, e: _format_exc = format_exc(e) status = _status_internal_server_error if isinstance(e, ClientHTTPError): response = e.msg status_code = e.status # TODO: Refactor this series of if/else's into a lookup dict. if isinstance(e, Unauthorized): status = _status_unauthorized wsgi_environ['zato.http.response.headers']['WWW-Authenticate'] = e.challenge elif isinstance(e, BadRequest): status = _status_bad_request elif isinstance(e, NotFound): status = _status_not_found elif isinstance(e, MethodNotAllowed): status = _status_method_not_allowed elif isinstance(e, Forbidden): status = _status_forbidden elif isinstance(e, TooManyRequests): status = _status_too_many_requests else: status_code = INTERNAL_SERVER_ERROR response = _format_exc if self.return_tracebacks else self.default_error_message # TODO: This should be configurable. Some people may want such # things to be on DEBUG whereas for others ERROR will make most sense # in given circumstances. logger.error('Caught an exception, cid:`%s`, status_code:`%s`, _format_exc:`%s`', cid, status_code, _format_exc) try: error_wrapper = get_client_error_wrapper(channel_item['transport'], channel_item['data_format']) except KeyError: # It's OK. Apparently it's neither 'soap' nor json' if logger.isEnabledFor(TRACE1): msg = 'No client error wrapper for transport:`{}`, data_format:`{}`'.format( channel_item.get('transport'), channel_item.get('data_format')) logger.log(TRACE1, msg) else: response = error_wrapper(cid, response) wsgi_environ['zato.http.response.status'] = status return response
def dispatch(self, cid, req_timestamp, wsgi_environ, worker_store, _status_response=status_response, no_url_match=(None, False), _response_404=response_404, _has_debug=_has_debug, _http_soap_action='HTTP_SOAPACTION', _stringio=StringIO, _gzipfile=GzipFile, _accept_any_http=accept_any_http, _accept_any_internal=accept_any_internal, _rate_limit_type=RATE_LIMIT.OBJECT_TYPE.HTTP_SOAP, _stack_format=stack_format, _exc_sep='*' * 80): # Needed as one of the first steps http_method = wsgi_environ['REQUEST_METHOD'] http_method = http_method if isinstance( http_method, unicode) else http_method.decode('utf8') http_accept = wsgi_environ.get('HTTP_ACCEPT') or _accept_any_http http_accept = http_accept.replace('*', _accept_any_internal).replace( '/', 'HTTP_SEP') http_accept = http_accept if isinstance( http_accept, unicode) else http_accept.decode('utf8') # Needed in later steps path_info = wsgi_environ['PATH_INFO'] if PY3 else wsgi_environ[ 'PATH_INFO'].decode('utf8') # Immediately reject the request if it is not a support HTTP method, no matter what channel # it would have otherwise matched. if http_method not in self.http_methods_allowed: wsgi_environ[ 'zato.http.response.status'] = _status_method_not_allowed return client_json_error(cid, 'Unsupported HTTP method') if _http_soap_action in wsgi_environ: soap_action = self._handle_quotes_soap_action( wsgi_environ[_http_soap_action]) else: soap_action = '' # Can we recognize this combination of URL path and SOAP action at all? # This gives us the URL info and security data - but note that here # we still haven't validated credentials, only matched the URL. # Credentials are checked in a call to self.url_data.check_security url_match, channel_item = self.url_data.match(path_info, soap_action, http_method, http_accept, bool(soap_action)) if _has_debug and channel_item: logger.debug('url_match:`%r`, channel_item:`%r`', url_match, sorted(channel_item.items())) # This is needed in parallel.py's on_wsgi_request wsgi_environ['zato.channel_item'] = channel_item payload = wsgi_environ['wsgi.input'].read() # OK, we can possibly handle it if url_match not in no_url_match: try: # Raise 404 if the channel is inactive if not channel_item['is_active']: logger.warn( 'url_data:`%s` is not active, raising NotFound', sorted(url_match.items())) raise NotFound(cid, 'Channel inactive') # We need to read security info here so we know if POST needs to be # parsed. If so, we do it here and reuse it in other places # so it doesn't have to be parsed two or more times. post_data = {} match_target = channel_item['match_target'] sec = self.url_data.url_sec[match_target] if sec.sec_def != ZATO_NONE or sec.sec_use_rbac is True: if sec.sec_def != ZATO_NONE: if sec.sec_def.sec_type == SEC_DEF_TYPE.OAUTH: post_data.update( QueryDict(payload, encoding='utf-8')) # Eagerly parse the request but only if we expect XPath-based credentials. The request will be re-used # in later steps, it won't be parsed twice or more. elif sec.sec_def.sec_type == SEC_DEF_TYPE.XPATH_SEC: wsgi_environ[ 'zato.request.payload'] = payload_from_request( cid, payload, channel_item.data_format, channel_item.transport) # Will raise an exception on any security violation self.url_data.check_security(sec, cid, channel_item, path_info, payload, wsgi_environ, post_data, worker_store) # Check rate limiting now - this could not have been done earlier because we wanted # for security checks to be made first. Otherwise, someone would be able to invoke # our endpoint without credentials as many times as it is needed to exhaust the rate limit # denying in this manner access to genuine users. if channel_item.get('is_rate_limit_active'): self.server.rate_limiting.check_limit( cid, _rate_limit_type, channel_item['name'], wsgi_environ['zato.http.remote_addr']) # This is handy if someone invoked URLData's OAuth API manually wsgi_environ['zato.oauth.post_data'] = post_data # OK, no security exception at that point means we can finally invoke the service. response = self.request_handler.handle( cid, url_match, channel_item, wsgi_environ, payload, worker_store, self.simple_io_config, post_data, path_info, soap_action) wsgi_environ['zato.http.response.headers'][ 'Content-Type'] = response.content_type wsgi_environ['zato.http.response.headers'].update( response.headers) wsgi_environ['zato.http.response.status'] = _status_response[ response.status_code] if channel_item['content_encoding'] == 'gzip': s = _stringio() with _gzipfile(fileobj=s, mode='w') as f: f.write(response.payload) response.payload = s.getvalue() s.close() wsgi_environ['zato.http.response.headers'][ 'Content-Encoding'] = 'gzip' # Finally, return payload to the client return response.payload except Exception as e: _format_exc = format_exc() status = _status_internal_server_error if isinstance(e, ClientHTTPError): response = e.msg status_code = e.status # TODO: Refactor this series of if/else's into a lookup dict. if isinstance(e, Unauthorized): status = _status_unauthorized wsgi_environ['zato.http.response.headers'][ 'WWW-Authenticate'] = e.challenge elif isinstance(e, BadRequest): status = _status_bad_request elif isinstance(e, NotFound): status = _status_not_found elif isinstance(e, MethodNotAllowed): status = _status_method_not_allowed elif isinstance(e, Forbidden): status = _status_forbidden elif isinstance(e, TooManyRequests): status = _status_too_many_requests else: # JSON Schema validation if isinstance(e, JSONSchemaValidationException): status_code = _status_bad_request needs_prefix = False if e.needs_err_details else True response = JSONSchemaDictError( cid, e.needs_err_details, e.error_msg, needs_prefix=needs_prefix).serialize() # Rate limiting and whitelisting elif isinstance(e, RateLimitingException): response, status_code, status = self._on_rate_limiting_exception( cid, e, channel_item) else: status_code = INTERNAL_SERVER_ERROR response = _format_exc if self.return_tracebacks else self.default_error_message _exc = _stack_format( e, style='color', show_vals='like_source', truncate_vals=5000, add_summary=True, source_lines=20) if _stack_format else _format_exc # TODO: This should be configurable. Some people may want such # things to be on DEBUG whereas for others ERROR will make most sense # in given circumstances. logger.error( 'Caught an exception, cid:`%s`, status_code:`%s`, e:\n%s\n`%s`', cid, status_code, _exc_sep, _exc) try: error_wrapper = get_client_error_wrapper( channel_item['transport'], channel_item['data_format']) except KeyError: # It's OK. Apparently it's neither 'soap' nor json' if logger.isEnabledFor(TRACE1): msg = 'No client error wrapper for transport:`{}`, data_format:`{}`'.format( channel_item.get('transport'), channel_item.get('data_format')) logger.log(TRACE1, msg) else: response = error_wrapper(cid, response) wsgi_environ['zato.http.response.status'] = status return response # This is 404, no such URL path and SOAP action is not known either. else: response = _response_404.format(path_info, wsgi_environ.get('REQUEST_METHOD'), wsgi_environ.get('HTTP_ACCEPT'), cid) wsgi_environ['zato.http.response.status'] = _status_not_found logger.error(response) return response
def update_handle(self, set_response_func, service, raw_request, channel, data_format, transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs): wsgi_environ = kwargs.get('wsgi_environ', {}) payload = wsgi_environ.get('zato.request.payload') # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates # to False so we'd be parsing the payload again superfluously. if not isinstance(payload, ObjectifiedElement) and not payload: payload = payload_from_request(cid, raw_request, data_format, transport) job_type = kwargs.get('job_type') channel_params = kwargs.get('channel_params', {}) merge_channel_params = kwargs.get('merge_channel_params', True) params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT) serialize = kwargs.get('serialize') as_bunch = kwargs.get('as_bunch') channel_item = kwargs.get('channel_item') service.update(service, channel, server, broker_client, worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, wsgi_environ, job_type=job_type, channel_params=channel_params, merge_channel_params=merge_channel_params, params_priority=params_priority) # Depending on whether this is a pass-through service or not we invoke # the target services or the one we have in hand right now. Note that # hooks are always invoked for the one we have a handle to right now # even if it's a pass-through one. service.pre_handle() service.call_hooks('before') if service.passthrough_to: sio = getattr(service, 'SimpleIO', None) return self.invoke(service.passthrough_to, payload, channel, data_format, transport, serialize, as_bunch, sio=sio, from_passthrough=True, passthrough_request=self.request, set_response_func=set_response_func, channel_item=channel_item) else: service.validate_input() # # If channel is HTTP and there are any per-HTTP verb methods, it means we want for the service to be a REST target. # Let's say it is POST. If we have handle_POST, it is invoked. If there is no handle_POST, # '405 Method Not Allowed is returned'. # # However, if we have 'handle' only, it means this is always invoked and no default 405 is returned. # # In short, implement handle_* if you want REST behaviour. Otherwise, keep everything in handle. # # Ok, this is HTTP if channel in (CHANNEL.HTTP_SOAP, CHANNEL.INVOKE): # We have at least one per-HTTP verb handler if service.http_method_handlers: # But do we have any handler matching current request's verb? if service.request.http.method in service.http_method_handlers: # Yes, call the handler service.http_method_handlers[ service.request.http.method](service) # No, return 405 else: service.response.status_code = METHOD_NOT_ALLOWED # We have no customer handlers so we always call 'handle' else: service.handle() # It's not HTTP so we simply call 'handle' else: service.handle() service.validate_output() service.call_hooks('after') service.post_handle() service.call_hooks('finalize') return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
def update_handle(self, set_response_func, service, raw_request, channel, data_format, transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs): wsgi_environ = kwargs.get('wsgi_environ', {}) payload = wsgi_environ.get('zato.request.payload') # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates # to False so we'd be parsing the payload again superfluously. if not isinstance(payload, ObjectifiedElement) and not payload: payload = payload_from_request(cid, raw_request, data_format, transport) job_type = kwargs.get('job_type') channel_params = kwargs.get('channel_params', {}) merge_channel_params = kwargs.get('merge_channel_params', True) params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT) serialize = kwargs.get('serialize') as_bunch = kwargs.get('as_bunch') channel_item = kwargs.get('channel_item') service.update(service, channel, server, broker_client, worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, wsgi_environ, job_type=job_type, channel_params=channel_params, merge_channel_params=merge_channel_params, params_priority=params_priority) # Depending on whether this is a pass-through service or not we invoke # the target services or the one we have in hand right now. Note that # hooks are always invoked for the one we have a handle to right now # even if it's a pass-through one. service.pre_handle() service.call_hooks('before') if service.passthrough_to: sio = getattr(service, 'SimpleIO', None) return self.invoke(service.passthrough_to, payload, channel, data_format, transport, serialize, as_bunch, sio=sio, from_passthrough=True, passthrough_request=self.request, set_response_func=set_response_func, channel_item=channel_item) else: service.validate_input() # # If channel is HTTP and there are any per-HTTP verb methods, it means we want for the service to be a REST target. # Let's say it is POST. If we have handle_POST, it is invoked. If there is no handle_POST, # '405 Method Not Allowed is returned'. # # However, if we have 'handle' only, it means this is always invoked and no default 405 is returned. # # In short, implement handle_* if you want REST behaviour. Otherwise, keep everything in handle. # # Ok, this is HTTP if channel == CHANNEL.HTTP_SOAP: # We have at least one per-HTTP verb handler if service.http_method_handlers: # But do we have any handler matching current request's verb? if service.request.http.method in service.http_method_handlers: # Yes, call the handler service.http_method_handlers[service.request.http.method](service) # No, return 405 else: service.response.status_code = METHOD_NOT_ALLOWED # We have no customer handlers so we always call 'handle' else: service.handle() # It's not HTTP so we simply call 'handle' else: service.handle() service.validate_output() service.call_hooks('after') service.post_handle() service.call_hooks('finalize') return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
def dispatch(self, cid, req_timestamp, wsgi_environ, worker_store): """ Base method for dispatching incoming HTTP/SOAP messages. If the security configuration is one of the technical account or HTTP basic auth, the security validation is being performed. Otherwise, that step is postponed until a concrete transport-specific handler is invoked. """ # Needed in later steps path_info = wsgi_environ['PATH_INFO'] soap_action = wsgi_environ.get('HTTP_SOAPACTION', '') # Fix up SOAP action - turns "my:soap:action" into my:soap:action, # that is, strips it out of surrounding quotes, if any. if soap_action: soap_action = self._handle_quotes_soap_action(soap_action) # Can we recognize this combination of URL path and SOAP action at all? # This gives us the URL info and security data - but note that here # we still haven't validated credentials, only matched the URL. # Credentials are checked in a call to self.url_data.check_security url_match, channel_item = self.url_data.match(path_info, soap_action) if channel_item: logger.debug('url_match:`%r`, channel_item:`%r`', url_match, sorted(channel_item.items())) # This is needed in parallel.py's on_wsgi_request wsgi_environ['zato.http.channel_item'] = channel_item payload = wsgi_environ['wsgi.input'].read() # OK, we can possibly handle it if url_match not in (None, False): # This is a synchronous call so that whatever happens next we are always # able to have at least initial audit log of requests. if channel_item['audit_enabled']: self.url_data.audit_set_request(cid, channel_item, payload, wsgi_environ) try: # Raise 404 if the channel is inactive if not channel_item['is_active']: logger.warn( 'url_data:`%s` is not active, raising NotFound', sorted(url_match.items())) raise NotFound(cid, 'Channel inactive') expected_method = channel_item['method'] if expected_method: actual_method = wsgi_environ['REQUEST_METHOD'] if expected_method != actual_method: logger.warn('Expected `%s` instead of `%s` for `%s`', expected_method, actual_method, channel_item['url_path']) raise MethodNotAllowed( cid, 'Method `{}` is not allowed here'.format( actual_method)) # Need to read security info here so we know if POST needs to be # parsed. If so, we do it here and reuse it in other places # so it doesn't have to be parsed two or more times. sec = self.url_data.url_sec[channel_item['match_target']] if sec.sec_def != ZATO_NONE and sec.sec_def.sec_type == SEC_DEF_TYPE.OAUTH: post_data = QueryDict(payload, encoding='utf-8') else: post_data = {} # This is handy if someone invoked URLData's OAuth API manually wsgi_environ['zato.oauth.post_data'] = post_data # Eagerly parse the request but only if we expect XPath-based credentials. The request will be re-used # in later steps, it won't be parsed twice or more. if sec.sec_def != ZATO_NONE and sec.sec_def.sec_type == SEC_DEF_TYPE.XPATH_SEC: wsgi_environ[ 'zato.request.payload'] = payload_from_request( cid, payload, channel_item.data_format, channel_item.transport) # Will raise an exception on any security violation self.url_data.check_security(sec, cid, channel_item, path_info, payload, wsgi_environ, post_data, worker_store) # OK, no security exception at that point means we can finally # invoke the service. response = self.request_handler.handle( cid, url_match, channel_item, wsgi_environ, payload, worker_store, self.simple_io_config, post_data) # Got response from the service so we can construct response headers now self.add_response_headers(wsgi_environ, response) # Return the payload to the client return response.payload except Exception, e: _format_exc = format_exc(e) status = _status_internal_server_error if isinstance(e, ClientHTTPError): response = e.msg status_code = e.status # TODO: Refactor this series of if/else's into a lookup dict. if isinstance(e, Unauthorized): status = _status_unauthorized wsgi_environ['zato.http.response.headers'][ 'WWW-Authenticate'] = e.challenge elif isinstance(e, BadRequest): status = _status_bad_request elif isinstance(e, NotFound): status = _status_not_found elif isinstance(e, MethodNotAllowed): status = _status_method_not_allowed elif isinstance(e, Forbidden): status = _status_forbidden elif isinstance(e, TooManyRequests): status = _status_too_many_requests else: status_code = INTERNAL_SERVER_ERROR response = _format_exc # TODO: This should be configurable. Some people may want such # things to be on DEBUG whereas for others ERROR will make most sense # in given circumstances. logger.error( 'Caught an exception, cid:`%s`, status_code:`%s`, _format_exc:`%s`', cid, status_code, _format_exc) try: error_wrapper = get_client_error_wrapper( channel_item['transport'], channel_item['data_format']) except KeyError: # It's OK. Apparently it's neither 'soap' nor json' if logger.isEnabledFor(TRACE1): msg = 'No client error wrapper for transport:`{}`, data_format:`{}`'.format( channel_item.get('transport'), channel_item.get('data_format')) logger.log(TRACE1, msg) else: response = error_wrapper(cid, response) wsgi_environ['zato.http.response.status'] = status return response
def update_handle(self, set_response_func, # type: Callable service, # type: Service raw_request, # type: object channel, # type: ChannelInfo data_format, # type: unicode transport, # type: unicode server, # type: ParallelServer broker_client, # type: BrokerClient worker_store, # type: WorkerStore cid, # type: unicode simple_io_config, # type: dict _utcnow=datetime.utcnow, _call_hook_with_service=call_hook_with_service, _call_hook_no_service=call_hook_no_service, _CHANNEL_SCHEDULER=CHANNEL.SCHEDULER, _CHANNEL_SERVICE=CHANNEL.SERVICE, _pattern_channels=(CHANNEL.FANOUT_CALL, CHANNEL.PARALLEL_EXEC_CALL), *args, **kwargs): wsgi_environ = kwargs.get('wsgi_environ', {}) payload = wsgi_environ.get('zato.request.payload') # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates # to False so we'd be parsing the payload again superfluously. if not isinstance(payload, ObjectifiedElement) and not payload: payload = payload_from_request(cid, raw_request, data_format, transport) job_type = kwargs.get('job_type') channel_params = kwargs.get('channel_params', {}) merge_channel_params = kwargs.get('merge_channel_params', True) params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT) service.update(service, channel, server, broker_client, worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, wsgi_environ, job_type=job_type, channel_params=channel_params, merge_channel_params=merge_channel_params, params_priority=params_priority, in_reply_to=wsgi_environ.get('zato.request_ctx.in_reply_to', None), environ=kwargs.get('environ'), wmq_ctx=kwargs.get('wmq_ctx')) # It's possible the call will be completely filtered out. The uncommonly looking not self.accept shortcuts # if ServiceStore replaces self.accept with None in the most common case of this method's not being # implemented by user services. if (not self.accept) or service.accept(): # Assumes it goes fine by default e, exc_formatted = None, None try: # Check rate limiting first if self._has_rate_limiting: self.server.rate_limiting.check_limit(self.cid, _CHANNEL_SERVICE, self.name, self.wsgi_environ['zato.http.remote_addr']) if service.server.component_enabled.stats: service.usage = service.kvdb.conn.incr('{}{}'.format(KVDB.SERVICE_USAGE, service.name)) service.invocation_time = _utcnow() # Check if there is a JSON Schema validator attached to the service and if so, # validate input before proceeding any further. if service._json_schema_validator and service._json_schema_validator.is_initialized: validation_result = service._json_schema_validator.validate(cid, self.request.payload) if not validation_result: error = validation_result.get_error() error_msg = error.get_error_message() error_msg_details = error.get_error_message(True) raise JSONSchemaValidationException(cid, CHANNEL.SERVICE, service.name, error.needs_err_details, error_msg, error_msg_details) # All hooks are optional so we check if they have not been replaced with None by ServiceStore. # Call before job hooks if any are defined and we are called from the scheduler if service._has_before_job_hooks and self.channel.type == _CHANNEL_SCHEDULER: for elem in service._before_job_hooks: if elem: _call_hook_with_service(elem, service) # Called before .handle - catches exceptions if service.before_handle: _call_hook_no_service(service.before_handle) # Called before .handle - does not catch exceptions if service.validate_input: service.validate_input() # This is the place where the service is invoked self._invoke(service, channel) # Called after .handle - does not catch exceptions if service.validate_output: service.validate_output() # Called after .handle - catches exceptions if service.after_handle: _call_hook_no_service(service.after_handle) # Call after job hooks if any are defined and we are called from the scheduler if service._has_after_job_hooks and self.channel.type == _CHANNEL_SCHEDULER: for elem in service._after_job_hooks: if elem: _call_hook_with_service(elem, service) # Internal method - always defined and called service.post_handle() # Optional, almost never overridden. if service.finalize_handle: _call_hook_no_service(service.finalize_handle) except Exception as ex: e = ex exc_formatted = format_exc() logger.warn(exc_formatted) finally: try: response = set_response_func(service, data_format=data_format, transport=transport, **kwargs) # If this was fan-out/fan-in we need to always notify our callbacks no matter the result if channel in _pattern_channels: func = self.patterns.fanout.on_call_finished if channel == CHANNEL.FANOUT_CALL else \ self.patterns.parallel.on_call_finished spawn(func, self, service.response.payload, exc_formatted) except Exception as resp_e: # If we already have an exception around, log the new one but don't overwrite the old one with it. logger.warn('Exception in service `%s`, e:`%s`', service.name, format_exc()) if e: if isinstance(e, Reportable): raise e else: raise Exception(exc_formatted) raise resp_e else: if e: raise e if isinstance(e, Exception) else Exception(e) # We don't accept it but some response needs to be returned anyway. else: response = service.response response.payload = '' response.status_code = BAD_REQUEST return response