Http входящий адаптер дает пустой ответ на запрос OPTIONS под CORS - PullRequest
0 голосов
/ 08 мая 2018

В моей среде разработки я использую два отдельных технологических стека для внешнего интерфейса и внутреннего интерфейса:

  • React + webpack-dev-server для веб-интерфейса, обслуживается по адресу localhost:8081
  • Spring для бэкэнда, обслуживающий конечные точки HTTP через localhost:7080, двумя способами:
    • с использованием интеграция для обработки данных (прием, преобразование и хранение), эта обработка начинается с http:inbound-channel-adapter (см. Следующую конфигурацию XML)
    • с использованием MVC для предоставления доступа только для чтения (т.е. только GET) через API REST (реализовано с помощью @RestController -аннотированных классов)

Интерфейс взаимодействует с бэкэндом, используя только fetch() вызовы.

Находясь на другом порту, браузер блокирует любой запрос к конечным точкам HTTP без правильной конфигурации CORS. Поскольку это будет временно, я попытался найти самое простое / быстрое решение, мне удалось заставить работать вызовы API REST, но входящий адаптер по-прежнему вызывает проблемы во время запроса OPTIONS.

Веб-приложение состоит из двух разделов:

  1. таблица, которая извлекает данные из REST API
    • эта часть работает, клиент успешно извлекает данные из серверной части
  2. форма, которая загружает файл с парой других полей
    • здесь я продолжаю нажимать на ошибки

Это текущая конфигурация Spring Integration (я использую версию 5.0.3):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ctx="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:int="http://www.springframework.org/schema/integration"
    xmlns:int-file="http://www.springframework.org/schema/integration/file"
    xmlns:int-jdbc="http://www.springframework.org/schema/integration/jdbc"
    xmlns:int-sftp="http://www.springframework.org/schema/integration/sftp"
    xmlns:int-http="http://www.springframework.org/schema/integration/http"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="...">

    <!-- ... -->

    <int-http:inbound-channel-adapter id="httpInboundAdapter"
        channel="httpRequestsChannel"
        path="/services/requests"
        supported-methods="POST">
        <int-http:cross-origin allow-credentials="false" allowed-headers="*" origin="*" />
    </int-http:inbound-channel-adapter>

    <int:channel id="httpRequestsChannel" />

    <int:chain input-channel="httpRequestsChannel" id="httpRequestsChain">

        <int:transformer method="transform">
            <bean class="transformers.RequestToMessageFile" />
        </int:transformer>

    <!-- ... -->

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

    <!-- ... -->

</beans>

Вместо этого выполняется одна из конечных точек REST:

package api;

// imports

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping(path="/api/requests", produces="application/json")
public class RequestController {
    // ...

Вот преобразователь, который должен обрабатывать POST-файл:

package transformers;

// imports

public class RequestToMessageFile {

    private static final String ALLOW_DUPES = "allowDuplicateFilenames";

    public Message<?> transform(LinkedMultiValueMap<String, Object> multipartRequest) {
        File file = null;
        int tx = -1;

        String localFilename = null;

        for (String elementName : multipartRequest.keySet()) {
            if (RequestFields.TRANSACTION_TYPE.equals(elementName)) {
                tx = Integer.valueOf(((String[]) multipartRequest.getFirst(RequestFields.TRANSACTION_TYPE))[0]);
            } else if (RequestFields.FILE.equals(elementName)){
                try {
                    UploadedMultipartFile uploadedFile = (UploadedMultipartFile) multipartRequest.getFirst(RequestFields.FILE);

                    // file handling
                    file = new File(/* ... */);
                    uploadedFile.transferTo(file);
                } catch (IllegalStateException e) {
                    LogManager.getLogger(getClass()).error(e, e);
                } catch (IOException e) {
                    LogManager.getLogger(getClass()).error(e, e);
                }
            }
        }

        if(file != null) {
            MessageBuilder<File> builder = MessageBuilder.withPayload(file);
            // ...
            Message<?> m = builder.build();
            return m;
        } else {
            return MessageBuilder.withPayload(new Object()).build();
        }
    }

}

Вот как я вызываю конечную точку из браузера:

var data = new FormData();
data.append('transaction-type', this.state.transactionType);
data.append('file', this.state.file);

fetch('http://localhost:7080/services/requests?allowDuplicateFilenames=true', {
    method: 'POST',
    body: data,
    mode: 'cors',
    headers:{
        'Accept': 'application/json',
        },
  }
)
.then(/* ... */)
.catch(/* ... */)

Что я испытываю, так это то, что преобразователь вызывается во время запроса OPTIONS (чего я не ожидал), разумеется, он не содержит тела, поэтому присутствует только параметр allowDuplicateFilenames url.

Поскольку файл не может быть определен, преобразователь возвращает сообщение withPayload(new Object()), но запрос продолжает идти по настроенным каналам и конечным точкам, в конечном итоге заканчивая ошибкой, когда пустые данные передаются последней конечной точке & mdash ; jdbc:outbound-channel-adapter.

Ошибка в конечном итоге возвращается клиенту как ошибка HTTP 500.

Я ожидаю, что только запрос POST будет передан входящему адаптеру http, тогда как запрос OPTIONS обрабатывается платформой, но это может быть неверной интерпретацией.

Что мешает выполнению запросов POST? Что-то не так в моей конфигурации? Должен ли я обрабатывать запрос OPTIONS тоже?


В последующих попытках я добавил расширение Allow-Control-Allow-Origin: * в Chrome и удалил параметр headers из вызовов внешнего интерфейса fetch(). С этим параметром запросы работают, потому что браузер просто выдает POST.

Я хотел бы добавить, что запросы к REST API успешны, поэтому конфигурация CORS должна быть правильной; хотя мне кажется, что входящий адаптер http имеет ту же конфигурацию, что и эти @RestController s, что-то должно отсутствовать.


UPDATE

Я добавил еще одну конечную точку для специальной обработки запроса OPTIONS:

</int-http:inbound-channel-adapter>

    <int-http:inbound-channel-adapter id="httpInboundAdapterOptions"
        channel="nullChannel"
        path="/services/requests"
        supported-methods="OPTIONS">
        <int-http:cross-origin allow-credentials="false" allowed-headers="*" origin="*" />
    </int-http:inbound-channel-adapter>

Теперь получены и запросы OPTIONS, и POST, и последний начинает полную обработку данных интеграция ; браузер все еще жалуется на отсутствующий заголовок “Access-Control-Allow-Origin”, а из консоли браузера я вижу только заголовки:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Tue, 08 May 2018 14:29:30 GMT

В отличие от этого GET над конечной точкой REST приносит:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Vary: Origin
Access-Control-Allow-Origin: *
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 08 May 2018 14:29:08 GMT
...