尝试使用Nest API时,我得到了OAuth流程正常工作,进行了第一次API调用(至https://developer-api.nest.com/devices.json
),按预期得到了307重定向,但随后调用了重定向位置以Remote host closed connection during handshake
失败。我昨晚去了旧金山的Nest开发者大会,Lev Stesin告诉我在这里发布完整日志并提及他的名字。远程主机在与Nest API握手期间关闭连接
代码(APEX,在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;
}
}
初始OAuth的重定向:
https://home.nest.com/login/oauth2?client_id=989360fb-9a1f-4d13-929e-0b40111c725a&state=wow
用户授权的应用程序访问的自动调温器,巢重定向回我的应用程序:
https://c.na9.visual.force.com/apex/Nest?state=wow&code=6F3GV6WQ35NGLYB2
我成功交换访问令牌的代码:
POST到https://api.home.nest.com/oauth2/access_token
与身体
code=6F3GV6WQ35NGLYB2&client_id=989360fb-9a1f-4d13-929e-0b40111c725a&client_secret=SECRET&grant_type=authorization_code
响应:(!我撤销令牌从home.nest.com
,因此它是安全的,我在这里发表)
{"access_token":"c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU","expires_in":315360000}
所以我做了GET on
https://developer-api.nest.com/devices.json?auth=c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU&print=pretty
和收到预期的307重定向,与位置
https://firebase-apiserver01-tah01-iad01.dapi.production.nest.com:9553/devices.json?auth=c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU&print=pretty
现在,当我得到的URL在Force.com上运行我的Apex代码,它失败
System.CalloutException: Remote host closed connection during handshake
但如果我这样做在命令行上通过curl进行GET,它会成功,并返回预期的JSON响应。
因此,看起来SSL握手中可能存在一些不兼容问题。我将在Force.com结束时进行调查;如果Nest的某个人可以在他们的最后检查日志会很好 - 这里应该有足够的细节。
编辑 - 下面是从卷曲-v输出到网址:
$ 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):
从详细卷曲请求到该URL的输出是什么? curl -v此外,你可能想确保那些访问令牌无效:) –
mimming
添加(成功)curl -v输出到我的问题,并撤销访问令牌:) – metadaddy