PUT-запрос работает с использованием почтальона, но не использует Java, весеннюю загрузку

Я пытаюсь сделать вызов API в своем проекте Spring Boot. Запрос PUT работает нормально с использованием почтальона, и я получаю код состояния 204 (без содержимого). В то время как код JAVA выдает внутреннюю ошибку сервера 500. URL-адрес, сгенерированный из кода (согласно журналам), аналогичен URL-адресу в почтальоне. Вот частичный код:

private void subscribe() {            
    String url = "https://api.linkedin.com/rest/eventSubscriptions/(developerApplication:urn%3Ali%3AdeveloperApplication%3A{dev_app_id},user:urn%3Ali%3Aperson%3A{person_id},entity:urn%3Ali%3Aorganization%3A{organization_id},eventType:ORGANIZATION_SOCIAL_ACTION_NOTIFICATIONS)";
    url.replace("{dev_app_id}", DEVELOPER_APPLICATION_ID);
    url.replace("{person_id}", "iTFpJkMpIl");
    url.replace("{organization_id}", ORGANIZATION_ID);

    // headers
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.add("X-Restli-Protocol-Version", "2.0.0");
    httpHeaders.add("LinkedIn-Version", VERSION);
    httpHeaders.setContentType(MediaType.APPLICATION_JSON);
    httpHeaders.set(HttpHeaders.AUTHORIZATION, new StringBuilder("Bearer ").append(channel.getAccessToken()).toString());

    //body
    Webhook body = new Webhook(callback_url);
    HttpEntity<Webhook> entity = new HttpEntity<Webhook>(body, httpHeaders);
    logger.info("Hitting subscription API: {} with body: {}", url, getJson(entity.getBody()));
    logger.info("Headers: {}", entity.getHeaders().toSingleValueMap());
    try {
        restTemplate.put(url, entity);
    } catch (RestClientException e) {
        e.printStackTrace();
    }
}

// just to check logs
private String getJson(Webhook body) {
    try {
        return new ObjectMapper().writeValueAsString(body);
    } catch (Exception e) {
        logger.error("Error converting object to JSON.", e);
        return "";
    }
}

Класс Webhook:

public class Webhook {
    private String webhook;

    public Webhook(String webhook) {
        this.webhook = webhook;
    }

    public String getWebhook() {
        return webhook;
    }

    public void setWebhook(String webhook) {
        this.webhook = webhook;
    }
}

В документации указан URL-адрес, который необходимо создать. Конкретные URN закодированы.

Ошибка:

org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 Server Error: "{"message":"Internal Server Error","status":500}"
        at org.springframework.web.client.HttpServerErrorException.create(HttpServerErrorException.java:102)
        at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:186)
        at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:137)
        at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
        at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:942)
        at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:891)
        at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:790)
        at org.springframework.web.client.RestTemplate.put(RestTemplate.java:567)
        at com.germin8.main.services.EventSubscriber.lambda$0(EventSubscriber.java:152)
        at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
        at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
        at com.germin8.main.services.EventSubscriber.createSubscriptions(EventSubscriber.java:131)
        at com.germin8.main.services.EventSubscriber.organizarionSubscriptionMonitor(EventSubscriber.java:99)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
        at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
        at io.micrometer.observation.Observation.observe(Observation.java:499)
        at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
        at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
        at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
        at java.base/java.lang.Thread.run(Thread.java:840)

Вот мой pom.xml

<?xml version = "1.0" encoding = "UTF-8"?>
<project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
        <relativePath/>
    </parent>
    <groupId>com.demo</groupId>
    <artifactId>linkedin-crawler</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>

    <name>linkedin-crawler</name>
    <description>Linkedin Crawling Service</description>
    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>32.1.3-jre</version>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>1_maven.apache.org</id>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <url>https://repo.maven.apache.org/maven2</url>
            <layout>default</layout>
        </repository>
        <repository>
          <id>jcenter-snapshots</id>
          <name>jcenter</name>
          <url>https://jcenter.bintray.com/</url>
        </repository>
    </repositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

я пытался

  1. попадание в конечную точку без кодирования.
  2. Использование exchange вместо put.
restTemplate.exchange(url, HttpMethod.PUT, entity, String.class);

Чтобы проверить, правильно ли сериализуется тело, я сделал аналогичный запрос к локальной конечной точке, и он тоже оказался правильным.
Чтобы проверить заголовки, я зарегистрировал их и сравнил с теми, что были в моем запросе почтальона.

