【问题标题】:Identical request timing out in Java Apache, working fine in postmanJava Apache中相同的请求超时,在邮递员中工作正常
【发布时间】:2018-11-07 16:45:18
【问题描述】:

我正在尝试根据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"
}

(在 wireshark 中捕获)(### 部分已被删除信息,我确保 auth 和 ids 匹配)

###/office365/receive.php 有一个 php 脚本,它只是回显$_GET['validationtoken']。这在 Postman 中完美运行。

在 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 中完全相同的请求超时(无论超时长度如何)

【问题讨论】:

  • 你使用代理服务器吗?
  • @declension 没有使用代理
  • POST /api/v2.0/me/subscriptions HTTP/1.1POST /me/subscriptions HTTP/1.1。实际上还有一些值得注意的进一步变化,因此请求相似但不完全相同。也许差异之一是导致问题。你有没有收到你的客户端发来的请求,并被相同的服务器逻辑处理?
  • 您使用来自 Java 和 Postman 的不同版本的 API 是否正确?
  • @DmitrySurin 抱歉,不,当我尝试不同的方法来捕获请求时,这是一个无关的错误。更新

标签: java apache


【解决方案1】:

问题是,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);
}

【讨论】:

    猜你喜欢
    • 2017-06-19
    • 1970-01-01
    • 1970-01-01
    • 2018-10-16
    • 1970-01-01
    • 2018-08-06
    • 2019-08-25
    • 2016-09-06
    • 2020-05-25
    相关资源
    最近更新 更多