当 i18n 打开时,django-cors-headers 不工作
django-cors-headers not working when i18n is on
我的工作环境是:
django==1.10
django-rest-framework==3.5.3
djangorestframework-jsonapi==2.1.1
channels
(最新)
daphne
(最新)而不是 gunicorn
.
我在 daphne
之上,在 docker 环境中使用 nginx
作为代理服务器。
我正在构建一个单独的 angular 2
SPA 连接到上述后端和
我正在使用 django-cors-headers==2.0.2
来允许来自该网络应用程序的连接。
适用于: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
curl 的输出:
* 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
时相同的连接失败。
curl 的输出:
* 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/' to 'http://localhost/en/api/auth/login/”的重定向,Django 将 'en' 添加到 URL.
有人可以解释一下吗?
我搜索了 django-cors-headers
相关问题,但 none 是针对与 I18N 明显不兼容的问题。该库在没有 I18N 的情况下工作正常,只是没有打开它。
编辑 2017-03-21
鉴于接受的答案中所述的限制,我选择了简单地避免 Django 的语言 URL 重定向。在使用 USE_I18N = True
时,我完全避免在根 URLconf.
中使用 i18n_patterns
事实上,Django Rest Framework 声明这是 API 客户的最佳实践:
If you want to allow per-request language preferences you'll need to include django.middleware.locale.LocaleMiddleware
in your MIDDLEWARE_CLASSES
setting.
You can find more information on how the language preference is determined in the Django documentation. For reference, the method is:
- First, it looks for the language prefix in the requested URL.
- Failing that, it looks for the
LANGUAGE_SESSION_KEY
key in the current user’s session.
- Failing that, it looks for a cookie.
- Failing that, it looks at the
Accept-Language
HTTP header.
- Failing that, it uses the global
LANGUAGE_CODE
setting.
For API clients the most appropriate of these will typically be to use the Accept-Language
header; Sessions and cookies will not be available unless using session authentication, and generally better practice to prefer an Accept-Language
header for API clients rather than using language URL prefixes.
所以,我保持上面的设置不变,但在根 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
returns 所请求语言的预期响应(请注意在上述请求中包含 "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
本质上,您 运行 遇到了旧版本 CORS 标准中的错误。
原始标准基本上使得在使用prelight请求时无法进行本地重定向。请参阅 Fetch 标准中的 on the subject, along with this bug report。
在您的情况下,USE_I18N = True
会发生这种情况,因为该设置会触发重定向。
希望浏览器能尽快实施修复。 (根据 latest report on the Fetch bug it already works in Edge.) In the meantime, 建议的一些解决方法。
我遇到了同样的问题,因为我没有对我的所有 URL 使用 i18n_patterns
,并且其中一个不在 i18n_patterns
内的 URL 返回了 404 响应。
我通过覆盖默认导入Django的Middleware 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
我的工作环境是:
django==1.10
django-rest-framework==3.5.3
djangorestframework-jsonapi==2.1.1
channels
(最新)daphne
(最新)而不是gunicorn
.
我在 daphne
之上,在 docker 环境中使用 nginx
作为代理服务器。
我正在构建一个单独的 angular 2
SPA 连接到上述后端和
我正在使用 django-cors-headers==2.0.2
来允许来自该网络应用程序的连接。
适用于: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
curl 的输出:
* 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
时相同的连接失败。
curl 的输出:
* 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/' to 'http://localhost/en/api/auth/login/”的重定向,Django 将 'en' 添加到 URL.
有人可以解释一下吗?
我搜索了 django-cors-headers
相关问题,但 none 是针对与 I18N 明显不兼容的问题。该库在没有 I18N 的情况下工作正常,只是没有打开它。
编辑 2017-03-21
鉴于接受的答案中所述的限制,我选择了简单地避免 Django 的语言 URL 重定向。在使用 USE_I18N = True
时,我完全避免在根 URLconf.
i18n_patterns
事实上,Django Rest Framework 声明这是 API 客户的最佳实践:
If you want to allow per-request language preferences you'll need to include
django.middleware.locale.LocaleMiddleware
in yourMIDDLEWARE_CLASSES
setting.You can find more information on how the language preference is determined in the Django documentation. For reference, the method is:
- First, it looks for the language prefix in the requested URL.
- Failing that, it looks for the
LANGUAGE_SESSION_KEY
key in the current user’s session.- Failing that, it looks for a cookie.
- Failing that, it looks at the
Accept-Language
HTTP header.- Failing that, it uses the global
LANGUAGE_CODE
setting.For API clients the most appropriate of these will typically be to use the
Accept-Language
header; Sessions and cookies will not be available unless using session authentication, and generally better practice to prefer anAccept-Language
header for API clients rather than using language URL prefixes.
所以,我保持上面的设置不变,但在根 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
returns 所请求语言的预期响应(请注意在上述请求中包含 "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
本质上,您 运行 遇到了旧版本 CORS 标准中的错误。
原始标准基本上使得在使用prelight请求时无法进行本地重定向。请参阅 Fetch 标准中的
在您的情况下,USE_I18N = True
会发生这种情况,因为该设置会触发重定向。
希望浏览器能尽快实施修复。 (根据 latest report on the Fetch bug it already works in Edge.) In the meantime,
我遇到了同样的问题,因为我没有对我的所有 URL 使用 i18n_patterns
,并且其中一个不在 i18n_patterns
内的 URL 返回了 404 响应。
我通过覆盖默认导入Django的Middleware 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