Использование нескольких ресурсов в веб-службе RESTful
В моем приложении веб-сервера у меня есть метод, который изменяет XML-документ и выглядит примерно так:
@POST
@Path("somePath")
@Consumes({"application/xml", "application/zip"})
public Response modifyXml() {
//some processing
}
Использованный zip-архив содержит XML-файл, который необходимо изменить, и некоторые другие файлы. Как я могу различить используемый XML-файл и архив внутри метода и какой тип параметра метода следует использовать для представления этого использованного ресурса?
1 ответ
Одним из решений является просто прочитать из InputStream
, Вы могли бы обернуть InputStream
в ZipInputStream
, С ZipInputStream
Вы можете получить ZipEntry
с ZipInputStream.getNextEntry()
, затем вы можете получить имя файла с ZipEntry.getName()
, Затем просто проверьте, если имя endsWith(".xml")
,
С этим, хотя, вам нужно будет потреблять application/octet-stream
, Вот простой пример
@Path("/zip")
public class ZipResource {
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
public Response postZipFile(InputStream is) throws Exception {
StringBuilder builder;
try (ZipInputStream zip = new ZipInputStream(is)) {
builder = new StringBuilder("==== Data ====\n");
ZipEntry entry;
while ((entry = zip.getNextEntry()) != null) {
String filename = entry.getName();
if (filename.endsWith(".xml")) {
builder.append("name: ").append(entry.getName()).append("\n");
String xml = filePartToString(zip, (int) entry.getSize());
builder.append("data: ").append(xml).append("\n");
}
zip.closeEntry();
}
}
return Response.ok(builder.toString()).build();
}
private String filePartToString(InputStream is, int size) throws Exception {
String xml;
byte[] buff = new byte[size];
is.read(buff, 0, size);
return new String(buff);
}
}
Вот тест
@Test
public void testResteasy() throws Exception {
WebTarget target = client.target(
TestPortProvider.generateURL(BASE_URI)).path("zip");
File file = new File("C:/test/test.zip");
Response response = target.request().post(
Entity.entity(file, MediaType.APPLICATION_OCTET_STREAM));
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
response.close();
}
Используя эти файлы в zip
test1.xml
---------
<test1>hello world</test1>
test2.xml
---------
<test2>hello squirrel</test2>
test3.json
----------
{
"test3":"Hello Girls"
}
Я получаю следующий результат
==== Data ====
name: test1.xml
data: <test1>hello world</test1>
name: test2.xml
data: <test2>hello squirrel</test2>
Кроме того, если у вас есть контроль над тем, как отправляются данные, возможно, вы захотите также рассмотреть многокомпонентное решение. Там вы устанавливаете типы контента, и вы можете назвать каждую часть, где они легче доступны.
Вы можете увидеть поддержку Resteasy для multipart здесь и необходимую зависимость.
ОБНОВИТЬ
Если вы должны использовать application/zip
Нет стандартной поддержки для этого. Так что вам нужно было бы самим MessageBodyReader
, Это может быть что-то такое же простое, как упаковка и возврат уже предоставленного InputStream
@Provider
@Consumes("application/zip")
public class ZipMessageBodyReader implements MessageBodyReader<ZipInputStream> {
@Override
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return type == ZipInputStream.class;
}
@Override
public ZipInputStream readFrom(Class<ZipInputStream> type,
Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream) throws IOException, WebApplicationException {
return new ZipInputStream(entityStream);
}
}
Тогда в вашем методе ресурсов, вы могли бы просто иметь ZipInputStream
параметр, а не InputStream
,
@POST
@Consumes("application/zip")
public Response postZipFile(ZipInputStream zip) throws Exception {
На стороне клиента (с клиентским API), если вы должны были использовать application/zip
, вам, конечно, нужно также написать MessageBodyWriter
за application/zip
ОБНОВЛЕНИЕ 2
Из комментария: мне нужно, чтобы мой метод мог использовать как простой XML-файл, так и ZIP-архив, содержащий XML-файл, поэтому я аннотирую метод примерно так (псевдокод): "потребляет (XML, Zip)" и объявляет метод с параметром InputStream is; Затем в теле метода мне нужно определить, относится ли этот InputStream к типу xml или zip-архиву, и хотите написать что-то похожее на: "if(имеет тип xml) {затем обрабатывать как xml} else {обращаться как zip-архив}. Надеюсь, теперь вопрос более понятен
Мы можем сохранить вашу оригинальную подпись метода, принимая как application/xml
а также application/zip
, Кроме того, мы можем проверить, какой на самом деле отправляется путем инъекции HttpHeaders
и получить Content-Type
от него. Исходя из этого, мы определим, как извлечь. Вот еще один пример того, как мы можем завершить это
@Path("/zip")
public class ZipResource {
@POST
@Consumes({"application/zip", "application/xml"})
public Response postZipFile(InputStream is, @Context HttpHeaders headers) throws Exception {
String contentType = headers.getHeaderString(HttpHeaders.CONTENT_TYPE);
String returnString = null;
if (null != contentType) switch (contentType) {
case "application/xml":
returnString = readXmlFile(is);
break;
case "application/zip":
returnString = readZipFile(is);
break;
}
return Response.ok(returnString).build();
}
private String filePartToString(InputStream is, int size) throws Exception {
String xml;
byte[] buff = new byte[size];
is.read(buff, 0, size);
return new String(buff);
}
private String readXmlFile(InputStream is) {
StringWriter writer = new StringWriter();
try {
IOUtils.copy(is, writer, "utf-8");
} catch (IOException ex) {
Logger.getLogger(ZipResource.class.getName()).log(Level.SEVERE, null, ex);
}
return writer.toString();
}
private String readZipFile(InputStream is) {
StringBuilder builder = new StringBuilder("==== Data ====\n");
try (ZipInputStream zip = new ZipInputStream(is)) {
ZipEntry entry;
while ((entry = zip.getNextEntry()) != null) {
String filename = entry.getName();
if (filename.endsWith(".xml")) {
builder.append("name: ").append(entry.getName()).append("\n");
String xml = filePartToString(zip, (int) entry.getSize());
builder.append("data: ").append(xml).append("\n");
}
zip.closeEntry();
}
} catch (Exception ex) {
ex.printStackTrace();
}
return builder.toString();
}
}
Нам понадобится MessageBodyReader
обращаться с application/zip
тип. Вышеуказанный работает нормально, но нам просто нужно вернуть InputStream
вместо ZipInputStream
@Provider
@Consumes("application/zip")
public class ZipMessageBodyReader implements MessageBodyReader<InputStream> {
@Override
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return type == InputStream.class;
}
@Override
public InputStream readFrom(Class<InputStream> type,
Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream) throws IOException, WebApplicationException {
return entityStream;
}
}
Теперь с тестом
@Test
public void testResteasy() throws Exception {
WebTarget target = client.target(
TestPortProvider.generateURL(BASE_URI)).path("zip");
File file = new File("C:/test/test.zip");
Response response = target.request().post(
Entity.entity(file, "application/zip"));
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
response.close();
file = new File("C:/test/test1.xml");
response = target.request().post(
Entity.entity(file, "application/xml"));
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
response.close();
}
мы получаем следующее
200
==== Data ====
name: test1.xml
data: <test1>hello world</test1>
name: test2.xml
data: <test2>hello squirrel</test2>
200
<test1>hello world</test1>
Примечание: с клиентом мне пришлось реализовать MessageBodyWriter
обращаться с application/zip
тип. Ниже приведена простая реализация, чтобы заставить тест работать. Реальная реализация потребует некоторого исправления
@Provider
@Produces("application/xml")
public class ZipClientMessageBodyWriter implements MessageBodyWriter<File> {
@Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return type == File.class;
}
@Override
public long getSize(File t, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return -1;
}
@Override
public void writeTo(File t, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
throws IOException, WebApplicationException {
IOUtils.write(IOUtils.toByteArray(new FileInputStream(t)), entityStream);
}
}
....
client.register(ZipClientMessageBodyWriter.class);
Вы также заметите, что в некоторых примерах кода я использовал Apache Commons IOUtils
, Простите за это. Я был ленивым:-)
ОБНОВЛЕНИЕ 3
На самом деле нам не нужно MessageBodyReader
, Алгоритм поиска читателя будет по умолчанию просто InputStream
читатель, так как он поддерживает application/xml
так что он уже вернет InputStream
есть ли у нас читатель для application/zip
или нет