Если мы посмотрим, что пытается сделать OP, он / она пытается опубликовать два (возможно, не связанных) объекта JSON. Сначала любое решение, чтобы попытаться отправить одну часть как тело, а другую как другой параметр, IMO, - это ужасные решения. Данные POST должны идти в теле. Это не правильно делать что-то только потому, что это работает. Некоторые обходные пути могут нарушать основные принципы REST.
Я вижу несколько решений
- Используйте application / x-www-form-urlencoded
- Использовать Multipart
- Просто оберните их в один родительский объект
1. Используйте application / x-www-form-urlencoded
Другой вариант - просто использовать application/x-www-form-urlencoded
. На самом деле мы можем иметь значения JSON. Например: 1018 *
curl -v http://localhost:8080/api/model \
-d 'one={"modelOne":"helloone"}' \
-d 'two={"modelTwo":"hellotwo"}'
public class ModelOne {
public String modelOne;
}
public class ModelTwo {
public String modelTwo;
}
@Path("model")
public class ModelResource {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String post(@FormParam("one") ModelOne modelOne,
@FormParam("two") ModelTwo modelTwo) {
return modelOne.modelOne + ":" + modelTwo.modelTwo;
}
}
Единственное, что нам нужно, чтобы заставить это работать, это ParamConverterProvider
, чтобы заставить это работать. Ниже приведен пример, который был реализован Михалом Гаджосом из команды Джерси (найден здесь с объяснением ).
@Provider
public class JacksonJsonParamConverterProvider implements ParamConverterProvider {
@Context
private Providers providers;
@Override
public <T> ParamConverter<T> getConverter(final Class<T> rawType,
final Type genericType,
final Annotation[] annotations) {
// Check whether we can convert the given type with Jackson.
final MessageBodyReader<T> mbr = providers.getMessageBodyReader(rawType,
genericType, annotations, MediaType.APPLICATION_JSON_TYPE);
if (mbr == null
|| !mbr.isReadable(rawType, genericType, annotations, MediaType.APPLICATION_JSON_TYPE)) {
return null;
}
// Obtain custom ObjectMapper for special handling.
final ContextResolver<ObjectMapper> contextResolver = providers
.getContextResolver(ObjectMapper.class, MediaType.APPLICATION_JSON_TYPE);
final ObjectMapper mapper = contextResolver != null ?
contextResolver.getContext(rawType) : new ObjectMapper();
// Create ParamConverter.
return new ParamConverter<T>() {
@Override
public T fromString(final String value) {
try {
return mapper.reader(rawType).readValue(value);
} catch (IOException e) {
throw new ProcessingException(e);
}
}
@Override
public String toString(final T value) {
try {
return mapper.writer().writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new ProcessingException(e);
}
}
};
}
}
Если вы не сканируете ресурсы и провайдеров, просто зарегистрируйте этого провайдера, и приведенный выше пример должен работать.
2. Использовать Multipart
Одним из решений, о котором никто не упомянул, является использование multipart . Это позволяет нам отправлять произвольные части в запросе. Поскольку каждый запрос может иметь только одно тело сущности, multipart - это обходной путь, поскольку он позволяет иметь разные части (со своими собственными типами контента) как часть тела сущности.
Вот пример использования Джерси (см. Официальный документ здесь )
Зависимость
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey-2.x.version}</version>
</dependency>
Зарегистрировать MultipartFeature
import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
@ApplicationPath("/api")
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
packages("stackoverflow.jersey");
register(MultiPartFeature.class);
}
}
Ресурсный класс
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.media.multipart.FormDataParam;
@Path("foobar")
public class MultipartResource {
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response postFooBar(@FormDataParam("foo") Foo foo,
@FormDataParam("bar") Bar bar) {
String response = foo.foo + "; " + bar.bar;
return Response.ok(response).build();
}
public static class Foo {
public String foo;
}
public static class Bar {
public String bar;
}
}
Теперь сложность некоторых клиентов заключается в том, что нет способа установить Content-Type
каждой части тела, что необходимо для работы вышеупомянутого. Многокомпонентный провайдер будет искать программу чтения сообщений в зависимости от типа каждой части. Если для него не установлено значение application/json
или тип, для которого у Foo
или Bar
есть считыватель, это не удастся. Мы будем использовать JSON здесь. Там нет никакой дополнительной конфигурации, кроме наличия ридера. Я буду использовать Джексона. С учетом приведенной ниже зависимости никакая другая конфигурация не требуется, поскольку поставщик будет обнаружен при сканировании пути к классам.
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey-2.x.version}</version>
</dependency>
Теперь тест. Я буду использовать cURL . Вы можете видеть, что я явно установил Content-Type
для каждой части с type
. -F
означает другую часть. (Смотрите в самом конце поста идею того, как на самом деле выглядит тело запроса.)
curl -v -X POST \
-H "Content-Type:multipart/form-data" \
-F "bar={\"bar\":\"BarBar\"};type=application/json" \
-F "foo={\"foo\":\"FooFoo\"};type=application/json" \
http://localhost:8080/api/foobar
Результат: FooFoo; BarBar
Результат точно такой, как мы ожидали. Если вы посмотрите на метод ресурса, все, что мы делаем, это возвращаем эту строку foo.foo + "; " + bar.bar
, собранную из двух объектов JSON.
Вы можете увидеть некоторые примеры использования различных клиентов JAX-RS по ссылкам ниже. Вы также увидите некоторый пример на стороне сервера также из этих различных реализаций JAX-RS. Каждая ссылка должна иметь где-то ссылку на официальную документацию для этой реализации
Существуют и другие реализации JAX-RS, но вам нужно будет найти документацию для них самостоятельно. Вышеупомянутые три - единственные, с которыми я имею опыт.
Что касается клиентов Javascript, большинство примеров, которые я вижу (например, , некоторые из них , включают установку Content-Type
в undefined / false (с использованием FormData
), позволяя браузеру обрабатывать его. Но это не будет работать для нас, так как браузер не будет устанавливать Content-Type
для каждой части. И тип по умолчанию - text/plain
.
Я уверен, что есть библиотеки, которые позволяют устанавливать тип для каждой части, но просто чтобы показать вам, как это можно сделать вручную, я опубликую пример (небольшая помощь от здесь . Я буду использовать Angular, но тяжелая работа по созданию тела сущности будет простым старым Javascript.
<!DOCTYPE html>
<html ng-app="multipartApp">
<head>
<script src="js/libs/angular.js/angular.js"></script>
<script>
angular.module("multipartApp", [])
.controller("defaultCtrl", function($scope, $http) {
$scope.sendData = function() {
var foo = JSON.stringify({foo: "FooFoo"});
var bar = JSON.stringify({bar: "BarBar"});
var boundary = Math.random().toString().substr(2);
var header = "multipart/form-data; charset=utf-8; boundary=" + boundary;
$http({
url: "/api/foobar",
headers: { "Content-Type": header },
data: createRequest(foo, bar, boundary),
method: "POST"
}).then(function(response) {
$scope.result = response.data;
});
};
function createRequest(foo, bar, boundary) {
var multipart = "";
multipart += "--" + boundary
+ "\r\nContent-Disposition: form-data; name=foo"
+ "\r\nContent-type: application/json"
+ "\r\n\r\n" + foo + "\r\n";
multipart += "--" + boundary
+ "\r\nContent-Disposition: form-data; name=bar"
+ "\r\nContent-type: application/json"
+ "\r\n\r\n" + bar + "\r\n";
multipart += "--" + boundary + "--\r\n";
return multipart;
}
});
</script>
</head>
<body>
<div ng-controller="defaultCtrl">
<button ng-click="sendData()">Send</button>
<p>{{result}}</p>
</div>
</body>
</html>
Интересной частью является функция createRequest
.Здесь мы строим составную часть, устанавливая Content-Type
каждой части на application/json
и объединяя строковые объекты foo
и bar
с каждой частью.Если вы не знакомы с многочастным форматом , см. Дополнительную информацию .Другая интересная часть - заголовок.Мы установили его на multipart/form-data
.
Ниже приведен результат.В Angular я просто использовал результат для отображения в HTML с $scope.result = response.data
.Кнопка, которую вы видите, была просто чтобы сделать запрос.Вы также увидите данные запроса в firebug
3.Просто оберните их в один родительский объект
Этот параметр должен быть понятен, как уже упоминали другие.