В моей среде разработки я использую два отдельных технологических стека для внешнего интерфейса и внутреннего интерфейса:
- 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.
Веб-приложение состоит из двух разделов:
- таблица, которая извлекает данные из REST API
- эта часть работает, клиент успешно извлекает данные из серверной части
- форма, которая загружает файл с парой других полей
- здесь я продолжаю нажимать на ошибки
Это текущая конфигурация 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