Java Apache中的请求超时相同,而邮递员工作正常

时间:2018-11-07 16:45:19

标签: java apache

我正在尝试按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中完全相同的请求超时(无论超时时间长短)

1 个答案:

答案 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);
}