Angular:自定义标头被 $http 和 $resource 忽略。为啥?

Posted

技术标签:

【中文标题】Angular:自定义标头被 $http 和 $resource 忽略。为啥?【英文标题】:Angular: Custom headers are ignored by $http and $resource. Why?Angular:自定义标头被 $http 和 $resource 忽略。为什么? 【发布时间】:2013-12-14 12:22:06 【问题描述】:

我正在尝试访问我无法控制的 REST 服务。第一个问题是该服务不包含 Access-Control-Allow-Origin 标头,如果我理解正确,这个问题会立即将我限制为 JSONP。

此外,默认情况下,此服务发送 XML 而不是 JSON,尽管它能够发送 JSON。我认为它应该响应我的 Accept 标头,负责服务的人员说它会查看我的 Content-Type。这意味着我需要执行 POST 而不是 GET(尽管当我只是获取一些静态数据时 get 更有意义,对吧?)。

虽然我很固执,但我会先尝试我的 Accept 标头。由于 Angular 只接受 JSON,我希望它默认使用 Accept: application/json 标头,但它没有,它忽略了我手动设置它的尝试:

app.config(['$httpProvider', function($httpProvider)
    console.log($httpProvider.defaults.headers.common);
    delete $httpProvider.defaults.headers.common['X-Requested-With'];
    $httpProvider.defaults.headers.post['Accept'] = 'application/json, text/javascript';
    $httpProvider.defaults.headers.post['Content-Type'] = 'application/json; charset=utf-8';
    $httpProvider.defaults.headers.post['Access-Control-Max-Age'] = '1728000';
    $httpProvider.defaults.headers.common['Access-Control-Max-Age'] = '1728000';
    $httpProvider.defaults.headers.common['Accept'] = 'application/json, text/javascript';
    $httpProvider.defaults.headers.common['Content-Type'] = 'application/json; charset=utf-8';
    $httpProvider.defaults.useXDomain = true;
]);

我在实际资源中再次这样做:

return $resource('http://foo.com/getStuff', , 
    fetch: 
        method:'JSONP',
        params: params,
        headers: 
            'Accept':'application/json, text/javascript',
            'Content-Type':'application/json; charset=utf-8'
        ,
        isArray:false,
        callback: 'JSON_CALLBACK'
    
);

但是,请求标头仍然包含Accept: */*

我的问题是:为什么?为什么 Angular 会忽略我的标题?以及如何让它使用正确的标题?

还有:有没有办法在 POST 中使用 JSONP?

编辑: 最初我使用 Angular 1.0.7,但我只是在 1.2.3 上尝试过,得到了相同的结果。标题被忽略,但每个人都声称这是这样做的方法。

我也试过直接用$http而不是用$resource来做,结果是一样的。

编辑 2: 这是JSFiddle。它是匿名的,不使用我的真实服务器,但使用 Firebug/开发人员工具,您可以验证它在两个调用中都发送 Accept: */*,尽管我多次尝试设置 application/json 标头。这是我真正的问题。在我的真实服务器上,尽管我的真实服务器能够发送 JSON,但我得到了 XML 结果。

(目前真实服务器是否支持jsonp不太重要。这个虚拟服务器显然不支持,但没关系。我只关心标题。)

编辑 3:我尝试了以下建议的两种解决方案:

$http.defaults.headers.common['Accept'] = 'application/json, text/javascript';

$http.defaults.transformRequest.push(function (data, headersGetter) 
    headersGetter().Accept = "application/json, text/javascript";
    return data;
);

我已经分别尝试了这两种说法。在控制器中,然后在 http 调用本身之前的服务中。还是不行。

有人可以给我一个 JsFiddle 吗?

编辑 4: 我注意到当我使用 GET 而不是 JSONP 时,Accept 标头是正确的。但随后响应被拒绝,因为它没有正确的标题。

JSONP 调用应该有什么样的标头?因为 JSONP 调用中有更多的标头,但没有将其标识为 JSONP。服务器是否必须有明确的 JSONP 支持才能工作?我突然意识到我对 jsonp 的了解还不够。

【问题讨论】:

您确定您使用的 WebService 以有效的JSONP 响应您的请求吗?我注意到的第一件事是您没有正确设置callback 查询参数。 $resource 应该是:..., params: callback: 'JSON_CALLBACK', ... 如果可能,请提供 Plunk 或 JSFiddle,以便我们可以使用您的代码。 我确定它不会以有效的 JSONP 响应。它向我发送 XML,大概是因为我没有发送正确的标头。我目前的主要问题是正确设置标头,以便服务知道向我发送 json 或 jsonp。 我知道,这就是为什么我建议首先查看您的 REQUEST URL(-:。每个使用 JSONP 响应的 WebService 都在 URL 查询字符串中搜索 callback,并且永远不会使用 JSONP 响应因为它需要callback 参数值来执行此操作。它基本上提取callback 参数值并将:<<callback_param_value>>(<<response_JSON_object>>); 作为application/javascript 发送给您 @mcv 您不能为 JSONP 请求设置标头或使用 POST 方法。 JSONP 只是CORS 的一种老套且过时的方式:) 【参考方案1】:

