5

我正在使用一个不错的 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>

问题

  1. 原始来源出现(也许?我的 php 生锈了......)包含一个在将请求转发到 Twitter 时命名的标头Expect,据我所知,它的值是空的。如果我包含这个标题,我会得到这个响应:417 Expectation Failed.
  2. 如果我改为省略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 设置可能存在的问题。

4

2 回答 2

1

我不会接受这个作为答案,因为问题是关于移植代码,这肯定不能解决我在转换代码时遇到的问题。但我认为这对处于同样困境中的其他任何人都有用,所以我会在这里记录我所做的事情以及原因。


今天我记得 Heroku 支持 PHP,所以我在那里设置了一个原始 PHP 项目的实例,它运行良好。我仍然对我的 CFML 端口无法正常工作感到困惑,但至少我可以继续我的项目,因为我知道我将能够使用我的 Heroku 托管代理,并且它的正常运行时间比一些随机网站更安全。

归根结底,虽然我对主机有一些(但不完全)信任,不会记录和恶意使用我的 Twitter 凭据,但将它们放在中间让我有点不安。不过,在 github 存储库中发布的源代码看起来不错——只要 PHP 有效,我对它没有任何反对意见!我的问题的根源在于 99% 的正常运行时间无法控制,1% 的问题在于安全性。

它也可以无限期地在单个免费的 Heroku dyno 上运行,这并没有什么坏处。<3 赫罗库

如果有人遇到这个并想做同样的事情,这里是我遵循的步骤(命令行)。注意,您必须首先安装Heroku 工具带

$ git clone https://github.com/mynetx/codebird-cors-proxy.git twitter-cors-proxy-php
Cloning into 'twitter-cors-proxy-php'...
remote: Counting objects: 52, done.
remote: Compressing objects: 100% (34/34), done.
remote: Total 52 (delta 23), reused 47 (delta 18)
Unpacking objects: 100% (52/52), done.

$ cd twitter-cors-proxy-php
$ rm -rf .git
$ ls -al
total 88
drwxr-xr-x   7 adam  staff    238 Aug 15 11:14 .
drwxr-xr-x  57 adam  staff   1938 Aug 15 11:11 ..
-rw-r--r--   1 adam  staff    250 Aug 15 11:11 CHANGELOG
-rw-r--r--   1 adam  staff  35147 Aug 15 11:11 LICENSE
-rw-r--r--   1 adam  staff   1531 Aug 15 11:11 README.md
drwxr-xr-x   5 adam  staff    170 Aug 15 11:11 src

$ rm -f CHANGELOG LICENSE README.md
$ mv src/* ./
$ rm -rf src
$ mv codebird-cors-proxy.php index.php
$ git init
Initialized empty Git repository in /Users/adam/DEV/twitter-cors-proxy-php/.git/

$ git add .
$ git st
## Initial commit on master
A  cacert.pem
A  index.php

$ git commit -am"initial import from codebird"
[master (root-commit) 4196e98] initial import from codebird
 2 files changed, 4115 insertions(+)
 create mode 100644 cacert.pem
 create mode 100644 index.php

$ heroku apps:create twitter-cors-proxy
Creating twitter-cors-proxy... done, stack is cedar
http://twitter-cors-proxy.herokuapp.com/ | git@heroku.com:twitter-cors-proxy.git
Git remote heroku added

$ git push heroku master
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 134.24 KiB, done.
Total 4 (delta 0), reused 0 (delta 0)

-----> PHP app detected
-----> Bundling mcrypt version 2.5.8
-----> Bundling Apache version 2.2.25
-----> Bundling PHP version 5.3.27
-----> Discovering process types
       Procfile declares types -> (none)
       Default types for PHP   -> web

-----> Compiled slug size: 22.3MB
-----> Launching... done, v3
       http://twitter-cors-proxy.herokuapp.com deployed to Heroku

To git@heroku.com:twitter-cors-proxy.git
 * [new branch]      master -> master

然后我将我的新代理设置为 Codebird:

var cb = new Codebird;
cb.setProxy('https://twitter-cors-proxy.herokuapp.com/index.php');

...世界上一切都很好!

于 2013-08-15T15:52:20.880 回答
0

不确定您对 twitter API 进行了多少研究,但他们已经放弃了对公共请求的支持,现在所有内容都必须使用已注册的应用程序进行授权。你可以在这里注册一个dev.Twitter

我可以在这里看到您想要实现的目标,我有一个基本的 CFC,它演示了对 twitter API 的有效 http 调用,添加您的密钥和秘密,您可以将 twitterEndpoint 变量更改为不同的选项并查看不同的响应。- 希望这可以帮助。

基本 Twitter CFC - 请注意,这仅在 OpenBD CFML 引擎上进行了测试

于 2013-08-15T08:15:22.363 回答