Время ожидания идентичного запроса в Java Apache, нормально работает в почтальоне

Я пытаюсь зарегистрировать веб-перехватчик Office365 Api для официальные API-документы. Попробовал в почтальоне, все работает как положено.

Версия Java: 1.7 (я знаю ...)

Я работаю с версией фреймворка Play 1.2.7.2.

HttpClient: org.apache.http.client.HttpClient

Соответствующая документация:

The subscription process goes as follows:

A client app makes a subscription request (POST) for a specific resource. It includes a notification URL, among other properties.

The Outlook notifications service tries to validate the notification URL with the listener service. It includes a validation token in the validation request.

If the listener service successfully validates the URL, it returns a success response within 5 seconds as follows:

Sets the content type in the response header to text\plain. Includes the same validation token in the response body. Returns an HTTP 200 response code. The listener can discard the validation token subsequently. Depending on the URL validation result, the Outlook notifications service sends a response to the client app:

If URL validation was successful, the service creates the subscription with a unique subscription ID and sends the response to the client. If URL validation was unsuccessful, the service sends an error response with an error code and other details. Upon receiving a successful response, the client app stores the subscription ID to correlate future notifications with this subscription.

Запрос почтальона:

Оба запроса были перехвачены 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) (части ### удалены, я убедился, что аутентификация и идентификаторы совпадают)

На ###/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();

Независимо от того, насколько высоко я установил лимит, я никогда не получаю ответа, офисный api. Даже не ответ с ошибкой.

TL; DR: мой запрос отлично работает в почтальоне, точно такой же запрос в Java истекает (независимо от продолжительности тайм-аута)

Вы используете прокси-сервер?

declension 16.11.2018 15:54

@declension прокси не используется

Le 'nton 16.11.2018 15:57
POST /api/v2.0/me/subscriptions HTTP/1.1 против POST /me/subscriptions HTTP/1.1. Есть некоторые другие изменения, которые действительно примечательны, поэтому запросы похожи, но НЕ идентичны. Возможно, одно из различий вызывает проблемы. Вы даже получили запрос, отправленный от вашего клиента, и были ли обработаны той же логикой сервера?
Roman Vottner 16.11.2018 16:22

Верно ли, что вы использовали разные версии API от Java и Postman?

Dmitry Surin 16.11.2018 16:23

@DmitrySurin, извините, нет, это была несвязанная ошибка, связанная с тем, что я пробовал разные способы захвата запросов. Обновлено

Le 'nton 16.11.2018 16:42

Просто совет: если вы установите награду за вопрос, но отметите один ответ как принятый, люди будут считать, что награда предназначена для принятого ответа, и просто прокрутят (то есть проигнорируют) ваш вопрос, таким образом, ваши очки репутации будут потрачены впустую. . В следующий раз дождитесь «льготного периода», прежде чем принимать ответ, чтобы максимально увеличить доступ.

walen 23.11.2018 14:55
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
9
6
991
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что org.apache.http.client.HttpClient не является потокобезопасным (по крайней мере, для игровой платформы 1.2.7). Поскольку игровая среда повторно использует потоки, я могу предположить, что она так и не получила ответа (поскольку запрос регистрации веб-перехватчика занимает немного больше времени, чем обычные запросы).

Я переключился на 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);
}

Другие вопросы по теме