我想你的答案是here。根据the wiki,通过注入<script> 标签来执行JSONP 调用,以从主机服务器加载脚本,主机服务器通过调用您的回调、传递数据来响应。 <script> 标记生成常规浏览器请求(不是XmlHttpRequest),浏览器将发送自己的 Accept 标头(例如,它也发送自己的 User-Agent 标头)。

我希望有一种更简单的客户端方法可以做到这一点,但我认为唯一的方法可能是the referenced post 中建议的方法:

所以,如果您希望能够为跨域调用设置请求标头 您必须在您的域上设置一个服务器端脚本,该脚本将 将调用委托给远程域(并设置相应的 headers),然后将 AJAX 请求发送到您的脚本。

编辑:here 是关于同一问题的(被拒绝的)jQuery 错误报告。

更多背景信息:

在 Angular 中,回调是自动管理的,所以如果你这样说:

$http(
    method: "JSONP",
    url: "http://headers.jsontest.com?callback=JSON_CALLBACK",
).success(function(data) 
    console.log('Return value:');
    console.log(data);
).error(function(data) 
    console.log('Error!');
    console.log(data);
)

将创建一个<script> 标签,看起来或多或少像这样:

<script type="application/javascript"
        src="http://headers.jsontest.com/?callback=angular.callbacks._1">
</script>

回复http://headers.jsontest.com/?callback=angular.callbacks._1的内容为:

angular.callbacks._1(key1: "value1", key2: "value2");

angular.callbacks._1 将包含您的success 函数,并将与数据一起调用。

【讨论】:

感谢您的广泛解释。在过去的一周里,我有点把这些信息拼凑在一起,但最好把它明确地放在一个地方。不知何故,我的印象是客户端以某种方式处理了 JSONP 的东西,但现在我很清楚服务器需要能够处理它。而且我猜这也意味着 JSONP 已经过时了,因为如果您可以向服务器添加 JSONP 支持,那么在您的响应中添加 Access-Control-Allow-Origin 标头可能会更容易。 这也使得docs.angularjs.org/api/ng.$http 的“JSON 漏洞保护”变得毫无意义,IMO。 (正是这一点让我相信您可以在服务器上违背服务器所有者的意愿使用 JSONP,从而需要额外的保护。)【参考方案2】:

虽然您所拥有的应该根据文档工作,但我的经验却有点不同。为了解决这个问题,我们做了以下工作:

创建一个“基本控制器”,在 body 或 html 标记上添加到页面。 在该控制器中,使用 $http 而不是 $httpProvider 进行分配。因为您的基本控制器会在初始页面加载时加载,所以所有其他将在您的应用中运行的控制器和服务都将在此加载。

我不知道为什么这有效而被禁止的方法无效,我很想看到比这种变通方法更好的问题的答案,但至少这可以让您继续发展再次。

【讨论】:

似乎不起作用。也许你能在 JsFiddle 中展示这个吗?在我的情况下,如果我在控制器或服务中添加它,我仍然会得到Accept: */* JsFiddle 和我今天有爱/恨的关系。在尝试使小提琴工作时,我在 Angular 文档中注意到,除了列出默认值正是您想要的之外,它们还使用对象而不是单独进行设置: $httpProvider.defaults.headers.get = “我的标题”:“价值”。也许您应该检查您的服务器以查看这些接受类型是否得到正确处理?我们很早就遇到了同样的情况,结果就是这样。【参考方案3】:

以下内容对我有用 - 但是,我在“运行时”使用 $http 执行此操作,并且在引导期间我没有使用 $httpProvider

function SomeCtrl($http) 

    $http.defaults.transformRequest.push(function (data, headersGetter) 
        headersGetter().Accept = "application/json, text/javascript";
        return data;
    );


编辑

这是一个有效的jsFiddle 版本。检查使用 Developer Tools/Firebug 完成的请求,并查看是否请求了 "application/json, text/javascript"

【讨论】:

等等,transformRequest 是做什么的?这看起来像是第三种处理方式,尽管它听起来与 MBielski 的解决方案非常相似。 @mcv 我添加了一个适用于此版本的小提琴 jsFiddle。 但是你做的是get,而不是jsonp。使用 jsonp,您的小提琴也会丢失标题。当我得到时,我也得到了正确的标题,但我试图让 jsonp 工作。

以上是关于Angular:自定义标头被 $http 和 $resource 忽略。为啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Angular2 中设置全局自定义标头?

Angular HTTP 拦截器自定义预检

带有 CORS 和自定义标头的 AngularJS

Angular:如何扩展 HttpClient?

如何以角度将自定义标头添加到httpRequest

自定义安全 HTTP 标头是不是违反关注点分离