Якщо ми подивимося, що намагається зробити OP, він / вона намагається опублікувати два (можливо, не пов’язані) об’єкти JSON. По-перше, будь-яке рішення, щоб спробувати надіслати одну частину як тіло, а одну частину як інший параметр, ІМО, є жахливими рішеннями. Дані POST повинні надходити в тіло. Не правильно робити щось лише тому, що це працює. Деякі робочі процедури можуть порушувати основні принципи REST.
Я бачу кілька рішень
- Використовуйте application / x-www-form-urlencoded
- Використовуйте Multipart
- Просто оберніть їх одним батьківським об’єктом
1. Використовуйте application / x-www-form-urlencoded
Інший варіант - просто використовувати application/x-www-form-urlencoded
. Ми можемо мати значення JSON. Для екзамена
curl -v http:
-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) {
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;
}
final ContextResolver<ObjectMapper> contextResolver = providers
.getContextResolver(ObjectMapper.class, MediaType.APPLICATION_JSON_TYPE);
final ObjectMapper mapper = contextResolver != null ?
contextResolver.getContext(rawType) : new ObjectMapper();
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
Одним із рішень, про яке ніхто не згадував, є використання багаточастин . Це дозволяє нам надсилати довільні частини у запиті. Оскільки кожен запит може мати лише одне тіло сутності, мультичастиною є обхід, оскільки він дозволяє мати різні частини (зі своїми власними типами вмісту) як частину тіла сутності.
Ось приклад використання Джерсі (див. Офіційний документ тут )
Залежність
<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. Просто оберніть їх одним батьківським об’єктом
Цей варіант повинен бути зрозумілим, як уже згадували інші.