0

我有一个工作环境:

  • 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它来允许来自该 Web 应用程序的连接。

它适用于:USE_I18N = False

当我设置 Django 的USE_I18N = False. 当尝试对后端进行身份验证时,我发送一个 POST 请求,相当于:

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时,我完全避免i18n_patterns在根 URLconf 中。

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

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

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

  • 首先,它在请求的 URL 中查找语言前缀。
  • 否则,它会LANGUAGE_SESSION_KEY在当前用户的会话中查找密钥。
  • 如果做不到这一点,它会寻找一个 cookie。
  • 如果做不到这一点,它会查看Accept-LanguageHTTP 标头。
  • 否则,它将使用全局LANGUAGE_CODE设置。

对于 API 客户端,最合适的通常是使用Accept-Language标头;除非使用会话身份验证,否则会话和 cookie 将不可用,通常更好的做法是更喜欢Accept-LanguageAPI 客户端的标头而不是使用语言 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
4

2 回答 2

0

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

原始标准基本上使得在使用 prelight 请求时无法进行本地重定向。请参阅有关该主题的此问题,以及有关 Fetch 标准的此错误报告。

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

希望该修复程序将很快由浏览器实施。(根据关于 Fetch 错误的最新报告,它已经在 Edge 中工作。)与此同时,这个答案提出了一些解决方法。

于 2017-03-20T18:43:29.400 回答
0

我遇到了同样的问题,因为我没有i18n_patterns对我的所有 URL 使用 ,并且其中一个不在内部的 URLi18n_patterns返回了 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
于 2017-05-27T15:45:45.760 回答