Для начала перестаньте смешивать версии, удалите тег version из зависимости spring-boot-starter-data-mongodb. Во-вторых, не создавайте RestTemplate, потому что он вам нужен, используйте RestTemplateBuilder из Spring Boot, чтобы создать его как bean-компонент и раскрыть его. Наконец покажите, что вы отправляете из Postman и как выглядит ваш Webhook?

M. Deinum 10.04.2024 16:35

@M.Deinum спасибо, что указали на это. Мой URL-адрес почтальона и кода выглядит как https://api.linkedin.com/rest/eventSubscriptions/(developerA‌​pplication:urn%3Ali%‌​3AdeveloperApplicati‌​on%3A{dev_app_id},us‌​er:urn%3Ali%3Aperson‌​%3A{person_id},entit‌​y:urn%3Ali%3Aorganiz‌​ation%3A{organizatio‌​n_id},eventType:ORGA‌​NIZATION_SOCIAL_ACTI‌​ON_NOTIFICATIONS). Тело выглядит так: {"webhook":"https://{ext_ip}/webhook"}.

xD-prateek 11.04.2024 09:48

Можете ли вы добавить полный код, включая url и body, которые вы используете. Я подозреваю, что URL-адрес, который вы используете, неверен и нуждается в некоторых дополнительных параметрах (заменены ли заполнители типа {organization_id} в почтальоне?).

M. Deinum 11.04.2024 09:49

Я обновил код. И да, поля dev_app_id, person_id, organization_id, access_token, ext_ip заменяются. Также я добавил журнал в конце сообщения (со всеми идентификаторами).

xD-prateek 11.04.2024 10:23

Ваш URL-адрес неверен. Вы не помещаете туда URL-адрес encoded, поскольку теперь он будет дважды закодирован остальным шаблоном. Во-вторых, вы используете заполнители, для которых я бы предложил использовать RestTemplate для замены их в URL-адресе вместо String.replace.

M. Deinum 11.04.2024 10:25

Я только что попробовал использовать https://api.linkedin.com/rest/eventSubscriptions/(developerA‌​pplication:urn:li:de‌​veloperApplication:5‌​327785,user:urn:li:p‌​erson:iTFpJkMpIl,ent‌​ity:urn:li:organizat‌​ion:97204294,eventTy‌​pe:ORGANIZATION_SOCI‌​AL_ACTION_NOTIFICATI‌​ONS из кода. В результате получилось 400 Bad Request: "{"status":400,"code":"ILLEGAL_ARGUMENT","message":"Syntax exception in path variables"}" Кроме того, согласно документации, мне просто нужно закодировать URN. Таким образом, : после developerApplication в URL-адресе, на мой взгляд, остается прежним, и URN будут кодироваться.

xD-prateek 11.04.2024 10:44

ИМХО, ваш URL-адрес все еще неверен и приводит к двойному кодированию. Вы не публикуете URL-адрес как фактически используемый клиентом, поскольку его обрабатывает resttemplate. Вы просто публикуете URL-адрес, который вводите, а не то, что присутствует после обработки. Это разные вещи. Включите ведение журнала отладки, чтобы увидеть, что на самом деле используется, вместо того, чтобы пытаться определить это самостоятельно. Тем не менее, я все еще считаю, что проблема в вашем URL, но я удалил ответ на него. На данный момент я сдаюсь, и, возможно, в документации LInkedIn есть дополнительная информация, которая поможет вам.

M. Deinum 11.04.2024 10:56

Да вы правы! Я зарегистрировал restTemplate, и это двойное кодирование. Я был бы очень признателен, если бы вы могли предложить решение для создания URL-адреса с закодированными URN. В любом случае большое спасибо за помощь в выяснении точной причины.

xD-prateek 11.04.2024 12:26

Я бы порекомендовал вам использовать Spring Cloud OpenFeign для запросов на отдых, благодаря этому вы сэкономите много кода. Spring.io/projects/spring-cloud-openfeign он прекрасно работает вместе с автоконфигурацией, вам нужно позаботиться о других зависимостях (например, Jackson), которые вам придется включить, чтобы получать JSON в виде данных по сети.

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

Ответы 1

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

Оказывается, при передаче URL-адреса, закодированного вручную, он закодировался снова, что привело к двойному кодированию.
Тогда как, если кодирование не выполняется, запрос PUT вообще его не кодирует. Не знаю точной причины, почему это так, но вот решение, отключив кодировку по умолчанию.

DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
restTemplate = new RestTemplateBuilder()
                  .uriTemplateHandler(factory)
                  .defaultHeader("X-Restli-Protocol-Version", "2.0.0")
                  .defaultHeader("LinkedIn-Version", VERSION)
                  .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                  .build();

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