在RESTful Web服务中使用多个资源

在我的Web服务器应用程序中,我有一个方法,它修改xml文档,看起来类似于:

@POST @Path("somePath") @Consumes({"application/xml", "application/zip"}) public Response modifyXml() { //some processing } 

消费的zip存档包含需要修改的xml文件和一些其他文件。 如何区分消耗的xml文件和方法内的存档以及应该使用哪种方法参数来表示此消耗的资源?

一种解决方案是从InputStream读取。 您可以将InputStream包装在ZipInputStream 。 使用ZipInputStream您可以使用ZipInputStream.getNextEntry()获取ZipEntry ,然后您可以使用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 --------- hello world test2.xml --------- hello squirrel test3.json ---------- { "test3":"Hello Girls" } 

我得到以下结果

 ==== Data ==== name: test1.xml data: hello world name: test2.xml data: hello squirrel 

另外,如果您可以控制数据的发送方式,您可能还需要查看多部分解决方案。 您可以在其中设置内容类型,并且可以为每个部分命名,以便更容易访问。

你可以在这里看到Resteasy对multipart的支持,以及所需的依赖 。


UPDATE

如果您必须使用application/zip ,则没有标准支持。 所以你需要打造自己的MessageBodyReader 。 它可以像包装一样简单并返回已经提供的InputStream

 @Provider @Consumes("application/zip") public class ZipMessageBodyReader implements MessageBodyReader { @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return type == ZipInputStream.class; } @Override public ZipInputStream readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap 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 ,您当然还需要为application/zip编写MessageBodyWriter


更新2

来自Comment:我需要我的方法能够同时使用一个简单的xml文件和一个包含xml文件的zip存档,所以我注释了类似(伪代码)的方法:“consume(xml,zip)”并声明一个参数InputStream的方法是; 在方法体中,我需要确定此InputStream是xml类型还是zip存档,并且想要写类似于:“if(类型为xml){then treat is a xml} else {treat is a a zip archive}。希望现在这个问题更容易理解

我们可以保持您的原始方法签名同时接受application/xmlapplication/zip 。 我们还可以通过注入HttpHeadersHttpHeaders获取Content-Type来检查实际发送的是HttpHeaders 。 基于此,我们将确定如何提取。 这是我们如何完成这个的另一个例子

 @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 { @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return type == InputStream.class; } @Override public InputStream readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap 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: hello world name: test2.xml data: hello squirrel 200 hello world 

注意:使用客户端,我必须实现MessageBodyWriter来处理application/zip类型。 以下是一个简单的实现,只是为了让测试工作。 一个真正的实现需要一些修复

 @Provider @Produces("application/xml") public class ZipClientMessageBodyWriter implements MessageBodyWriter { @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 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 ,所以无论我们是否有application/zip的读者,它都会返回InputStream