将会话从模板视图传递给python请求api调用

时间:2016-07-18 17:28:08

标签: python django

我想使用请求库从我的Django TemplateView进行多个内部REST API调用。现在我想将会话也从模板视图传递到api调用。建议的方法是什么,牢记性能。

现在,我在模板视图中从当前cookie对象中提取request,并将其传递给requests.get()requests.post()来电。但问题是,我必须将request对象传递给我的API客户端,这是我不想要的。

这是我用来路由我的请求的当前包装器:

def wrap_internal_api_call(request, requests_api, uri, data=None, params=None, cookies=None, is_json=False, files=None):
    headers = {'referer': request.META.get('HTTP_REFERER')}
    logger.debug('Request API: %s calling URL: %s', requests_api, uri)
    logger.debug('Referer header sent with requests: %s', headers['referer'])
    if cookies:
        csrf_token = cookies.get('csrftoken', None)
    else:
        csrf_token = request.COOKIES.get('csrftoken', None)

    if csrf_token:
        headers['X-CSRFToken'] = csrf_token
    if data:
        if is_json:
            return requests_api(uri, json=data, params=params, cookies=cookies if cookies else request.COOKIES, headers=headers)
        elif not files:
            return requests_api(uri, data=data, params=params, cookies=cookies if cookies else request.COOKIES, headers=headers)
        else:
            return requests_api(uri, data=data, files=files, params=params, cookies=cookies if cookies else request.COOKIES,
                                headers=headers)
    else:
        return requests_api(uri, params=params, cookies=cookies if cookies else request.COOKIES, headers=headers)

基本上我想摆脱那个request参数(第一个参数),因为之后要调用它我不断将request对象从TemplateViews传递到内部服务。另外,如何在多个呼叫之间保持持久连接?<​​/ p>

3 个答案:

答案 0 :(得分:4)

REST vs直接调用视图

虽然Web应用程序可以对自己进行REST API调用。这不是REST的设计目标。请考虑以下内容:https://docs.djangoproject.com/ja/1.9/topics/http/middleware/

Django Request Response Life Cycle

正如您所看到的,django请求/响应周期具有相当大的开销。添加到webserver和wsgi容器的开销。在客户端,您有与请求库相关联的开销,但是在一秒钟内,客户端也恰好是同一个Web应用程序,因此它也成为Web应用程序开销的一部分。并且存在持久性问题(我将很快介绍)。

最后但并非最不重要的一点是,如果您有DNS循环设置,您的请求实际上可能会在返回同一服务器之前通过网络进行。有一种更好的方法,可以直接调用视图。

在没有其余API调用的情况下调用另一个视图非常简单

 other_app.other_view(request, **kwargs)

此处已经在Django Call Class based view from another class based viewCan I call a view from within another view?这样的链接上进行了几次讨论,所以我不会详细说明。

持久请求

持久性http请求(谈论python请求而非django.http.request.HttpRequest)通过会话对象进行管理(同样不要与django会话混淆)。避免混淆真的很难:

  

Session对象允许您跨越某些参数   要求。它还会在所有请求中保留cookie   会话实例,并将使用urllib3的连接池。因此,如果   您正在向同一主机(底层TCP)发出多个请求   连接将被重用,这可能会导致重大连接   绩效提升

您的django视图的不同点击可能来自不同的用户,因此您不希望将相同的Cookie重用于内部REST调用。另一个问题是python会话对象不能在两个不同的django视图之间保持持久化。套接字通常不能被序列化,这是将它们放入memcached或redis的要求。

如果您仍想继续使用内部REST

我认为@julian的答案显示了如何避免将django请求实例作为参数传递。

答案 1 :(得分:1)

如果你想避免将request传递给wrap_internal_api_call,你需要做的就是在你调用api包装器的TemplateView末尾做更多的工作。请注意,您的原始包装器正在执行大量cookies if cookies else request.COOKIES。您可以将其分解为呼叫站点。重写你的api包装器如下:

def wrap_internal_api_call(referer, requests_api, uri, data=None, params=None, cookies, is_json=False, files=None):
    headers = {'referer': referer}
    logger.debug('Request API: %s calling URL: %s', requests_api, uri)
    logger.debug('Referer header sent with requests: %s', referer)
    csrf_token = cookies.get('csrftoken', None)

    if csrf_token:
        headers['X-CSRFToken'] = csrf_token
    if data:
        if is_json:
            return requests_api(uri, json=data, params=params, cookies=cookies, headers=headers)
        elif not files:
            return requests_api(uri, data=data, params=params, cookies=cookies, headers=headers)
        else:
            return requests_api(uri, data=data, files=files, params=params, cookies=cookies, headers=headers)
    else:
        return requests_api(uri, params=params, cookies=cookies, headers=headers)

现在,在调用的地方,而不是

wrap_internal_api_call(request, requests_api, uri, data, params, cookies, is_json, files)

做的:

cookies_param = cookies or request.COOKIES
referer_param = request.META.get['HTTP_REFERER']
wrap_internal_api_call(referer_param, requests_api, uri, data, params, cookies_param, is_json, files)

现在您不再将request对象传递给包装器了。这样可以节省一点时间,因为您不会反复测试cookies,但不会对性能产生影响。实际上,只需在api包装器中执行cookies or request.COOKIES一次内部即可获得相同的轻微性能提升。

网络始终是任何应用程序中最严重的瓶颈。因此,如果这些内部API与TemplateView在同一台机器上,那么性能的最佳选择是避免进行API调用。

答案 2 :(得分:-1)

  

基本上我想摆脱那个请求参数(第一个参数),因为之后要调用它我不断将请求对象从TemplateViews传递到内部服务。

要传递函数args而不将它们显式传递给函数调用,您可以使用装饰器来包装函数并自动注入参数。使用全局变量和一些django中间件在它到达您的视图之前注册请求将解决您的问题。请参阅下文,了解我的意思的抽象和简化版本。

request_decorators.py

REQUEST = None


def request_extractor(func):

    def extractor(cls, request, *args, **kwargs):
        global REQUEST
        REQUEST = request # this part registers request arg to global
        return func(cls, request, *args, **kwargs) 

    return extractor


def request_injector(func):

    def injector(*args, **kwargs):
        global REQUEST
        request = REQUEST
        if len(args) > 0 and callable(args[0]): # to make it work with class methods
            return func(args[0], request, args[1:], **kwargs) # class method
        return func(request, *args, **kwargs) # function

    return injector

extract_request_middleware.py

See the django docs for info on setting up middleware

from request_decorators import request_extractor

class ExtractRequest:

    @request_extractor
    def process_request(self, request):
        return None

internal_function.py

from request_decorators import request_injector

@request_injector
def internal_function(request):
    return request

your_view.py

from internal_function import internal_function

def view_with_request(request):
    return internal_function() # here we don't need to pass in the request arg.

def run_test():

    request = "a request!"
    ExtractRequest().process_request(request)
    response = view_with_request(request)
    return response


if __name__ == '__main__':

    assert run_test() == "a request!"