def handle(self, _channel=CHANNEL.IBM_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 # Extract MQMD mqmd = msg['mqmd'] mqmd = b64decode(mqmd) mqmd = pickle_loads(mqmd) # Find the message's CCSID request_ccsid = mqmd.CodedCharSetId # Try to find an encoding matching the CCSID, # if not found, use the default one. try: encoding = CCSIDConfig.encoding_map[request_ccsid] except KeyError: encoding = CCSIDConfig.default_encoding # Encode the input Unicode data into bytes msg['text'] = msg['text'].encode(encoding) # Extract the business payload data = payload_from_request(self.server.json_parser, self.cid, msg['text'], request['data_format'], None) # Invoke the target service 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'], 'data': data, 'mqmd': mqmd })
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) msg['mqmd'] = b64decode(msg['mqmd']) 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'], 'data': data, 'mqmd': pickle_loads(msg['mqmd']) })
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('is_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 is_async branch, except in is_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).decode( 'utf8') if response else ''
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_http=RATE_LIMIT.OBJECT_TYPE.HTTP_SOAP, _rate_limit_type_sso_user=RATE_LIMIT.OBJECT_TYPE.SSO_USER, _stack_format=stack_format, _exc_sep='*' * 80, _jwt=_jwt, _sso_ext_auth=_sso_ext_auth): # 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 # Read the raw data payload = wsgi_environ['wsgi.input'].read() # Store for later use prior to any kind of parsing wsgi_environ['zato.http.raw_request'] = payload # 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 auth_result = 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_http, channel_item['name'], wsgi_environ['zato.http.remote_addr']) # Security definition-based checks went fine but it is still possible # that this sec_def is linked to an SSO user whose rate limits we need to check. # Check SSO-related limits only if SSO is enabled if self._sso_api_user: # Not all sec_def types may have associated SSO users if sec.sec_def != ZATO_NONE: if sec.sec_def.sec_type in _sso_ext_auth: # JWT comes with external sessions whereas Basic Auth does not if sec.sec_def.sec_type == _jwt: ext_session_id = auth_result.raw_token else: ext_session_id = None # Try to log in the user to SSO by that account's external credentials. self.server.sso_tool.on_external_auth( sec.sec_def.sec_type, sec.sec_def.id, sec.sec_def.username, cid, wsgi_environ, ext_session_id) else: raise Exception('Unexpected sec_type `{}`'.format( sec.sec_def.sec_type)) # 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`, `%s`', cid, status_code, _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