Commit bad15ffe authored by 张宇's avatar 张宇

patch process_single_request

parent a46dbd94
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import json
import logging import logging
import os import os
import traceback
import uuid import uuid
from django.conf import settings from django.conf import settings
from gm_logging.internal.instance import request_logging_guard_maker
from gm_rpcd.internals.common import now_str
from gm_rpcd.internals.configuration.model import Config, environ, from_property from gm_rpcd.internals.configuration.model import Config, environ, \
from_property
from gm_rpcd.internals.configuration.model_base import literal, xml_text, xml_text_list, \ from gm_rpcd.internals.configuration.model_base import literal, xml_text, xml_text_list, \
DefaultConfigPropertyWrapper, EnvironmentConfigProperty DefaultConfigPropertyWrapper, EnvironmentConfigProperty
from gm_rpcd.internals.context import Context
from gm_rpcd.internals.dispatcher import json_encode_with_length
from gm_rpcd.internals.dynamic_scopes import dynamic_scope, Keys
from gm_rpcd.internals.protocol.request import Request, \
dump_request_to_json_value
from gm_rpcd.internals.protocol.response import FaultResponse, SuccessResponse, \
response_to_v1_json_value, SystemErrorResponse
from gm_rpcd.internals.protocol.response_extras import \
make_method_not_found_response
from gm_rpcd.internals.utils import strict_check_json_value
from gm_tracer.context import current_tracer
from rpc_framework.exceptions import RPCViewBaseException
DISPATCHER = None DISPATCHER = None
...@@ -77,6 +95,112 @@ class DjangoRpcdConfig(Config): ...@@ -77,6 +95,112 @@ class DjangoRpcdConfig(Config):
return os.path.join(self.log_dir, 'gm_rpcd_request_error.log') return os.path.join(self.log_dir, 'gm_rpcd_request_error.log')
def process_single_request(self, request):
assert isinstance(request, Request)
tracer = current_tracer()
method_function = self._method_table.get_method_function(request.method)
if not method_function:
return make_method_not_found_response(request)
request_json_value = dump_request_to_json_value(request)
request_json_str = json.dumps(request_json_value, ensure_ascii=False)
now = now_str()
request_info = self._request_info_extractor(request)
log_id = request_info.log_id
span_id = getattr(request_info, 'span_id', '')
parent_span_id = getattr(request_info, 'parent_span_id', '')
gm_request_id = getattr(request_info, 'gm_request_id', '')
log_locating_info = 'log_id={} span_id={} parent_span_id={}'.format(
log_id, span_id, parent_span_id
)
with request_logging_guard_maker(request_info) as guard:
context = Context(
request=request,
request_info=request_info,
logger=guard.logger,
helios_base_invoker=self._helios_base_invoker,
)
with dynamic_scope.set(Keys.CONTEXT, context), dynamic_scope.set(Keys.REQUEST_INFO, request_info):
try:
result = method_function(**request.params)
strict_check_json_value(result)
response = SuccessResponse(
request_id=request.request_id,
result=result,
)
except RPCViewBaseException as e:
guard.set_errno(e.code)
response = FaultResponse(
request_id=request.request_id,
error={
# 最终相应为: {'error': '..','message': '..', 'data': ''}
'code': e.code,
'message': e.message,
}
)
except Exception as e:
guard.set_errno_unexpected()
if self._raven_client:
self._raven_client.captureException(
tags={
'log_id': log_id,
'gm_request_id': gm_request_id,
'span_id': span_id,
'parent_span_id': parent_span_id,
},
extra={
'log_id': log_id,
'span_id': span_id,
'parent_span_id': parent_span_id,
'request': request_json_value,
'gm_request_id': gm_request_id,
}
)
format_exc = traceback.format_exc()
log_message = json.dumps({
'timestamp': now,
'gm_request_id': gm_request_id,
'log_locating_info': log_locating_info,
'format_exc': format_exc,
'request_json_str': request_json_str,
}, ensure_ascii=False)
self._request_error_logger.error(log_message)
from gm_rpcd.internals.configuration.model import config
if config.is_develop_mode:
debug_message = '{}\nrequest = {}'.format(
format_exc,
request_json_str,
)
else:
debug_message = None
response = SystemErrorResponse(
message='Unexpected exception on server side. {}'.format(log_locating_info),
debug_message=debug_message,
)
if tracer:
# tracer.add_annotation({'error': time.time()})
tracer.add_binary_annotation({
'error': 'true',
'exc.info': format_exc,
'request_json_str': request_json_str,
})
self._request_logger.info(json.dumps({
'timestamp': now,
'gm_request_id': gm_request_id,
'log_locating_info': log_locating_info,
'request_json_value': json_encode_with_length(request_json_value),
'response_json_value': json_encode_with_length(response_to_v1_json_value(response)),
}, ensure_ascii=False))
return response
def setup_rpcd(): def setup_rpcd():
logger = logging.getLogger('django.start') logger = logging.getLogger('django.start')
logger.info('****** setup rpcd ******') logger.info('****** setup rpcd ******')
...@@ -91,6 +215,9 @@ def setup_rpcd(): ...@@ -91,6 +215,9 @@ def setup_rpcd():
from gm_rpcd.internals import configuration from gm_rpcd.internals import configuration
configuration.__dict__['config'] = config_wrapper configuration.__dict__['config'] = config_wrapper
from gm_rpcd.internals.dispatcher import Dispatcher
Dispatcher.process_single_request = process_single_request
####################### end PATCH ########################## ####################### end PATCH ##########################
from gm_rpcd.internals.initializations import initialize from gm_rpcd.internals.initializations import initialize
......
...@@ -20,7 +20,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ...@@ -20,7 +20,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'd2#)2cd!efrj@to&45yn=+=82-b*&of3i*&fxgfq%v53h1&1qx' SECRET_KEY = 'fl-lv3j^czg(dupo@hbs(+_+djby9zelwt&rh71@b1x*ptmj4#(^' # gaia dev
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
...@@ -34,10 +34,11 @@ INSTALLED_APPS = [ ...@@ -34,10 +34,11 @@ INSTALLED_APPS = [
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
# 'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
# 'django.contrib.staticfiles', # 'django.contrib.staticfiles',
'api' 'api',
'rpc_framework'
] ]
MIDDLEWARE = [ MIDDLEWARE = [
...@@ -274,4 +275,10 @@ COUNT_LIMIT = 100 ...@@ -274,4 +275,10 @@ COUNT_LIMIT = 100
ES_SEARCH_TIMEOUT = '10s' ES_SEARCH_TIMEOUT = '10s'
DATABASE_ROUTERS = ['courier.db_routers.MessageRouter'] DATABASE_ROUTERS = ['courier.db_routers.MessageRouter']
MESSAGE_DB_NAME = 'default' MESSAGE_DB_NAME = 'default'
MESSAGE_SLAVE_DB_NAME = 'slave' MESSAGE_SLAVE_DB_NAME = 'slave'
\ No newline at end of file
# AUTHENTICATION_BACKENDS = (AUTH_WE_CHAT_BACKEND, AUTH_DJANGO_DEFAULT_BACKEND)
AUTH_USER_USING_GAIA_DB = True
# SESSION_ENGINE = 'django.contrib.sessions.backends.db'
# SESSION_ENGINE = 'rpc_framework.auth'
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' # fuck gaia
\ No newline at end of file
...@@ -3,4 +3,5 @@ from gm_types.utils.enum import Enum, unique ...@@ -3,4 +3,5 @@ from gm_types.utils.enum import Enum, unique
class Error(int, Enum): class Error(int, Enum):
PARAMS_INVALID = (10001, '非法参数') PARAMS_INVALID = (10001, '非法参数')
\ No newline at end of file AUTH_FAILED = (40300, '登录信息不正确或没有权限')
\ No newline at end of file
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from typing import List, Dict, Optional from typing import List, Dict, Optional
from django.conf import settings
from six import string_types from six import string_types
import dateutil import dateutil
from django.db.models import Q from django.db.models import Q
...@@ -11,9 +12,12 @@ from adapter.old_system import bind_prefix ...@@ -11,9 +12,12 @@ from adapter.old_system import bind_prefix
from adapter.rpcd.exceptions import RPCPermanentError from adapter.rpcd.exceptions import RPCPermanentError
from api.models.message import ConversationUserStatus from api.models.message import ConversationUserStatus
from rpc import gaia_client from rpc import gaia_client
from rpc.gaia_client import get_doctor_basic_info, get_single_doctor_basic_info
from rpc_framework import exceptions
from rpc_framework.context import RPCViewContext from rpc_framework.context import RPCViewContext
from rpc_framework.decorators import rpc_view, interceptor_classes from rpc_framework.decorators import rpc_view, interceptor_classes
from rpc_framework.interceptors import CalleeParametersInterceptor from rpc_framework.interceptors import CalleeParametersInterceptor, \
SessionUserInterceptor
from search.utils import search_conversation_from_es from search.utils import search_conversation_from_es
from services.unread.stat import UserUnread from services.unread.stat import UserUnread
...@@ -236,6 +240,29 @@ def message_conversation_list_v3(user_ids: List[int], ...@@ -236,6 +240,29 @@ def message_conversation_list_v3(user_ids: List[int],
@rpc_view('message/conversation/can_send') @rpc_view('message/conversation/can_send')
@interceptor_classes([CalleeParametersInterceptor]) @interceptor_classes([CalleeParametersInterceptor, SessionUserInterceptor])
def check_can_send_message(context: RPCViewContext, target_uid: str) -> Dict: def check_can_send_message(context: RPCViewContext, target_uid: str) -> Dict:
return {'a': 'b'} user = context.user
# doctor_info = get_single_doctor_basic_info(user.id)
#
# target_user_id = doctor_info.get('user_id')
# print('target_user_id:', target_user_id)
# raise exceptions.NotFoundException
# if not target_user_id:
# raise 26872687
# target_doctor = get_doctor_by_user_id(target_user.id)
#
# if doctor or target_doctor:
# return {'can_send': True}
#
# user_follow = user.fans.filter(follow=target_user, bond=True).exists()
# target_follow = target_user.fans.filter(follow=user, bond=True).exists()
#
# if user_follow and target_follow:
# return {'can_send': True}
# if target_user.person.id.hex == settings.KEFU_PERSION_ID:
# return {'can_send': True}
return {'can_send': False}
...@@ -44,6 +44,12 @@ def get_doctor_basic_info(doctor_ids: List[str]) -> List[Dict]: ...@@ -44,6 +44,12 @@ def get_doctor_basic_info(doctor_ids: List[str]) -> List[Dict]:
).unwrap() ).unwrap()
def get_single_doctor_basic_info(doctor_id: str) -> Dict:
return get_rpc_remote_invoker()['api/doctor/basic_info'](
doctor_ids=[doctor_id]
).unwrap()[0]
def batch_get_doctor_info_by_user_ids(user_ids: List[int]) -> Dict: def batch_get_doctor_info_by_user_ids(user_ids: List[int]) -> Dict:
return get_rpc_remote_invoker()['api/batch_get_doctor_info_by_user_ids']( return get_rpc_remote_invoker()['api/batch_get_doctor_info_by_user_ids'](
user_ids=user_ids, user_ids=user_ids,
......
# -*- coding: utf-8 -*-
from importlib import import_module
from typing import Type
import django
from django.conf import settings
from django.contrib.auth import BACKEND_SESSION_KEY, load_backend, \
HASH_SESSION_KEY, SESSION_KEY
from django.db import models
from django.utils.crypto import constant_time_compare
from rpc_framework.context import RPCViewContext
from rpc_framework.models import GaiaUser
from django.contrib.sessions.models import Session as DjangoSession
from django.contrib.sessions.backends.db import SessionStore as DjangoSessionStore
# class Session(DjangoSession):
# objects = models.Manager().db_manager(using='gaia')
# class SessionStore(DjangoSessionStore):
# @classmethod
# def get_model_class(cls):
# Avoids a circular import and allows importing SessionStore when
# django.contrib.sessions is not in INSTALLED_APPS.
# return Session
def get_user(context):
"""
Return the user model instance associated with the given request session.
If no user is retrieved, return an instance of `AnonymousUser`.
"""
if getattr(settings, 'AUTH_USER_USING_GAIA_DB', False):
return get_user_using_gaia_db(context)
else:
return get_user_using_passport_service(context)
def get_session_store_class() -> Type[django.contrib.sessions.backends.db.SessionStore]:
# django SessionMiddleware
engine = import_module(settings.SESSION_ENGINE)
return engine.SessionStore
def get_user_using_gaia_db(context: RPCViewContext):
from django.contrib.auth.models import AnonymousUser
user = None
try:
session_store_class = get_session_store_class()
session = session_store_class(context._context.session_id)
user_id = GaiaUser._meta.pk.to_python(session[SESSION_KEY])
backend_path = session[BACKEND_SESSION_KEY]
except KeyError:
pass
else:
if backend_path in settings.AUTHENTICATION_BACKENDS:
backend = load_backend(backend_path)
user = backend.get_user(user_id)
# Verify the session
if hasattr(user, 'get_session_auth_hash'):
session_hash = session.get(HASH_SESSION_KEY)
session_hash_verified = session_hash and constant_time_compare(
session_hash,
user.get_session_auth_hash()
)
if not session_hash_verified:
session.flush()
user = None
return user or AnonymousUser()
def get_user_using_passport_service(context):
pass
"""
Provides various authentication policies.
"""
from __future__ import unicode_literals
class BaseAuthentication(object):
"""
All authentication classes should extend BaseAuthentication.
"""
def authenticate(self, request):
"""
Authenticate the request and return a two-tuple of (user, token).
"""
raise NotImplementedError(".authenticate() must be overridden.")
def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
pass
class SessionAuthentication(BaseAuthentication):
"""
Use Django's session framework for authentication.
"""
def authenticate(self, context):
"""
Returns a `User` if the request session currently has a logged in user.
Otherwise returns `None`.
"""
# Get the session-based user from the underlying HttpRequest object
user = getattr(context.context, 'user', None)
# Unauthenticated, CSRF validation not required
if not user or not user.is_active:
return None
# self.enforce_csrf(context)
# CSRF passed with authenticated user
return (user, None)
\ No newline at end of file
...@@ -82,7 +82,7 @@ class RPCViewContext(object): ...@@ -82,7 +82,7 @@ class RPCViewContext(object):
instance, ensuring that it is available to any middleware in the stack. instance, ensuring that it is available to any middleware in the stack.
""" """
self._user = value self._user = value
self._request.user = value self._context.user = value
@property @property
def request(self) -> Optional[Request]: def request(self) -> Optional[Request]:
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from typing import Sequence from typing import Sequence, Optional, Union, Dict, List
from gm_rpcd.internals.exceptions import RPCDFaultException from gm_rpcd.internals.exceptions import RPCDFaultException
from pydantic import ValidationError, create_model from pydantic import ValidationError, create_model
from pydantic.error_wrappers import ErrorList from pydantic.error_wrappers import ErrorList, ErrorWrapper
from extension.types import Error from extension.types import Error
...@@ -22,29 +22,41 @@ def _check_methods(C, *methods): ...@@ -22,29 +22,41 @@ def _check_methods(C, *methods):
return True return True
class RPCViewBaseException(metaclass=ABCMeta): class RPCViewBaseException(Exception, metaclass=ABCMeta):
code: int code: int
message: str message: str
data: Optional[Union[Dict, List]]
__slots__ = () __slots__ = ()
@classmethod @classmethod
def __subclasshook__(cls, C): def __subclasshook__(cls, C):
if cls is RPCViewBaseException: if cls is RPCViewBaseException:
return _check_methods(C, "as_rpcd_fault") return _check_methods(C, "as_rpc_view_exception")
return NotImplemented return NotImplemented
@abstractmethod
def as_rpcd_fault(self):
return RPCDFaultException(error=self.error, message=self.message)
RequestErrorModel = create_model("Request") RequestErrorModel = create_model("Request")
class CalleeTypeHintValidationError(ValidationError): class CalleeTypeHintValidationError(RPCViewBaseException):
def __init__(self, errors: Sequence[ErrorList]) -> None: code = Error.PARAMS_INVALID
return super().__init__(errors, RequestErrorModel) message = ''
@classmethod
def from_error_wrapper_list(cls, errors:List[ErrorWrapper]):
validation_error = ValidationError(errors, RequestErrorModel)
self = cls()
self.message = validation_error.json()
return self
class AuthenticationFailed(RPCViewBaseException):
code = Error.AUTH_FAILED
message = Error.getDesc(Error.AUTH_FAILED)
class NotFoundException(RPCViewBaseException):
code = 1404
message = "Not Found"
# default_message = "Not Found"
def as_rpcd_fault(self):
return RPCDFaultException(code=Error.PARAMS_INVALID, message=self.json())
...@@ -4,6 +4,7 @@ from abc import ABCMeta, abstractmethod ...@@ -4,6 +4,7 @@ from abc import ABCMeta, abstractmethod
from copy import deepcopy from copy import deepcopy
from typing import Type, Any, List, Optional, Dict, Tuple from typing import Type, Any, List, Optional, Dict, Tuple
from django.utils.functional import SimpleLazyObject
from pydantic import BaseModel, BaseConfig, MissingError from pydantic import BaseModel, BaseConfig, MissingError
from pydantic.error_wrappers import ErrorWrapper from pydantic.error_wrappers import ErrorWrapper
from pydantic.fields import ModelField, Required, FieldInfo, SHAPE_SINGLETON, \ from pydantic.fields import ModelField, Required, FieldInfo, SHAPE_SINGLETON, \
...@@ -11,7 +12,7 @@ from pydantic.fields import ModelField, Required, FieldInfo, SHAPE_SINGLETON, \ ...@@ -11,7 +12,7 @@ from pydantic.fields import ModelField, Required, FieldInfo, SHAPE_SINGLETON, \
from pydantic.schema import get_annotation_from_field_info from pydantic.schema import get_annotation_from_field_info
from pydantic.utils import lenient_issubclass from pydantic.utils import lenient_issubclass
from rpc_framework import params from rpc_framework import params, auth, exceptions
from rpc_framework.context import RPCViewContext from rpc_framework.context import RPCViewContext
from rpc_framework.exceptions import CalleeTypeHintValidationError from rpc_framework.exceptions import CalleeTypeHintValidationError
from rpc_framework.inspector import get_typed_signature from rpc_framework.inspector import get_typed_signature
...@@ -165,7 +166,6 @@ def check_parameters_fit_signature( ...@@ -165,7 +166,6 @@ def check_parameters_fit_signature(
else: else:
values[field.name] = deepcopy(field.default) values[field.name] = deepcopy(field.default)
continue continue
print(value, values)
v_, errors_ = field.validate(value, values, loc=("body", field.alias)) v_, errors_ = field.validate(value, values, loc=("body", field.alias))
if isinstance(errors_, ErrorWrapper): if isinstance(errors_, ErrorWrapper):
errors.append(errors_) errors.append(errors_)
...@@ -182,5 +182,15 @@ class CalleeParametersInterceptor(Interceptor): ...@@ -182,5 +182,15 @@ class CalleeParametersInterceptor(Interceptor):
callee_field_list = get_callee_field_list(rpc_view.callee) callee_field_list = get_callee_field_list(rpc_view.callee)
validated_values, errors = check_parameters_fit_signature(callee_field_list, context.request.params) validated_values, errors = check_parameters_fit_signature(callee_field_list, context.request.params)
if errors: if errors:
raise CalleeTypeHintValidationError(errors) raise CalleeTypeHintValidationError.from_error_wrapper_list(errors)
return True return True
\ No newline at end of file
class SessionUserInterceptor(Interceptor):
def intercept(self, context: RPCViewContext, rpc_view: RPCView) -> bool:
user = auth.get_user(context)
context.user = user
if context.user:
return True
raise exceptions.AuthenticationFailed()
# -*- coding: utf-8 -*-
from django.contrib.auth.models import User
from django.db import models
class BaseUser(User):
class Meta(User.Meta):
db_table = 'auth_user'
class GaiaUser(User):
# objects = models.Manager().db_manager(using='gaia')
pass
\ No newline at end of file
...@@ -14,10 +14,11 @@ def set_rollback(): ...@@ -14,10 +14,11 @@ def set_rollback():
def exception_handler(exc, wrapped_context) -> None: def exception_handler(exc, wrapped_context) -> None:
if issubclass(exc.__class__, exceptions.RPCViewBaseException): set_rollback()
set_rollback() if isinstance(exc, exceptions.RPCViewBaseException):
raise exc.as_rpcd_fault() # type: RPCDFaultException raise exc
elif issubclass(exc.__class__, exceptions.RPCViewBaseException):
raise exc.as_rpc_view_exception()
return None return None
...@@ -76,7 +77,6 @@ class RPCView(RPCAbstractView): ...@@ -76,7 +77,6 @@ class RPCView(RPCAbstractView):
result = handler(context, *args, **kwargs) result = handler(context, *args, **kwargs)
except Exception as exc: except Exception as exc:
print('error::', exc, type(exc))
result = self.handle_exception(exc) result = self.handle_exception(exc)
self.result = self.finalize_response(context, result, *args, **kwargs) self.result = self.finalize_response(context, result, *args, **kwargs)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment