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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27707724/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 12:15:12  来源:igfitidea点击:

Consume multiple resources in a RESTful Web Service

javaxmlrestjax-rswildfly-8

提问by Arthur Eirich

In my web server application I have a method, which modifies an xml document and looks similar to that:

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

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

The consumed zip archive contains the xml file which needs to be modified and some other files. How can I distinguish between consumed xml file and the archive inside the method and which kind of method parameter should I use to represent this consumed resource?

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

采纳答案by Paul Samsotha

One solution is to just read from an InputStream. You could wrap the InputStreamin a ZipInputStream. With ZipInputStreamyou can get a ZipEntrywith ZipInputStream.getNextEntry(), then you can get the file name with ZipEntry.getName(). Then just check if the name endsWith(".xml").

一种解决方案是仅从InputStream. 你可以在包裹InputStreamZipInputStream。WithZipInputStream你可以得到一个ZipEntrywith ZipInputStream.getNextEntry(),然后你可以得到文件名 with ZipEntry.getName()。然后只需检查名称endsWith(".xml")

With this though, you would need to consume application/octet-stream. Here's a simple example

尽管如此,你需要消耗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);
    }
}

Here's a test

这是一个测试

@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();
}

Using these files in a zip

在 zip 中使用这些文件

test1.xml
---------
<test1>hello world</test1>

test2.xml
---------
<test2>hello squirrel</test2>

test3.json
----------
{
    "test3":"Hello Girls"
}

I get the following result

我得到以下结果

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


As an aside, if you have control over how the data is sent, you might want to also look into a multipart solution. There you set content types, and you can name each part, where they're easier to access.

顺便说一句,如果您可以控制数据的发送方式,您可能还想研究多部分解决方案。在那里您可以设置内容类型,您可以命名每个部分,以便更容易访问。

You can see Resteasy's support for multipart here, and the required dependency.

您可以在此处查看 Resteasy 对 multipart 的支持以及所需的依赖项



UPDATE

更新

If you mustuse application/zip, there is no standard support for this. So you would need to whip up your own MessageBodyReader. It could be something as simple as wrapping and return the already provided InputStream

如果您必须使用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);
    }    
}

Then in your resource method, you could just have a ZipInputStreamparameter, instead of InputStream.

然后在您的资源方法中,您可以只有一个ZipInputStream参数,而不是InputStream.

@POST
@Consumes("application/zip")
public Response postZipFile(ZipInputStream zip) throws Exception {

On the client side (with the client API), if you were to use application/zip, you would of course need to also write a MessageBodyWriterfor application/zip

在客户端(使用客户端 API),如果要使用application/zip,当然还需要编写一个MessageBodyWriterforapplication/zip



UPDATE 2

更新 2

From Comment:I need my method to be able to consume both a simple xml file and a zip archive which contains the xml file, so I annotate the method something like (pseudo code): "consumes(xml, zip)" and declare a method with the parameter InputStream is; In the method body I then need to determine whether this InputStream is of type xml or a zip archive and want to write something similar to: "if(is of type xml) {then treat is as an xml} else {treat is as a zip archive}. Hopefully now the question is more understandable

来自评论:我需要我的方法能够同时使用一个简单的 xml 文件和一个包含 xml 文件的 zip 存档,所以我对该方法进行了注释(伪代码):“consumes(xml, zip)”并声明一个带有参数 InputStream 的方法是;然后在方法主体中,我需要确定此 InputStream 是 xml 类型还是 zip 存档,并希望编写类似于以下内容的内容:“if(is of type xml) {then Treat is as an x​​ml} else {treat is as a zip 存档}。希望现在这个问题更容易理解了

We can keep your original method signature accepting both application/xmland application/zip. Also we can check which on is actually being sent by injecting HttpHeadersand getting the Content-Typefrom it. Base on that, we will determine how to extract. Here's another example of how we can complete this

我们可以保留您的原始方法签名同时接受application/xmlapplication/zip。我们还可以通过注入HttpHeadersContent-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();
    }
}

We would need a MessageBodyReaderto handle the application/ziptype. The one above works fine, but we just need to it return an InputStreaminstead of ZipInputStream

我们需要一个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;
    }  
}

Now with the test

现在通过测试

@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();

}

we get the following

我们得到以下

200
==== Data ====
name: test1.xml
data: <test1>hello world</test1>
name: test2.xml
data: <test2>hello squirrel</test2>

200
<test1>hello world</test1>

Note:With the client, I had to implement a MessageBodyWriterto handle the application/ziptype. The following is a simple implementation just to get the test to work. A real implementation would need some fixing

注意:对于客户端,我必须实现一个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);

You'll also note in some of the example code, I made use of Apache Commons IOUtils. Excuse me for that. I was being lazy :-)

您还会在一些示例代码中注意到,我使用了 Apache Commons IOUtils。对不起。我很懒:-)



UPDATE 3

更新 3

Actually, we don't need to MessageBodyReader. The algorithm to find the reader will actually just default to the InputStreamreader, as it supports application/xml, so it will already return the InputStreamwhether we have a reader for the application/zipor not

实际上,我们不需要MessageBodyReader。找到读者的算法其实只是默认为InputStream读者,因为它支持application/xml,所以它会已经返回InputStream我们是否有对读者application/zip还是不