【问题标题】:django-cors-headers not working when i18n is ondjango-cors-headers 在 i18n 开启时不起作用
【发布时间】:2017-08-11 18:02:02
【问题描述】:

我的工作环境是:

  • django==1.10
  • django-rest-framework==3.5.3
  • djangorestframework-jsonapi==2.1.1
  • channels(最新)
  • daphne(最新)而不是 gunicorn

我在 docker 环境中使用nginx 作为daphne 上方的代理服务器。

我正在构建一个单独的angular 2 SPA,它连接到上述后端和 我正在使用django-cors-headers==2.0.2 来允许来自该网络应用程序的连接。

适用于:USE_I18N = False

当我设置 Django 的USE_I18N = False 时它工作正常。当尝试对后端进行身份验证时,我发送了一个相当于:

curl -H "Content-Type: application/vnd.api+json" -X POST -d '{"data": {"type": "obtainJSONWebTokens", "attributes": {"email":"admin@email.com", "password":"password"}}}' http://localhost/api/auth/login/ --verbose

卷曲的输出:

*   Trying ::1...
* Connected to localhost (::1) port 80 (#0)
> POST /api/auth/login/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.49.0
> Accept: */*
> Content-Type: application/vnd.api+json
> Content-Length: 107
>
* upload completely sent off: 107 out of 107 bytes
< HTTP/1.1 200 OK
< Server: nginx/1.11.9
< Date: Mon, 20 Mar 2017 13:00:47 GMT
< Content-Type: application/vnd.api+json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Allow: POST, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Content-Language: en
< Vary: Accept, Accept-Language, Cookie
<
{"data":{"token":"<token>"}}
* Connection #0 to host localhost left intact

我收到了我应该收到的 JWT 令牌。一切正常。

失败并显示:USE_I18N = True

但是,USE_I18N = True 时同样的连接失败。

卷曲的输出:

*   Trying ::1...
* Connected to localhost (::1) port 80 (#0)
> POST /api/auth/login/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.49.0
> Accept: */*
> Content-Type: application/vnd.api+json
> Content-Length: 107

* upload completely sent off: 107 out of 107 bytes
< HTTP/1.1 302 Found
< Server: nginx/1.11.9
< Date: Mon, 20 Mar 2017 12:53:49 GMT
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Location: /en/api/auth/login/
< Vary: Cookie
<
* Connection #0 to host localhost left intact

客户端返回的错误是:

XMLHttpRequest cannot load http://localhost/api/auth/login/. Redirect from 'http://localhost/api/auth/login/' to 'http://localhost/en/api/auth/login/' has been blocked by CORS policy: Request requires preflight, which is disallowed to follow cross-origin redirect.

相关设置:

INSTALLED_APPS += (
    'corsheaders',
)

if DEBUG is True:
    CORS_ORIGIN_ALLOW_ALL = True

MIDDLEWARE_CLASSES = (
    'corsheaders.middleware.CorsMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.admindocs.middleware.XViewMiddleware',
)

似乎不是客户端请求失败,而是从“http://localhost/api/auth/login/”重定向到“http://localhost/en/api/auth/login/”,Django 将“en”添加到 URL。

有人能解释一下吗?

我搜索了django-cors-headers 相关问题,但没有一个是针对这种与 I18N 明显不兼容的问题。该库在没有 I18N 的情况下也能正常工作,只是没有打开它。

编辑 2017-03-21

鉴于接受的答案中所述的限制,我选择简单地避免 Django 的语言 URL 重定向。在使用USE_I18N = True 时,我完全避免在根URLconf 中使用i18n_patterns

事实上,Django Rest Framework 声明这是 API 客户端的最佳实践:

如果您想允许每个请求的语言首选项,您需要在您的 MIDDLEWARE_CLASSES 设置中包含 django.middleware.locale.LocaleMiddleware

您可以在 Django 文档中找到有关如何确定语言首选项的更多信息。供参考,方法是:

  • 首先,它在请求的 URL 中查找语言前缀。
  • 如果失败,它会在当前用户的会话中查找LANGUAGE_SESSION_KEY 键。
  • 如果失败,它会寻找 cookie。
  • 如果失败,它会查看Accept-Language HTTP 标头。
  • 否则,它将使用全局 LANGUAGE_CODE 设置。

对于 API 客户端,最合适的通常是使用 Accept-Language 标头;除非使用会话身份验证,否则会话和 cookie 将不可用,通常更好的做法是更喜欢 API 客户端的 Accept-Language 标头,而不是使用语言 URL 前缀。

所以,我保持上述设置不变,但在根 URLconf 中更改了以下设置:

urlpatterns += i18n_patterns(
    url(_(r'^api/$'), SwaggerSchemaView.as_view(), name='api'),
    url(_(r'^api/account/'), include(account_patterns, namespace='account')),
    url(_(r'^api/auth/'), include(auth_patterns, namespace='auth')),
    url(_(r'^api/'), include('apps.party.api.urls', namespace='parties')),
    url(_(r'^api/'), include('apps.i18n.api.urls', namespace='i18n')),
    url(_(r'^api-auth/'), include('rest_framework.urls', namespace='rest_framework')),
    url(_(r'^admin/'), include(admin_patterns)),
    url(_(r'^docs/'), include('apps.docs.urls'))
)

urlpatterns += ([
    url(_(r'^api/$'), SwaggerSchemaView.as_view(), name='api'),
    url(_(r'^api/account/'), include(account_patterns, namespace='account')),
    url(_(r'^api/auth/'), include(auth_patterns, namespace='auth')),
    url(_(r'^api/'), include('apps.party.api.urls', namespace='parties')),
    url(_(r'^api/'), include('apps.i18n.api.urls', namespace='i18n')),
    url(_(r'^api-auth/'), include('rest_framework.urls',     namespace='rest_framework')),
    url(_(r'^admin/'), include(admin_patterns)),
    url(_(r'^docs/'), include('apps.docs.urls'))]
)

所以,现在,做:

curl -H "Content-Type: application/vnd.api+json" -H "Accept-Language: pt" -X POST -d '{"data": {"type": "obtainJSONWebTokens", "attributes": {"email":"admin@email.com", "password":"password"}}}' http://localhost:8000/api/auth/login/ --verbose

以请求的语言返回预期的响应(请注意在上面的请求中包含"Accept-Language: pt"):

*   Trying ::1...
* Connected to localhost (::1) port 8000 (#0)
> POST /api/auth/login/ HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.49.0
> Accept: */*
> Content-Type: application/vnd.api+json
> Accept-Language: pt
> Content-Length: 107
>
* upload completely sent off: 107 out of 107 bytes
< HTTP/1.1 200 OK
< Transfer-Encoding: chunked
< Allow: POST, OPTIONS
< X-Frame-Options: SAMEORIGIN
< Vary: Accept, Accept-Language, Cookie
< Content-Language: pt
< Content-Type: application/vnd.api+json
<
{"data":    {"token":"<token>"}}
*     Connection #0 to host localhost left intact

【问题讨论】:

    标签: django channels django-cors-headers daphne


    【解决方案1】:

    本质上,您遇到了旧版本 CORS 标准中的错误。

    原始标准基本上使得在使用预光照请求时无法进行本地重定向。请参阅有关主题的 this question 以及有关 Fetch 标准的 this bug report

    在您的情况下,USE_I18N = True 会发生这种情况,因为该设置会触发重定向。

    希望该修复程序将很快由浏览器实施。 (根据 Fetch 错误中的 latest report,它已经在 Edge 中工作。)与此同时,this answer 提出了一些解决方法。

    【讨论】:

    • 我接受了这个答案。鉴于提供的解释和链接中所述的限制,我选择简单地避免 Django 的语言重定向。在使用USE_I18N = True 时,我完全避开了i18n_patterns。我已经用更多细节编辑了这个问题。
    【解决方案2】:

    我遇到了同样的问题,因为我没有为我的所有 URL 使用 i18n_patterns,并且不在 i18n_patterns 中的 URL 之一返回了 404 响应。 我通过覆盖默认导入Django的中间件LocaleMiddleware解决了这个问题。

    class CustomLocaleMiddleware(LocaleMiddleware): 
      def current_urlpattern_is_locale(self, path):
          try:
              resolver = get_resolver(None).resolve(path)
          except Resolver404:
            return self.is_language_prefix_patterns_used()
          return isinstance(resolver, LocaleRegexURLResolver)
    
      def process_response(self, request, response):
        language = translation.get_language()
        language_from_path = translation.get_language_from_path(request.path_info)
        if (response.status_code == 404 and not language_from_path
                and self.current_urlpattern_is_locale(request.path)):
            urlconf = getattr(request, 'urlconf', None)
            language_path = '/%s%s' % (language, request.path_info)
            path_valid = is_valid_path(language_path, urlconf)
            if (not path_valid and settings.APPEND_SLASH
                    and not language_path.endswith('/')):
                path_valid = is_valid_path("%s/" % language_path, urlconf)
    
            if path_valid:
                script_prefix = get_script_prefix()
                language_url = "%s://%s%s" % (
                    request.scheme,
                    request.get_host(),
                    # insert language after the script prefix and before the
                    # rest of the URL
                    request.get_full_path().replace(
                        script_prefix,
                        '%s%s/' % (script_prefix, language),
                        1
                    )
                )
                return self.response_redirect_class(language_url)
    
        if not (self.is_language_prefix_patterns_used()
                and language_from_path):
            patch_vary_headers(response, ('Accept-Language',))
        if 'Content-Language' not in response:
            response['Content-Language'] = language
        return response
    

    【讨论】:

      猜你喜欢
      • 2015-03-18
      • 2018-08-21
      • 2016-06-08
      • 2019-08-12
      • 2016-11-16
      • 2018-06-23
      • 2018-04-24
      • 2012-06-29
      • 2018-01-16
      相关资源
      最近更新 更多