我正在使用一个不错的 Twitter API 包装器:codebird-js;它包含一个代理,以防您需要发出 CORS AJAX 请求(我愿意),因为 Twitter 的 API 不允许 CORS。
旁白: Twitter 可以接受这种代理吗?我不得不假设,因为 Codebird 被列在他们推荐的库中。
代理通过开发人员的服务器之一,这已经足够好了,但我注意到它偶尔会关闭几个小时,有时是一整天。一旦我的应用程序投入生产,这将是不可接受的,因此我需要自托管代理以获得更多控制权。
幸运的是,它们也提供了代理的来源。不幸的是,PHP 不是一种选择。所以我试图将它移植到 CFML,这是我目前最好的选择(我也可以考虑 Node.js 和 Ruby,虽然我对两者都不太熟悉,这就是我现在选择 CFML 的原因)
基本上它归结为我正在尝试将此脚本移植到 CFML。以下是我到目前为止所拥有的,但我遇到的问题将在代码下方进行描述。
<cfscript>
try{
header(name="Access-Control-Allow-Origin", value="*");
header(name="Access-Control-Allow-Headers", value="Origin, X-Authorization");
header(name="Access-Control-Allow-Methods", value="POST, GET, OPTIONS");
method = cgi.request_method;
if (method == 'OPTIONS'){
abort;
}
path = 'https://api.twitter.com' & cgi.path_info;
headers = [{name="Expect", value=""}];
req_headers = getHTTPRequestData().headers;
req_body = getHTTPRequestData().content;
if (isBinary(req_body)){
req_body = charsetEncode(req_body, "UTF-8");
}
if (structKeyExists(req_headers, 'X-Authorization')){
arrayAppend(headers, { name='Authorization', value=req_headers['X-Authorization'] });
}
response = http_wrapper(method, path, headers, req_body);
code = val( response.statusCode );
msg = trim( replace(response.statusCode, code, '') );
twitter_headers = listToArray(structKeyList( response.responseHeader ));
for (i = 1; i <= arrayLen(twitter_headers); i++){
if (twitter_headers[i] == 'set-cookie'){ continue; }
header(name=twitter_headers[i], value=response.responseHeader[twitter_headers[i]]);
}
header(statusCode=code, statusText=msg);
respond(response.filecontent);
}catch(any e){
application.bugService.notifyService(
message = "Error in Twitter Proxy"
,severityCode = "ERROR"
,exception = e
);
header(statusCode=500,statusText="Proxy Error");
writeDump(var={error=e,twitter_response=response}, format='text');
}
</cfscript>
<cffunction name="http_wrapper">
<cfargument name="method" />
<cfargument name="path" />
<cfargument name="headers" />
<cfargument name="body" />
<cfset var local = {} />
<cfhttp method="#arguments.method#" url="#arguments.path#" result="local.result">
<cfloop from="1" to="#arrayLen(arguments.headers)#" index="local.i">
<cfhttpparam type="header" name="#arguments.headers[i].name#" value="#arguments.headers[i].value#" />
</cfloop>
<cfhttpparam type="body" value="#arguments.body#" />
</cfhttp>
<cfreturn local.result />
</cffunction>
<cffunction name="header">
<cfheader attributeCollection="#arguments#" />
</cffunction>
<cffunction name="respond">
<cfargument name="body" />
<cfcontent reset="true" /><cfoutput>#body#</cfoutput><cfabort/>
</cffunction>
问题
- 原始来源出现(也许?我的 php 生锈了......)包含一个在将请求转发到 Twitter 时命名的标头
Expect
,据我所知,它的值是空的。如果我包含这个标题,我会得到这个响应:417 Expectation Failed
. - 如果我改为省略
Expect
标题,我将收到代理请求的 401 响应。
需要注意的是,我使用的客户端代码已经针对提供的 PHP 代理进行了测试并且工作正常。为了使用我自己的代理,我设置了我的 Codebird 实例,如下所示:
var cb = new Codebird;
cb.setProxy('https://mydomain.com/api/v1/proxy/twitter.cfm/');
cb.setConsumerKey(key, secret);
网络请求在 chrome 调试工具中如下所示:
请求 URL: https ://mydomain.com/api/v1/proxy/twitter.cfm/oauth/request_token
请求方法: POST
状态代码: 401 未经授权的
请求标头
接受: */*
接受编码: gzip、deflate、sdch
接受-Language: en-US,en;q=0.8
Connection: keep-alive
Content-Length: 61
Content-Type: application/x-www-form-urlencoded
Host: mydomain.com
Origin: null
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
X-授权:OAuth oauth_consumer_key=".......", oauth_nonce="PqK4KPCc", oauth_signature="B%2BQ.......b08%3D", oauth_signature_method=" HMAC-SHA1", oauth_timestamp="1376507615", oauth_version="1.0"
表单数据:
oauth_callback: http ://mydomain.com/twitter-login
您可以看到请求中包含 X-Authorization 标头,据我所知,它是完整且正确的,在第一个代码示例中您可以看到该请求标头随后作为 Authorization 标头转发到 Twitter .
我不知道为什么我会收到 401 回复。文档似乎没有表明 Expect 标头是必需的,所以我假设我可以忽略它。(事实上,据我公认的生疏的 PHP 知识,它实际上并没有在 PHP 版本中发送......)
但如果是这样的话,我为什么要退回 401 呢?其他一切对我来说似乎都是正确的......
更新:CURL 选项
根据亚当的评论,我忘记指出我也有意忽略了正在设置的 CURL 选项(我认为 CFHTTP 会为我处理所有这些)。我现在将仔细检查并记录每一个正在使用的内容,并确保 CFHTTP 涵盖了基础:
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
TRUE
将传输作为 curl_exec() 的返回值的字符串返回,而不是直接输出。
这是一篇写得不好的文档(CF 偶尔也会犯一些错误),但似乎表明设置此选项后,将返回结果而不是附加到响应缓冲区。查看; 默认情况下,CFHTTP 会这样做。
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
TRUE
跟随服务器作为 HTTP 标头的一部分发送的任何“Location:”标头(注意这是递归的,PHP 将跟随它发送的尽可能多的“Location:”标头,除非设置了 CURLOPT_MAXREDIRS)。
等效的 CFHTTP 设置为redirect="true"
(默认为 true)。那么,这是不匹配的,因为 PHP 版本说不遵循重定向。我会记住这一点,但严重怀疑这会导致我的 401。
curl_setopt($ch, CURLOPT_HEADER, 1);
TRUE
在输出中包含标题。
检查,CFHTTP 在响应中包含标头。
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
FALSE
阻止 cURL 验证对等方的证书。可以使用该选项指定要验证的备用证书,也可以使用该CURLOPT_CAINFO
选项指定证书目录CURLOPT_CAPATH
。
CFHTTP 确实验证了 SSL 证书,正如我在评论中指出的那样,似乎已经导入了必要的证书。
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
1 检查 SSL 对等证书中是否存在通用名称。2 检查公用名是否存在,并验证它是否与提供的主机名匹配。在生产环境中,此选项的值应保持为 2(默认值)。
我不肯定这是在检查什么,但我不相信 CFHTTP 有任何相关设置,所以我现在假设这个检查正在完成(或者不是我的问题的促成因素)。
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . '/cacert.pem');
保存一个或多个证书以验证对等点的文件的名称。这仅在与 结合使用时才有意义
CURLOPT_SSL_VERIFYPEER
。
正如我之前和评论中提到的,代理源提供的 PEM 文件已经包含在我的配置中并正在验证中。
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
要设置的 HTTP 标头字段数组,格式为
array('Content-type: text/plain', 'Content-length: 100')
检查:我正在将标题传递给 Twitter。
curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
TRUE
跟踪句柄的请求字符串。
什么?根据设置名称,我假设这意味着在输出中包含标头(检查,CFHTTP 这样做),但文档没有意义。
因此,这似乎排除了 curl 设置可能存在的问题。