改造 - 传递 POST 参数数据和 headers
Retrofit - Passing POST parameter data and headers
我正在尝试使用 retrofit 调用 Kraken API。我有一个工作的 AsyncHttpClient
版本,我想转换成一个改造版本,我在传递 POST 参数时遇到了一些问题。
如文档中所述,它需要:
- 2 HTTP headers :
API-Key
和 API-Sign
.
- 一个POST数据:
nonce
- 2 个我正在调用的端点的输入参数:
start
和 ofs
AsyncHttpClient 版本(有效):
String start = String.valueOf(cal.getTimeInMillis() / 1000);
String key = properties.getProperty("KRAKEN_API_PUBLIC_KEY");
String nonce = String.valueOf(System.currentTimeMillis());
String path = "/0/private/TradesHistory";
RequestParams params = new RequestParams();
params.add("nonce", nonce);
params.add("start", start);
params.add("ofs", String.valueOf(offset));
String sign = calculateSignature(path, nonce, params.toString());
AsyncHttpClient client = new AsyncHttpClient();
client.addHeader("API-Key", key);
client.addHeader("API-Sign", sign);
client.post("https://api.kraken.com"+ path, params, new JsonHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
// works
}
});
Retrofit版本(EAPI:Invalid key
响应):
String start = String.valueOf(cal.getTimeInMillis() / 1000);
String key = properties.getProperty("KRAKEN_API_PUBLIC_KEY");
String nonce = String.valueOf(System.currentTimeMillis());
String path = "/0/private/TradesHistory";
RequestParams params = new RequestParams();
params.add("nonce", nonce);
params.add("start", start);
params.add("ofs", String.valueOf(offset));
String sign = calculateSignature(path, nonce, params.toString());
KrakenService krakenService = KrakenService.retrofit.create(KrakenService.class);
Call<KrakenTrades> call = krakenService.getTradeHistory(key, sign, nonce, start, String.valueOf(offset));
call.enqueue(new Callback<KrakenTrades>() {
@Override
public void onResponse(@Nullable Call<KrakenTrades> call, @Nullable Response<KrakenTrades> response) {
// EAPI:Invalid key
}
});
服务:
public interface KrakenService {
@FormUrlEncoded
@POST("private/TradesHistory")
Call<KrakenTrades> getTradeHistory(
@Header("API-Key") String apiKey,
@Header("API-Sign") String apiSign,
@Field("nonce") String nonce,
@Field("start") String start,
@Field("ofs") String ofs);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.kraken.com/0/")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
只是调用不同,我错过了什么吗?我试过使用 @query
、@Body
、@FieldMap
、@HeaderMap
等,但无法成功。
Edit:我刚刚尝试使用其他 API,例如 Poloniex,但遇到了同样的问题(无效密钥),但它仍然适用于基本的 HTTPClient .
如果你对其他API开放,我推荐一下mesibo,使用起来会简单很多,
mesibo.Http http = new mesibo.Http();
Bundle b = new Bundle();
b.putString("token", "some_token");
b.putLong("mid", mid);
http.url = “https://example.com”;
http.postBundle = b;
http.other = myObject; // callback data
http.onMainThread = true; // invoke listener in main thread
http.listener = new mesibo.HttpListener() {
@Override
public boolean mesibo_onHttpProgress(mesibo.Http http, int state, int percent) {
if(100 == percent && mesibo.Http.STATE_DOWNLOAD == state) {
// download complete
}
Return true; // return false to cancel
}
};
if(http.execute()) {
}
我设法让它发挥作用,但我不得不承认我不明白这一点...
我激活了 OkHttp
日志以查看请求。这是我首先得到的(这些是假钥匙不要浪费你的时间-_-):
D/OkHttp: --> POST https://api.kraken.com/0/private/TradesHistory http/1.1
D/OkHttp: Content-Type: application/x-www-form-urlencoded
D/OkHttp: Content-Length: 42
D/OkHttp: API-Sign: /Eu6Sth0oa13Tr/ofme07TF4ct+TercW7uU9PxBEQhcpYTflC2c/jEW1BZuamBXco0jlgOzWt8RMh0o6kAE5SA==
D/OkHttp: API-Key: XnmS2gW2Sr1xR/vnB0ivJuHABdXUnW4bsMTOBMREOlz8xYDh00J+D9i4
D/OkHttp: start=1481587200&ofs=0&nonce=1511301862261
D/OkHttp: --> END POST (42-byte body)
D/OkHttp: <-- 200 https://api.kraken.com/0/private/TradesHistory (2049ms)
D/OkHttp: date: Tue, 21 Nov 2017 22:04:27 GMT
D/OkHttp: content-type: application/json; charset=utf-8
D/OkHttp: set-cookie: __cfduid=dc9e29889eeda633314ca5aaad10ce8291511301865; expires=Wed, 21-Nov-18 22:04:25 GMT; path=/; domain=.kraken.com; HttpOnly
D/OkHttp: vary: Accept-Encoding
D/OkHttp: server: cloudflare-nginx
D/OkHttp: cf-ray: 3c16f1d348503e80-ZRH
D/OkHttp: {"error":["EAPI:Invalid key"]}
D/OkHttp: <-- END HTTP (30-byte body)
现在,我没有使用 params.toString()
作为参数来计算签名,而是手动传递了连接值 ("start=" + start + "&ofs=" + offset +"&nonce=" + nonce
),这就是我得到的结果:
D/OkHttp: --> POST https://api.kraken.com/0/private/TradesHistory http/1.1
D/OkHttp: Content-Type: application/x-www-form-urlencoded
D/OkHttp: Content-Length: 42
D/OkHttp: API-Sign: DuxyUuHbj4V9WrOpaoBM6Rx3mZluoqJdkg3xBLgc0A/lVotq3WL19VErctz+ugTxi0eRpCI6oBnl95+Lmh9WmQ==
D/OkHttp: API-Key: XnmS2gW2Sr1xR/vnB0ivJuHABdXUnW4bsMTOBMREOlz8xYDh00J+D9i4
D/OkHttp: start=1481587200&ofs=0&nonce=1511302135971
D/OkHttp: --> END POST (42-byte body)
D/OkHttp: <-- 200 https://api.kraken.com/0/private/TradesHistory (1340ms)
D/OkHttp: date: Tue, 21 Nov 2017 22:09:04 GMT
D/OkHttp: content-type: application/json; charset=utf-8
D/OkHttp: set-cookie: __cfduid=d7916fbb113ed6f83b9382ea0a57240731511302143; expires=Wed, 21-Nov-18 22:09:03 GMT; path=/; domain=.kraken.com; HttpOnly
D/OkHttp: vary: Accept-Encoding
D/OkHttp: server: cloudflare-nginx
D/OkHttp: cf-ray: 3c16f89b4e273e62-ZRH
D/OkHttp: {"error":[],"result":{"trades":{...}}}
D/OkHttp: <-- END HTTP (11944-byte body)
看到发送数据的不同了吗?不?没有...
但它有效,我不再收到 invalid key
响应。
另一个奇怪的事情是改变参数的顺序会使它退回到invalid key
响应:
String parameters = "start=" + start + "&ofs=" + offset +"&nonce=" + nonce; //works
String parameters = "start=" + start + "&nonce=" + nonce + "&ofs=" + offset; //doesn't work
我无法解释这个...可能是编码问题?
EDIT : 好的明白了,似乎参数在请求正文中排序。尽管服务器不关心 HTTP 参数的顺序,但 HMAC 关心(当然 param1=value1¶m2=value2
会生成与 param2=value2¶m1=value1
不同的 HMAC)。
所以我必须在用于计算 HMAC 的字符串中使用与在请求正文中完全相同的顺序(我硬编码了从 OkHttp 日志中获得的顺序的值,但也许最好的方法是提取它来自请求正文,甚至更好地规范化请求正文)。
我正在尝试使用 retrofit 调用 Kraken API。我有一个工作的 AsyncHttpClient
版本,我想转换成一个改造版本,我在传递 POST 参数时遇到了一些问题。
如文档中所述,它需要:
- 2 HTTP headers :
API-Key
和API-Sign
. - 一个POST数据:
nonce
- 2 个我正在调用的端点的输入参数:
start
和ofs
AsyncHttpClient 版本(有效):
String start = String.valueOf(cal.getTimeInMillis() / 1000);
String key = properties.getProperty("KRAKEN_API_PUBLIC_KEY");
String nonce = String.valueOf(System.currentTimeMillis());
String path = "/0/private/TradesHistory";
RequestParams params = new RequestParams();
params.add("nonce", nonce);
params.add("start", start);
params.add("ofs", String.valueOf(offset));
String sign = calculateSignature(path, nonce, params.toString());
AsyncHttpClient client = new AsyncHttpClient();
client.addHeader("API-Key", key);
client.addHeader("API-Sign", sign);
client.post("https://api.kraken.com"+ path, params, new JsonHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
// works
}
});
Retrofit版本(EAPI:Invalid key
响应):
String start = String.valueOf(cal.getTimeInMillis() / 1000);
String key = properties.getProperty("KRAKEN_API_PUBLIC_KEY");
String nonce = String.valueOf(System.currentTimeMillis());
String path = "/0/private/TradesHistory";
RequestParams params = new RequestParams();
params.add("nonce", nonce);
params.add("start", start);
params.add("ofs", String.valueOf(offset));
String sign = calculateSignature(path, nonce, params.toString());
KrakenService krakenService = KrakenService.retrofit.create(KrakenService.class);
Call<KrakenTrades> call = krakenService.getTradeHistory(key, sign, nonce, start, String.valueOf(offset));
call.enqueue(new Callback<KrakenTrades>() {
@Override
public void onResponse(@Nullable Call<KrakenTrades> call, @Nullable Response<KrakenTrades> response) {
// EAPI:Invalid key
}
});
服务:
public interface KrakenService {
@FormUrlEncoded
@POST("private/TradesHistory")
Call<KrakenTrades> getTradeHistory(
@Header("API-Key") String apiKey,
@Header("API-Sign") String apiSign,
@Field("nonce") String nonce,
@Field("start") String start,
@Field("ofs") String ofs);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.kraken.com/0/")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
只是调用不同,我错过了什么吗?我试过使用 @query
、@Body
、@FieldMap
、@HeaderMap
等,但无法成功。
Edit:我刚刚尝试使用其他 API,例如 Poloniex,但遇到了同样的问题(无效密钥),但它仍然适用于基本的 HTTPClient .
如果你对其他API开放,我推荐一下mesibo,使用起来会简单很多,
mesibo.Http http = new mesibo.Http();
Bundle b = new Bundle();
b.putString("token", "some_token");
b.putLong("mid", mid);
http.url = “https://example.com”;
http.postBundle = b;
http.other = myObject; // callback data
http.onMainThread = true; // invoke listener in main thread
http.listener = new mesibo.HttpListener() {
@Override
public boolean mesibo_onHttpProgress(mesibo.Http http, int state, int percent) {
if(100 == percent && mesibo.Http.STATE_DOWNLOAD == state) {
// download complete
}
Return true; // return false to cancel
}
};
if(http.execute()) {
}
我设法让它发挥作用,但我不得不承认我不明白这一点...
我激活了 OkHttp
日志以查看请求。这是我首先得到的(这些是假钥匙不要浪费你的时间-_-):
D/OkHttp: --> POST https://api.kraken.com/0/private/TradesHistory http/1.1
D/OkHttp: Content-Type: application/x-www-form-urlencoded
D/OkHttp: Content-Length: 42
D/OkHttp: API-Sign: /Eu6Sth0oa13Tr/ofme07TF4ct+TercW7uU9PxBEQhcpYTflC2c/jEW1BZuamBXco0jlgOzWt8RMh0o6kAE5SA==
D/OkHttp: API-Key: XnmS2gW2Sr1xR/vnB0ivJuHABdXUnW4bsMTOBMREOlz8xYDh00J+D9i4
D/OkHttp: start=1481587200&ofs=0&nonce=1511301862261
D/OkHttp: --> END POST (42-byte body)
D/OkHttp: <-- 200 https://api.kraken.com/0/private/TradesHistory (2049ms)
D/OkHttp: date: Tue, 21 Nov 2017 22:04:27 GMT
D/OkHttp: content-type: application/json; charset=utf-8
D/OkHttp: set-cookie: __cfduid=dc9e29889eeda633314ca5aaad10ce8291511301865; expires=Wed, 21-Nov-18 22:04:25 GMT; path=/; domain=.kraken.com; HttpOnly
D/OkHttp: vary: Accept-Encoding
D/OkHttp: server: cloudflare-nginx
D/OkHttp: cf-ray: 3c16f1d348503e80-ZRH
D/OkHttp: {"error":["EAPI:Invalid key"]}
D/OkHttp: <-- END HTTP (30-byte body)
现在,我没有使用 params.toString()
作为参数来计算签名,而是手动传递了连接值 ("start=" + start + "&ofs=" + offset +"&nonce=" + nonce
),这就是我得到的结果:
D/OkHttp: --> POST https://api.kraken.com/0/private/TradesHistory http/1.1
D/OkHttp: Content-Type: application/x-www-form-urlencoded
D/OkHttp: Content-Length: 42
D/OkHttp: API-Sign: DuxyUuHbj4V9WrOpaoBM6Rx3mZluoqJdkg3xBLgc0A/lVotq3WL19VErctz+ugTxi0eRpCI6oBnl95+Lmh9WmQ==
D/OkHttp: API-Key: XnmS2gW2Sr1xR/vnB0ivJuHABdXUnW4bsMTOBMREOlz8xYDh00J+D9i4
D/OkHttp: start=1481587200&ofs=0&nonce=1511302135971
D/OkHttp: --> END POST (42-byte body)
D/OkHttp: <-- 200 https://api.kraken.com/0/private/TradesHistory (1340ms)
D/OkHttp: date: Tue, 21 Nov 2017 22:09:04 GMT
D/OkHttp: content-type: application/json; charset=utf-8
D/OkHttp: set-cookie: __cfduid=d7916fbb113ed6f83b9382ea0a57240731511302143; expires=Wed, 21-Nov-18 22:09:03 GMT; path=/; domain=.kraken.com; HttpOnly
D/OkHttp: vary: Accept-Encoding
D/OkHttp: server: cloudflare-nginx
D/OkHttp: cf-ray: 3c16f89b4e273e62-ZRH
D/OkHttp: {"error":[],"result":{"trades":{...}}}
D/OkHttp: <-- END HTTP (11944-byte body)
看到发送数据的不同了吗?不?没有...
但它有效,我不再收到 invalid key
响应。
另一个奇怪的事情是改变参数的顺序会使它退回到invalid key
响应:
String parameters = "start=" + start + "&ofs=" + offset +"&nonce=" + nonce; //works
String parameters = "start=" + start + "&nonce=" + nonce + "&ofs=" + offset; //doesn't work
我无法解释这个...可能是编码问题?
EDIT : 好的明白了,似乎参数在请求正文中排序。尽管服务器不关心 HTTP 参数的顺序,但 HMAC 关心(当然 param1=value1¶m2=value2
会生成与 param2=value2¶m1=value1
不同的 HMAC)。
所以我必须在用于计算 HMAC 的字符串中使用与在请求正文中完全相同的顺序(我硬编码了从 OkHttp 日志中获得的顺序的值,但也许最好的方法是提取它来自请求正文,甚至更好地规范化请求正文)。