Android RESTful/OAuth 上传文件到 Dropbox
Android 将文件写入云端。
介绍
我的一些应用程序会在本地生成文件,虽然包含了蓝牙/Wi-Fi 传输、电子邮件和类似的文件传输方法,但用户越来越多地使用云存储,因此将文件写入云的选项势在必行。我们可以使用云提供商的 RESTful API/服务来完成此操作。
RESTFul 服务由于其更简单的风格,特别是无状态模式,正日益成为当今主要的 Web 服务。不同平台的客户端可以通过这种架构以极低的复杂性访问资源。客户端需要向服务器进行身份验证,而 OAuth 是首选方法,因为它是一个开放且直接的标准,并具有不错的安全性。在本文中,我们将通过一个 Android 实现,介绍如何使用 Dropbox 的 RESTFul API(该 API 使用 OAuth 身份验证)上传文件到 Dropbox。
背景
Google、Facebook、Twitter 等大多数大公司都提供带有 OAuth 身份验证的 RESTful 服务。
OAuth 通过以下几个步骤执行身份验证:
- 请求服务器授权以允许用户访问其帐户。为此,客户端(例如我们的代码)需要一对密钥/秘密词凭据,这些凭据可在请求后从提供商处获得。
- 成功后,我们再请求用户授权,这通常需要用户登录 Dropbox 帐户。
- 成功后,将提供一组新的密钥/秘密词凭据,有了这一组凭据,就可以使用这对凭据签署每个请求来访问所有可用的 API/方法,在我们的情况下,我们可以上传文件、读取用户信息等。
这些 RESTful API 不过是通过 GET 或 POST HTTP 请求发送的 URL。OAUth 身份验证要求使用一组预定义参数调用这些 URL,这些参数包括时间戳、密钥/秘密词对以及加密类型(SHA1 等),此时间戳可防止其他人窃取 URL 并尝试再次调用它,因为稍后的时间戳将不同,从而提供良好的安全层。
语法很简单:
<URL from the RESTful API>?<OAuth args ...>&<Dropbox args...>
例如,要启动 Dropbox OAuth,我们需要调用下面的 URL,不带任何 Dropbox 参数,仅常规的 OAuth:
https://api.dropbox.com/1/oauth/request_token?<OAuth args ..>
oauth_consumer_key=<key> provided by Dropbox
oauth_token=<secret_word> provided by Dropbox
oauth_nonce=<a number used once> a random string that is meant to uniquely identify each signed request.
oauth_timestamp=<when the request is sent>
oauth_signature_method=HMAC-SHA1 (it could be any other standard encryption used to generate the nonce)
oauth_version
nonce 和时间戳的组合减轻了服务器的负担,并允许服务提供商仅在有限时间内保留 nonce 值。可以获取特定的签名规范并在我们的代码中实现,但已经有许多现成的库,因此无需在此重新发明轮子,我们将使用 Signpost 库来帮助我们完成 OAuth 身份验证步骤。
最后,像 Dropbox 这样的 RESTful 提供商也提供特定于平台的 SDK(例如,适用于 Android、iPhone 等),但这些不过是特定平台实现的、基于基本 RESTful API 的包装器。
1. 代码实现
这里的受众应该已经精通 Java/Android,所以我们将跳过基础知识,直入主题。我们的基本屏幕如下所示。
1.1 先决条件:Manifest 和 Jar 库
Manifest 需要包含一个允许我们的应用程序访问网络的节。此外,我们的应用程序需要一个 intent-filter,以便我们的浏览器能够正常启动,并且还需要定义一个虚拟 schema/URL,以便在我们从浏览器返回到我们的应用程序时可以捕获。
<activity android:name=".main"
....
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myrest" android:host="myStep.com" />
</intent-filter>
</activity>
...
<uses-permission android:name="android.permission.INTERNET">
</uses-permission>
我们需要下载 Signpost jar 库并将其添加到我们的项目中,只需将其放到 libs 文件夹并添加即可。
我们还需要引用 Dropbox RESTful APIs。
1.2 获取签署 API 请求的凭据
正确调用服务器以启动过程,SignPost 方法的调用已突出显示。
private static CommonsHttpOAuthConsumer consumer;
private static CommonsHttpOAuthProvider provider ;
...
// need to request this
String DropB_key="<request this From DropBox>";
String DropB_secret="<request this From DropBox>";
// from dropB API
String request_url = "https://api.dropbox.com/1/oauth/request_token";
String authorization_url = "https://www.dropbox.com/1/oauth/authorize";
String access_url ="https://api.dropbox.com/1/oauth/access_token";
// to come back to our App after user authorizes
String callBack_url ="myRest://myStep.com";
...
// ask authorization to request the user
consumer = new CommonsHttpOAuthConsumer(
DropB_key, DropB_secret);
provider = new CommonsHttpOAuthProvider(
request_url, access_url, authorization_url);
// we r good not let's ask the user's permission
try
{
String authURL = provider.retrieveRequestToken(
consumer, callBack_url);
Intent intent2 = new Intent(Intent.ACTION_VIEW);
intent2.setData( Uri.parse(authURL) );
startActivity(intent2);
}catch (Exception e)
{}
正如你所见,**突出显示的** Signpost 库方法使得启动身份验证过程非常简单,并且处理了所有繁琐的细节。当我们请求用户授权时,我们将启动一个浏览器返回到我们的应用程序,callBack_url 设置为我们可以在 **onResume** 事件中捕获的虚拟 schema 和 URL,并完成整个过程。
.. @Override
public void onResume()
{
// upon confirmation the call back will come to here.
super.onResume();
Uri uri = this.getIntent().getData();
...
// so it is coming back from the auth, catch it when it comes to our dummy URL
if( uri != null)
{
...
// now let's do the real stuff
if( uri.getHost().equals("myStep.com"))
{
//grab the tokens
String parms = uri.getEncodedQuery();
// perfomr the write thru
try {
// grab the token/secret items to carry on
String verifier = uri.getQueryParameter(OAuth.OAUTH_VERIFIER);
provider.retrieveAccessToken(consumer, verifier);
String ACCESS_KEY = consumer.getToken();
String ACCESS_SECRET = consumer.getTokenSecret();
Log.d("OAuth Dropbox", ACCESS_KEY);
Log.d("OAuth Dropbox", ACCESS_SECRET);
...
我们已经获得了签署可用 RESTful API 请求的凭据,现在可以开始工作了。
1.3 请求用户信息并上传文件到 Dropbox
获取凭据后,其余的操作就很简单了,我们获取适当的 API(来自 Dropbox 的 URL)并根据 API 的指定执行 GET 或 POST 操作。Dropbox 返回的值要么是格式化的 URL 参数,要么是 JSON 集。
每个请求都必须使用这些凭据进行签名。
下面我们请求 Dropbox 返回当前用户信息。
...
consumer.setTokenWithSecret(ACCESS_KEY, ACCESS_SECRET);
String uRL_file_list_req="https://api.dropbox.com/1/account/info";
HttpClient httpclient = new DefaultHttpClient();
HttpGet request = new HttpGet(uRL_file_list_req );
consumer.sign(request);
ResponseHandler<String> handler = new BasicResponseHandler();
try {
result = httpclient.execute(request, handler);
} catch (ClientProtocolException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "Protocol failure uploading the file",
Toast.LENGTH_SHORT).show();
return;
} catch (IOException e) {
e.printStackTrace();
Log.e("******",Log.getStackTraceString(e));
Toast.makeText(getApplicationContext(), "IO failure uploading the file",
Toast.LENGTH_SHORT).show();
return;
}
JSONObject json_data = new JSONObject(result);
JSONArray nameArray = json_data.names();
JSONArray valArray = json_data.toJSONArray(nameArray);
...
我们上传一个文本文件,其内容来自上面的简单 Android UI,更改适当的 MIME 类型并正确编码内容就足够了,以便上传任何其他文件类型,如图像。
//
// last but not least let's upload a file
// default parm is overwritten so we keep it simple
String textStr = myContents.getText() + "";
String fileName = fName.getText() + "";
uRL_file_list_req="https://api-content.dropbox.com/1/files_put/" +
"sandbox/" + fileName ; //+ "?param=val" ;
consumer.setTokenWithSecret(ACCESS_KEY, ACCESS_SECRET);
HttpPost request3 = new HttpPost(uRL_file_list_req );
request3.addHeader("Content-Type", "text/plain");
request3.setEntity(new StringEntity(textStr));
...
consumer.sign(request3);
result="";
try {
result = httpclient.execute(request3, handler);
} catch (ClientProtocolException e) {
e.printStackTrace();
Log.e("$$$$$$$",Log.getStackTraceString(e));
Toast.makeText(getApplicationContext(),
"Protocol failure uploading the file",
Toast.LENGTH_SHORT).show();
return;
} catch (IOException e)
{
e.printStackTrace();
Log.e("******",Log.getStackTraceString(e));
Toast.makeText(getApplicationContext(),
"IO failure uploading the file",
Toast.LENGTH_SHORT).show();
return;
}
// parse out the data
json_data = new JSONObject(result);
nameArray = json_data.names();
valArray = json_data.toJSONArray(nameArray);
Toast.makeText(getApplicationContext(), "Succes!! - " +
valArray.getString(7) + " " + valArray.getString(10),
Toast.LENGTH_SHORT).show();
httpclient.getConnectionManager().shutdown();
...
2. 注意事项
这里唯一的问题是 Dropbox 的文档参考有点模糊,当你需要进行文件传输操作时,URL 的形式是:
https://api-content.dropbox.com/1/files/<root>/<path>
<root> 是一个字符串,其值可以是 *dropbox* 或 *sandbox*。当你从 Dropbox 请求初始凭据对时,它默认你处于文件夹模式,root 为 sandbox。之后,你可以请求将状态更改为 production,这样用户就可以使用你的应用程序。它将在 Apps 下创建一个以你的应用程序命名的文件夹。
<path> 是文件的实际路径,相对于应用程序文件夹。例如,要获取位于 Apps/myCoolApp/ 文件夹中的文件 *test.txt*,上述内容应为:
https://api-content.dropbox.com/1/files/sandbox/text.txt
3. 最终想法
正如你所见,这非常简单,只是需要理解 Dropbox API 的规范。拥有凭据密钥并将其存储为应用程序中的本地首选项,可以在不进行任何其他用户与 Dropbox 交互的情况下,允许你的应用程序进一步查询或操作 Dropbox 文件。
包含的示例代码包含了一些让它在 Android 上工作的额外细节,因为我们调用的是 RESTful API 而不是 SDK,所以很容易将其移植到其他平台。显然,示例代码只是一个用于入门的简单示例。