def handle(self, cid, url_match, channel_item, wsgi_environ, raw_request, worker_store, simple_io_config, post_data, path_info, soap_action, channel_type=CHANNEL.HTTP_SOAP, _response_404=response_404): """ Create a new instance of a service and invoke it. """ service, is_active = self.server.service_store.new_instance(channel_item.service_impl_name) if not is_active: logger.warn('Could not invoke an inactive service:`%s`, cid:`%s`', service.get_name(), cid) raise NotFound(cid, _response_404.format(cid, path_info, soap_action)) if channel_item.merge_url_params_req: channel_params = self.create_channel_params(url_match, channel_item, wsgi_environ, raw_request, post_data) else: channel_params = None # If caching is configured for this channel, we need to first check if there is no response already if channel_item['cache_type']: cache_key, response = self.get_response_from_cache(service, raw_request, channel_item, channel_params, wsgi_environ) if response: return response # No cache for this channel or no cached response, invoke the service then. response = service.update_handle(self._set_response_data, service, raw_request, channel_type, channel_item.data_format, channel_item.transport, self.server, worker_store.broker_client, worker_store, cid, simple_io_config, wsgi_environ=wsgi_environ, url_match=url_match, channel_item=channel_item, channel_params=channel_params, merge_channel_params=channel_item.merge_url_params_req, params_priority=channel_item.params_pri) # Cache the response if needed (cache_key was already created on return from get_response_from_cache) if channel_item['cache_type']: self.set_response_in_cache(channel_item, cache_key, response) # Having used the cache or not, we can return the response now return response
def validate_input(self): if not asbool(self.server.fs_server_config.apispec.pub_enabled): # Note that we are using the same format that regular 404 does raise NotFound( self.cid, '[{}] Unknown URL:[{}] or SOAP action:[]'.format( self.cid, self.wsgi_environ['zato.channel_item']['url_path']))
def init(self, cid, path_info, request, headers, transport, data_format): 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() self.server.service_store.service_data( service_info.impl_name ) # TODO - check if this call is needed at all? return service_info
def handle(self, cid, url_match, channel_item, wsgi_environ, raw_request, worker_store, simple_io_config, post_data, path_info, soap_action, channel_type=CHANNEL.HTTP_SOAP, _response_404=response_404): """ Create a new instance of a service and invoke it. """ service, is_active = self.server.service_store.new_instance( channel_item.service_impl_name) if not is_active: logger.warn('Could not invoke an inactive service:`%s`, cid:`%s`', service.get_name(), cid) raise NotFound(cid, _response_404.format(cid, path_info, soap_action)) if channel_item.merge_url_params_req: channel_params = self.create_channel_params( url_match, channel_item, wsgi_environ, raw_request, post_data) else: channel_params = None response = service.update_handle( self._set_response_data, service, raw_request, channel_type, channel_item.data_format, channel_item.transport, self.server, worker_store.broker_client, worker_store, cid, simple_io_config, wsgi_environ=wsgi_environ, url_match=url_match, channel_item=channel_item, channel_params=channel_params, merge_channel_params=channel_item.merge_url_params_req, params_priority=channel_item.params_pri) 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): """ 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 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. """ path_info = wsgi_environ['PATH_INFO'] soap_action = wsgi_environ.get('HTTP_SOAPACTION', '') url_data = self.security.url_sec_get(path_info, soap_action) payload = wsgi_environ['wsgi.input'].read() if logger.isEnabledFor(logging.DEBUG): msg = 'cid:[{}], payload:[{}], wsgi_environ:[{}]'.format( cid, payload, wsgi_environ) logger.debug(msg) if url_data: transport = url_data['transport'] data_format = url_data['data_format'] try: if not url_data.is_active: msg = 'url_data:[{}] is not active, raising NotFound'.format( sorted(url_data.items())) logger.warn(msg) raise NotFound(cid, 'Inactive channel') if url_data.sec_def != ZATO_NONE: if url_data.sec_def.sec_type in ( security_def_type.tech_account, security_def_type.basic_auth, security_def_type.wss): self.security.handle(cid, url_data, path_info, payload, wsgi_environ) else: log_msg = '[{0}] sec_def.sec_type:[{1}] needs no auth'.format( cid, url_data.sec_def.sec_type) logger.debug(log_msg) else: log_msg = '[{0}] No security for URL [{1}]'.format( cid, path_info) logger.debug(log_msg) handler = getattr(self, '{0}_handler'.format(transport)) service_info, response = handler.handle( cid, wsgi_environ, payload, transport, worker_store, self.simple_io_config, data_format, path_info) 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'] = b'{} {}'.format( response.status_code, responses[response.status_code]) 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 if isinstance(e, Unauthorized): status = _status_unauthorized wsgi_environ['zato.http.response.headers'][ 'WWW-Authenticate'] = e.challenge elif isinstance(e, NotFound): status = _status_not_found 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. if logger.isEnabledFor(logging.ERROR): msg = 'Caught an exception, cid:[{0}], status_code:[{1}], _format_exc:[{2}]'.format( cid, status_code, _format_exc) logger.log(logging.ERROR, msg) try: error_wrapper = get_client_error_wrapper( transport, 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( transport, 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): """ 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 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? url_match, channel_item = self.url_data.match(path_info, soap_action) # OK, we can possibly handle it if url_match: # 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') # Read payload only now, right before it's needed the first time, # possibly, by security checks. payload = wsgi_environ['wsgi.input'].read() try: # Will raise an exception on any security violation self.url_data.check_security(cid, channel_item, path_info, payload, wsgi_environ) # 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) # 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 if isinstance(e, Unauthorized): status = _status_unauthorized wsgi_environ['zato.http.response.headers'][ 'WWW-Authenticate'] = e.challenge elif isinstance(e, NotFound): status = _status_not_found 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.transport, channel_item.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): """ 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: # 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) # 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') try: # 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 == security_def_type.oauth: post_data = QueryDict(payload, encoding='utf-8') else: post_data = None # Will raise an exception on any security violation self.url_data.check_security(sec, cid, channel_item, path_info, payload, wsgi_environ, 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) # 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 if isinstance(e, Unauthorized): status = _status_unauthorized wsgi_environ['zato.http.response.headers'][ 'WWW-Authenticate'] = e.challenge elif isinstance(e, NotFound): status = _status_not_found 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['transport'], channel_item['data_format']) logger.log(TRACE1, msg) else: response = error_wrapper(cid, response) wsgi_environ['zato.http.response.status'] = status return response