我正在尝试按official api docs注册一个Office365 Api Webhook。我在邮递员中尝试过,所有工作都按预期进行。
Java版本:1.7(我知道...)
我正在使用Play框架1.2.7.2版
HttpClient:org.apache.http.client.HttpClient
相关文档:
订阅过程如下:
客户端应用程序针对特定资源发出订阅请求(POST)。它包括一个通知URL,以及其他属性。
Outlook通知服务尝试使用侦听器服务验证通知URL。它在验证请求中包含一个验证令牌。
如果侦听器服务成功验证了URL,它将在5秒内返回成功响应,如下所示:
将响应头中的内容类型设置为text \ plain。 在响应正文中包含相同的验证令牌。 返回HTTP 200响应代码。侦听器可以随后丢弃验证令牌。 根据URL验证结果,Outlook通知服务会将响应发送到客户端应用程序:
如果URL验证成功,则该服务将创建具有唯一订阅ID的订阅,并将响应发送到客户端。 如果URL验证失败,则该服务将发送错误响应以及错误代码和其他详细信息。 收到成功的响应后,客户端应用程序将存储订阅ID,以将将来的通知与此订阅相关联。
邮递员请求:
两个请求均被Wireshark拦截:
邮递员:
Ý`!Ë2@ʸ1cÊþßV:
ðîPOST /api/v2.0/me/subscriptions HTTP/1.1
Content-Type: application/json
cache-control: no-cache
Postman-Token: a24df796-c49e-4245-a1cf-0949cd6538b6
Authorization: Bearer ###
User-Agent: PostmanRuntime/7.4.0
Accept: */*
Host: localhost:3000
accept-encoding: gzip, deflate
content-length: 430
Connection: keep-alive
{
"@odata.type":"#Microsoft.OutlookServices.PushSubscription",
"ChangeType": "Created, Deleted, Updated",
"ClientState": "some-token",
"NotificationURL": "https://###/office365/receive.php?subId=5",
"Resource": "https://outlook.office.com/api/v2.0/me/Calendars('###')/events"
}
(捕获在wirehark中)(###
部分已删除信息,我确保auth和id匹配)
在###/office365/receive.php
处放置了一个php脚本,该脚本仅回显$_GET['validationtoken']
。这在邮递员中非常有效。
在Java中,我创建一个具有相同标题和相同正文的请求
E/@@5â$¸³zêð-gÄV$
Là¼Là¼POST /api/v2.0/me/subscriptions HTTP/1.1
Accept: */*
Content-Type: application/json
Authorization: Bearer ###
Content-Length: 476
Host: localhost:3000
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.2 (Java/1.7.0_80)
Accept-Encoding: gzip,deflate
{
"@odata.type":"#Microsoft.OutlookServices.PushSubscription",
"ChangeType": "Created, Deleted, Updated",
"ClientState": "1fdb3e372212622e0b7e68abe09e1201a81a8bcc040cce9a6f013f71a09bfbe1",
"NotificationURL": "https://###/office365/receive.php",
"Resource": "https://outlook.office.com/api/v2.0/me/Calendars('###')/events"
}
我已验证请求的所有(重要)部分完全相同。
我在Java中的代码在这里很难完全共享,但是重要的一点是:
// build url
private final RequestBuilder getRequest (String url) {
RequestBuilder req = RequestBuilder.create(getMethod()); // e.g. POST
req.setUri(overwriteBaseUrl(url) + getPath());
// headers is a hashmap<String, String>
for (String key: headers.keySet()) {
req.setHeader(key, headers.get(key));
}
// set body (handle empty cases)
String body = getBody();
if (body == null) {
body = "";
}
req.setEntity(new StringEntity(body, "UTF-8"));
return req;
}
public void send (HttpClient client, Office365Credentials cred, String url) throws IOException, InvalidCrmCredentialsException, MalformedResponseException {
// creates request (with headers, body, etc)
RequestBuilder req = getRequest(url);
// adds auth
addAuthHeaderToRequest(cred, req);
// execute the request
Response response;
try {
response = new Response(client.execute(req.build()));
} catch (ConnectionPoolTimeoutException e) {
// The 10 second limit has passed
throw new MalformedResponseException("Response missing (response timed out)!", e);
}
// check for 401, not authorized
if (response.getStatus() == 401) {
throw new InvalidCrmCredentialsException("401 - Not authorized! " + req.getUri().toString());
}
// process response
processResponse(response);
}
HttpClient的构造如下:
int CONNECTION_TIMEOUT_MS = 10 * 1000; // 10 second timeout
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(CONNECTION_TIMEOUT_MS)
.setConnectTimeout(CONNECTION_TIMEOUT_MS)
.setSocketTimeout(CONNECTION_TIMEOUT_MS)
.build();
client = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build();
无论我将限制设置得多么高,我都永远不会得到响应,即Office api。甚至没有错误响应。
TL; DR:我的请求在邮递员中工作正常,在Java中完全相同的请求超时(无论超时时间长短)
答案 0 :(得分:6)
问题是org.apache.http.client.HttpClient
并不是线程安全的(至少对于play framework 1.2.7)。由于play框架重用了线程,所以我最大的猜测是,它从未收到答案(因为webhook注册请求比正常请求花了更长的时间)。
我切换到WS
,即播放框架本身提供的http客户端。这样做的缺点是,它仅对所有http动词提供有限的支持。
新方法如下:
private final WS.WSRequest getRequest (String url) {
WS.WSRequest req = WS.url(overwriteBaseUrl(url) + getPath());
for (String key: headers.keySet()) {
req.setHeader(key, headers.get(key));
}
String body = getBody();
if (body == null) {
body = "";
}
req.body(body);
return req;
}
public void send (WSMockableSender client, Office365Credentials cred, String url) throws IOException, InvalidCrmCredentialsException, MalformedResponseException {
WS.WSRequest req = getRequest(url);
addAuthHeaderToRequest(cred, req);
// execute the request
WSResponse response;
response = new WSResponse(client.post(req));
System.out.println("Response received!");
// check for 401, not authorized
if (response.getStatus() == 401) {
throw new InvalidCrmCredentialsException("401 - Not authorized! " + req.url);
}
processResponse(response);
}