远程主机在与 Nest API 握手期间关闭连接 [英] Remote host closed connection during handshake with Nest API

查看:24
本文介绍了远程主机在与 Nest API 握手期间关闭连接的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

试用 Nest API,我让 OAuth 流程正常工作,进行了第一次 API 调用(到 https://developer-api.nest.com/devices.json),得到307 重定向按预期进行,但随后我对重定向位置的调用失败,握手期间远程主机关闭连接.昨晚我参加了在旧金山举行的 Nest 开发者活动,Lev Stesin 让我在这里发布完整的日志并提及他的名字.

代码(Apex,在 Force.com 上运行):

public with share virtual class NestController {公共类 OAuthResponse {公共字符串访问令牌;公共字符串 token_type;公共整数 expires_in;公共字符串refresh_token;公共字符串错误;}公共静态 OAuthResponse 解析(字符串 json){返回(OAuthResponse)System.JSON.deserialize(json,OAuthResponse.class);}公共字符串 accessToken {get;放;}公共字符串输出{get;放;}私有字符串 getAll(String accessToken) {String url = 'https://developer-api.nest.com/devices.json?auth='+accessToken+'&print=pretty';HttpRequest req = new HttpRequest();req.setEndpoint(url);req.setMethod('GET');req.setTimeout(60*1000);Http h = new Http();字符串响应;HttpResponse res = h.send(req);resp = res.getBody();如果(res.getStatusCode()== 307){url = res.getHeader('位置');System.debug('重定向到:'+url);请求 = 新的 HttpRequest();req.setEndpoint(url);req.setMethod('GET');req.setTimeout(60*1000);h = 新的 Http();res = h.send(req);resp = res.getBody();}System.debug('获取返回值:'+resp);返回响应;}公共虚拟页面引用登录(){字符串 clientId = '989360fb-9a1f-4d13-929e-0b40111c725a';String clientSecret = 'SECRET';字符串 sessionId = null;字符串状态 = '哇';//获取页面的 URL,没有任何查询参数String url = ApexPages.currentPage().getUrl().split('\\?')[0];System.debug('url 是 '+url);//注意:fb 应用程序连接设置中的连接 url 应为:https://c.na3.visual.force.com/apex///你需要尾部斜杠,即使它很讨厌它String rediruri = 'https://'+ApexPages.currentPage().getHeaders().get('Host')+url;System.debug('rediruri 是:'+rediruri);字符串 authuri = 'https://home.nest.com/login/oauth2'+'?client_id='+clientId+'&state='+状态;//没有会话页面参考 pageRef;if (ApexPages.currentPage().getParameters().containsKey('error')) {//OAuth 的初始步骤 - 重定向到 OAuth 服务System.debug('Error:' + ApexPages.currentPage().getParameters().get('error'));返回空;}if (!ApexPages.currentPage().getParameters().containsKey('code')) {//OAuth 的初始步骤 - 重定向到 OAuth 服务System.debug('Nest OAuth Step 1');返回新的页面引用(authuri);}//OAuth 的第二步 - 从 OAuth 服务获取令牌String code = ApexPages.currentPage().getParameters().get('code');System.debug('Nest OAuth Step 2 - code:'+code);String tokenuri = 'https://api.home.nest.com/oauth2/access_token';String body = 'code='+code+'&client_id='+clientId+'&client_secret='+clientSecret+'&grant_type=authorization_code';System.debug('body is:'+body);HttpRequest req = new HttpRequest();req.setEndpoint(tokenuri);req.setMethod('POST');req.setTimeout(60*1000);req.setBody(body);Http h = new Http();字符串响应;if (code.equals('TEST')) {resp = 'access_token=TEST&expires=3600';} 别的 {HttpResponse res = h.send(req);resp = res.getBody();}System.debug('FINAL RESP IS:'+resp);OAuthResponse oauth = parse(resp);如果(oauth.error != null){//获取令牌时出错 - 可能重用代码 - 重新开始返回新的页面引用(authuri);}accessToken = oauth.access_token;输出 = getAll(accessToken);返回空;}}

初始 OAuth 重定向:

https://home.nest.com/login/oauth2?client_id=989360fb-9a1f-4d13-929e-0b40111c725a&state=wow

用户授权应用访问恒温器,Nest 重定向回我的应用:

https://c.na9.visual.force.com/apex/Nest?state=wow&code=6F3GV6WQ35NGLYB2

我成功交换了访问令牌的代码:

POST 到 https://api.home.nest.com/oauth2/access_token 和 body

code=6F3GV6WQ35NGLYB2&client_id=989360fb-9a1f-4d13-929e-0b40111c725a&client_secret=SECRET&grant_type=authorization_code

回复:

 <代码> { ACCESS_TOKEN": c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU", expires_in":315360000}

(我从 home.nest.com 撤销了令牌,所以我在这里发帖是安全的!)

所以我做了一个 GET

<预> <代码> https://developer-api.nest.com/devices.json?auth=c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU&print=pretty

并接收预期的 307 重定向,以及位置

<预> <代码> https://firebase-apiserver01-tah01-i​​ad01.dapi.production.nest.com:9553/devices.json?auth=c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU&print=pretty

现在,当我在 Force.com 上运行的 Apex 代码中获取该 URL 时,它失败并显示

System.CalloutException: 远程主机在握手期间关闭了连接

但是,如果我在命令行上从 curl 执行相同的 GET,它会成功,并返回预期的 JSON 响应.

所以看起来在 SSL 握手中可能存在一些不兼容.我会在 Force.com 端进行调查;如果 Nest 的某个人能在他们的最后检查日志,那就太好了 - 这里应该有足够的细节.

EDIT - 以下是 curl -v 到该 URL 的输出:

<预> <代码> $卷曲-v 'https://firebase-apiserver01-tah01-i​​ad01.dapi.production.nest.com:9553/devices.json?auth=c.dPHNEweWehQ47tzSm0sf13o8rX1isO9IdEG1HFwoAmeA2FtBLH1fTiksRtN9DGcPAOyEI3VINz2fD3CFma5ozSNbpqUIwGDGc8ixD1etjiIW6TmXN0Rd0p5VzEtk6sDwIe8j10NH1hKDhevX&print=pretty'* 即将连接()到 firebase-apiserver01-tah01-i​​ad01.dapi.production.nest.com 端口 9553 (#0)* 尝试 54.196.205.148...* 连接的* 连接到 firebase-apiserver01-tah01-i​​ad01.dapi.production.nest.com (54.196.205.148) 端口 9553 (#0)* SSLv3、TLS 握手、客户端问候 (1):* SSLv3、TLS 握手、服务器问候 (2):* SSLv3、TLS 握手、CERT (11):* SSLv3、TLS 握手、服务器密钥交换 (12):* SSLv3,TLS 握手,服务器完成(14):* SSLv3、TLS 握手、客户端密钥交换 (16):* SSLv3、TLS 更改密码、客户端问候 (1):* SSLv3,TLS 握手,完成 (20):* SSLv3、TLS 更改密码、客户端问候 (1):* SSLv3,TLS 握手,完成 (20):* SSL 连接使用 EDH-RSA-DES-CBC3-SHA* 服务器证书:* 主题:OU=域控制验证;CN=*.dapi.production.nest.com* 开始日期:2014-05-28 22:31:28 GMT* 到期日期:2015-05-28 22:31:28 GMT* subjectAltName: firebase-apiserver01-tah01-i​​ad01.dapi.production.nest.com 匹配* 发行人:C=US;ST=亚利桑那州;L=斯科茨代尔;O=GoDaddy.com, Inc.;OU=http://certs.godaddy.com/repository/;CN=Go Daddy 安全证书颁发机构 - G2* SSL 证书验证正常.>GET/devices.json?auth=c.dPHNEweWehQ47tzSm0sf13o8rX1isO9IdEG1HFwoAmeA2FtBLH1fTiksRtN9DGcPAOyEI3VINz2fD3CFma5ozSNbpqUIwGDGc8ixD1etjiIW6TmD1etjiIW6TmD1etjiIW6TmD1tjiIW6TmD1tjiIW6TmD1tjiIW6TmD1tjiIW6TmD1ftjisRtN9DGcPAOyEI3FtBLH1fTiksRtN9DGcPAOyEI3VINz2fD3>用户代理:curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8y zlib/1.2.5>主机:firebase-apiserver01-tah01-i​​ad01.dapi.production.nest.com:9553>接受: */*><HTTP/1.1 200 正常<内容类型:应用程序/json;字符集=UTF-8<访问控制允许来源:*<缓存控制:私有,无缓存,max-age=0<内容长度:2218<{恒温器":{pYo-lbpXuVm_DctuTckA_HdEswRgRkbx":{"locale" : "en-US",温度标度":F",is_using_emergency_heat":假,has_fan":真的,软件版本":4.2.3",has_leaf":真,"device_id" : "pYo-lbpXuVm_DctuTckA_HdEswRgRkbx","name" : "楼下",can_heat":真的,can_cool":真的,"hvac_mode": "关闭",目标温度_c":24.5,目标温度_f":76,target_temperature_high_c":24.0,target_temperature_high_f":75,target_temperature_low_c":20.0,target_temperature_low_f":68,ambient_temperature_c":25.0,环境温度_f":78,远离_温度_高_c":24.0,远离_温度_高_f":76,远离_温度_低_c":15.5,"away_temperature_low_f" : 60,"structure_id" : "HqSZlH08Jc3CtBNIS4OLPdiWLpcfW5o6dP2DvSox7hcGVpBGOH9cQA",fan_timer_active":假,"name_long": "楼下恒温器",is_online":真,last_connection":2014-06-26T23:16:24.341Z"},pYo-lbpXuVncrx7IdGTWyXdEswRgRkbx":{"locale" : "en-US",温度标度":F",is_using_emergency_heat":假,has_fan":真的,软件版本":4.2.3",has_leaf":真,"device_id" : "pYo-lbpXuVncrx7IdGTWyXdEswRgRkbx","name" : "楼上",can_heat":真的,can_cool":真的,"hvac_mode": "关闭",目标温度_c":24.0,目标温度_f":76,target_temperature_high_c":24.0,target_temperature_high_f":75,target_temperature_low_c":20.0,target_temperature_low_f":68,ambient_temperature_c":25.0,环境温度_f":78,远离_温度_高_c":24.0,远离_温度_高_f":76,远离_温度_低_c":15.5,远离_温度_低_f":60,"structure_id" : "HqSZlH08Jc3CtBNIS4OLPdiWLpcfW5o6dP2DvSox7hcGVpBGOH9cQA",fan_timer_active":假,"name_long": "楼上恒温器",is_online":真,last_connection":2014-06-26T23:16:27.849Z"}}* 连接 #0 到主机 firebase-apiserver01-tah01-i​​ad01.dapi.production.nest.com 保持不变}* 关闭连接#0* SSLv3、TLS 警报、客户端问候 (1):

解决方案

来自 Salesforce 的相同标注现在工作正常.我猜 Nest 或 Force.com 肯定已经调整了一些 SSL 配置.

Trying out the Nest API, I got the OAuth flow working without problems, made the first API call (to https://developer-api.nest.com/devices.json), got the 307 redirect as expected, but then my call to the redirect location fails with Remote host closed connection during handshake. I went to the Nest developer event in San Francisco last night, and Lev Stesin told me to post a full log here and mention his name.

Code (Apex, running on Force.com):

public with sharing virtual class NestController {
    public class OAuthResponse {
        public String access_token;
        public String token_type;
        public Integer expires_in;    
        public String refresh_token;
        public String error;
    }

    public static OAuthResponse parse(String json) {
        return (OAuthResponse) System.JSON.deserialize(json, OAuthResponse.class);
    }

    public String accessToken {get; set;}
    public String output {get; set;}

    private String getAll(String accessToken) {
        String url = 'https://developer-api.nest.com/devices.json?auth='+accessToken+'&print=pretty';

        HttpRequest req = new HttpRequest();
        req.setEndpoint(url);
        req.setMethod('GET');
        req.setTimeout(60*1000);

        Http h = new Http();
        String resp;
        HttpResponse res = h.send(req);
        resp = res.getBody();

        if (res.getStatusCode() == 307) {
            url = res.getHeader('Location');
            System.debug('Redirect to: '+url);

            req = new HttpRequest();
            req.setEndpoint(url);
            req.setMethod('GET');
            req.setTimeout(60*1000);

            h = new Http();
            res = h.send(req);
            resp = res.getBody();
        }

        System.debug('Get returns: '+resp);

        return resp;
    }

    public virtual PageReference login() {
        String clientId = '989360fb-9a1f-4d13-929e-0b40111c725a';
        String clientSecret = 'SECRET';
        String sessionId = null;
        String state = 'wow';

        // Get a URL for the page without any query params    
        String url = ApexPages.currentPage().getUrl().split('\\?')[0];

        System.debug('url is '+url);

        // note: connect url in fb application connect setting should be: https://c.na3.visual.force.com/apex/
        // you need the trailing slash even though it bitches about it
        String rediruri = 'https://'+ApexPages.currentPage().getHeaders().get('Host')+url;

        System.debug('rediruri is:'+rediruri);

        String authuri = 'https://home.nest.com/login/oauth2'+
            '?client_id='+clientId+
            '&state='+state;

        // No session
        PageReference pageRef;

        if (ApexPages.currentPage().getParameters().containsKey('error')) {
            // Initial step of OAuth - redirect to OAuth service
            System.debug('Error:' + ApexPages.currentPage().getParameters().get('error'));

            return null;
        }

        if (! ApexPages.currentPage().getParameters().containsKey('code')) {
            // Initial step of OAuth - redirect to OAuth service
            System.debug('Nest OAuth Step 1');

            return new PageReference(authuri);
        }

        // Second step of OAuth - get token from OAuth service
        String code = ApexPages.currentPage().getParameters().get('code');

        System.debug('Nest OAuth Step 2 - code:'+code);

        String tokenuri = 'https://api.home.nest.com/oauth2/access_token';
        String body = 'code='+code+
            '&client_id='+clientId+
            '&client_secret='+clientSecret+
            '&grant_type=authorization_code';
        System.debug('body is:'+body);

        HttpRequest req = new HttpRequest();
        req.setEndpoint(tokenuri);
        req.setMethod('POST');
        req.setTimeout(60*1000);
        req.setBody(body);

        Http h = new Http();
        String resp;
        if (code.equals('TEST')) {
            resp = 'access_token=TEST&expires=3600';
        } else {
            HttpResponse res = h.send(req);
            resp = res.getBody();
        }

        System.debug('FINAL RESP IS:'+resp);

        OAuthResponse oauth = parse(resp);

        if (oauth.error != null) {
            // Error getting token - probably reusing code - start again
            return new PageReference(authuri);            
        }

        accessToken = oauth.access_token;

        output = getAll(accessToken);

        return null;
    }    
}

Initial OAuth Redirect:

https://home.nest.com/login/oauth2?client_id=989360fb-9a1f-4d13-929e-0b40111c725a&state=wow

User authorizes app to access thermostats, Nest redirects back to my app:

https://c.na9.visual.force.com/apex/Nest?state=wow&code=6F3GV6WQ35NGLYB2

I successfully exchange the code for an access token:

POST to https://api.home.nest.com/oauth2/access_token with body

code=6F3GV6WQ35NGLYB2&client_id=989360fb-9a1f-4d13-929e-0b40111c725a&client_secret=SECRET&grant_type=authorization_code

Response:

{"access_token":"c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU","expires_in":315360000}

(I revoked the token from home.nest.com, so it's safe for me to post here!)

So I do a GET on

https://developer-api.nest.com/devices.json?auth=c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU&print=pretty

and receive the expected 307 redirect, with location

https://firebase-apiserver01-tah01-iad01.dapi.production.nest.com:9553/devices.json?auth=c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU&print=pretty

Now, when I GET that URL in my Apex code running on Force.com, it fails with

System.CalloutException: Remote host closed connection during handshake

But if I do the same GET from curl on the command line, it succeeds, returning the expected JSON response.

So it looks like there may be some incompatibility in the SSL handshake. I'll investigate at the Force.com end; it would be good if someone at Nest could check the logs at their end - there should be enough detail here.

EDIT - Here's the output from curl -v to that URL:

$ curl -v 'https://firebase-apiserver01-tah01-iad01.dapi.production.nest.com:9553/devices.json?auth=c.dPHNEweWehQ47tzSm0sf13o8rX1isO9IdEG1HFwoAmeA2FtBLH1fTiksRtN9DGcPAOyEI3VINz2fD3CFma5ozSNbpqUIwGDGc8ixD1etjiIW6TmXN0Rd0p5VzEtk6sDwIe8j10NH1hKDhevX&print=pretty'
* About to connect() to firebase-apiserver01-tah01-iad01.dapi.production.nest.com port 9553 (#0)
*   Trying 54.196.205.148...
* connected
* Connected to firebase-apiserver01-tah01-iad01.dapi.production.nest.com (54.196.205.148) port 9553 (#0)
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Server key exchange (12):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using EDH-RSA-DES-CBC3-SHA
* Server certificate:
*    subject: OU=Domain Control Validated; CN=*.dapi.production.nest.com
*    start date: 2014-05-28 22:31:28 GMT
*    expire date: 2015-05-28 22:31:28 GMT
*    subjectAltName: firebase-apiserver01-tah01-iad01.dapi.production.nest.com matched
*    issuer: C=US; ST=Arizona; L=Scottsdale; O=GoDaddy.com, Inc.; OU=http://certs.godaddy.com/repository/; CN=Go Daddy Secure Certificate Authority - G2
*    SSL certificate verify ok.
> GET /devices.json?auth=c.dPHNEweWehQ47tzSm0sf13o8rX1isO9IdEG1HFwoAmeA2FtBLH1fTiksRtN9DGcPAOyEI3VINz2fD3CFma5ozSNbpqUIwGDGc8ixD1etjiIW6TmXN0Rd0p5VzEtk6sDwIe8j10NH1hKDhevX&print=pretty HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8y zlib/1.2.5
> Host: firebase-apiserver01-tah01-iad01.dapi.production.nest.com:9553
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=UTF-8
< Access-Control-Allow-Origin: *
< Cache-Control: private, no-cache, max-age=0
< Content-Length: 2218
< 
{
  "thermostats" : {
    "pYo-lbpXuVm_DctuTckA_HdEswRgRkbx" : {
      "locale" : "en-US",
      "temperature_scale" : "F",
      "is_using_emergency_heat" : false,
      "has_fan" : true,
      "software_version" : "4.2.3",
      "has_leaf" : true,
      "device_id" : "pYo-lbpXuVm_DctuTckA_HdEswRgRkbx",
      "name" : "Downstairs",
      "can_heat" : true,
      "can_cool" : true,
      "hvac_mode" : "off",
      "target_temperature_c" : 24.5,
      "target_temperature_f" : 76,
      "target_temperature_high_c" : 24.0,
      "target_temperature_high_f" : 75,
      "target_temperature_low_c" : 20.0,
      "target_temperature_low_f" : 68,
      "ambient_temperature_c" : 25.0,
      "ambient_temperature_f" : 78,
      "away_temperature_high_c" : 24.0,
      "away_temperature_high_f" : 76,
      "away_temperature_low_c" : 15.5,
      "away_temperature_low_f" : 60,
      "structure_id" : "HqSZlH08Jc3CtBNIS4OLPdiWLpcfW5o6dP2DvSox7hcGVpBGOH9cQA",
      "fan_timer_active" : false,
      "name_long" : "Downstairs Thermostat",
      "is_online" : true,
      "last_connection" : "2014-06-26T23:16:24.341Z"
    },
    "pYo-lbpXuVncrx7IdGTWyXdEswRgRkbx" : {
      "locale" : "en-US",
      "temperature_scale" : "F",
      "is_using_emergency_heat" : false,
      "has_fan" : true,
      "software_version" : "4.2.3",
      "has_leaf" : true,
      "device_id" : "pYo-lbpXuVncrx7IdGTWyXdEswRgRkbx",
      "name" : "Upstairs",
      "can_heat" : true,
      "can_cool" : true,
      "hvac_mode" : "off",
      "target_temperature_c" : 24.0,
      "target_temperature_f" : 76,
      "target_temperature_high_c" : 24.0,
      "target_temperature_high_f" : 75,
      "target_temperature_low_c" : 20.0,
      "target_temperature_low_f" : 68,
      "ambient_temperature_c" : 25.0,
      "ambient_temperature_f" : 78,
      "away_temperature_high_c" : 24.0,
      "away_temperature_high_f" : 76,
      "away_temperature_low_c" : 15.5,
      "away_temperature_low_f" : 60,
      "structure_id" : "HqSZlH08Jc3CtBNIS4OLPdiWLpcfW5o6dP2DvSox7hcGVpBGOH9cQA",
      "fan_timer_active" : false,
      "name_long" : "Upstairs Thermostat",
      "is_online" : true,
      "last_connection" : "2014-06-26T23:16:27.849Z"
    }
  }
* Connection #0 to host firebase-apiserver01-tah01-iad01.dapi.production.nest.com left intact
}* Closing connection #0
* SSLv3, TLS alert, Client hello (1):

解决方案

The same callout from Salesforce works just fine now. I guess Nest or Force.com must have tweaked some SSL config.

这篇关于远程主机在与 Nest API 握手期间关闭连接的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