java - 动态响应格式依赖于 Jersey 的查询参数?

标签 java web-services jersey jackson jax-rs

我的网络服务 API 的所有端点都允许以 XML 和 Json 等多种格式返回响应。响应格式由查询参数决定,就像在这个请求 URI 中一样:

https://example.com/rest/countries?format=xml
https://example.com/rest/countries?format=json

我所有的端点都像这个例子中的一样快速实现:

@GET
@Path("/countries")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response getCountries(@QueryParam("format") String format) {

    if(format.equalsIgnoreCase("xml")) {
        Countries countries = getResponseFromSomewhere();
        String xml = toXmlWithJaxB(countries);
        return Response.ok(xml, MediaType.APPLICATION_XML).build();
    }
    else if(format.equalsIgnoreCase("json")) {
        Countries countries = getResponseFromSomewhere();
        String json = toJsonWithJackson(countries);
        return Response.ok(json, MediaType.APPLICATION_JSON).build();
    }
    else {
        return Response.status(415).entity("Invalid format");
    }
}

这个问题是否有更通用的解决方案,这样我就不必手动检查和处理每个端点中的格式? Jackson 或 Jersey 是否已经为此提供了解决方案?

最佳答案

无需执行任何自定义操作,Jersey 就有 UriConnegFilter 检查格式的“扩展”。例如

https://example.com/rest/countries.xml
https://example.com/rest/countries.json

您需要配置映射

final Map<String, MediaType> mappings = new HashMap<>();
mappings.put("json", MediaType.APPLICATION_JSON_TYPE);
mappings.put("xml", MediaType.APPLICATION_XML_TYPE);

return new ResourceConfig()
        .property(ServerProperties.MEDIA_TYPE_MAPPINGS, mappings);

这里我们只是将扩展名映射到媒体类型。 Jersey 将完成剩下的工作。

如果你真的想坚持只使用查询参数,那么你可以做的是编写一个 @PreMatching ContainerRequestFilter 来检查查询参数,然后相应地设置 Accept header 。

@Provider
@PreMatching
@Priority(3000)
public static class QueryConnegFilter implements ContainerRequestFilter {

    private static final Map<String, String> mappings;

    static {
        Map<String, String> map = new HashMap<>();
        map.put("xml", MediaType.APPLICATION_XML);
        map.put("json", MediaType.APPLICATION_JSON);
        mappings = Collections.unmodifiableMap(map);
    }

    @Override
    public void filter(ContainerRequestContext request) throws IOException {
        final String format = request.getUriInfo().getQueryParameters().getFirst("format");
        if (format != null) {
            final String mediaType = mappings.get(format);
            if (mediaType != null) {
                request.getHeaders().putSingle(HttpHeaders.ACCEPT, mediaType);
            }
        }
    }
}

然后你只需要在你的应用程序中注册它。现在你可以做

https://example.com/rest/countries?format=xml
https://example.com/rest/countries?format=json

这是一个完整的工作测试,上面列出了两个选项

import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

import javax.annotation.Priority;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

/**
 * Run it like any other JUnit test. Only two required dependencies:
 *
 * <dependency>
 *   <groupId>org.glassfish.jersey.test-framework.providers</groupId>
 *   <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
 *   <version>${jersey2.version}</version>
 * </dependency>
 * <dependency>
 *   <groupId>org.glassfish.jersey.media</groupId>
 *   <artifactId>jersey-media-json-jackson</artifactId>
 *   <version>${jersey2.version}</version>
 * </dependency>
 * 
 * @author Paul Samsotha.
 */
public class UriConnegTests extends JerseyTest {

    @XmlRootElement
    public static class Model {
        private String message;
        public Model() {}
        public Model(String message) { this.message = message; }
        public String getMessage() { return this.message; }
        public void setMessage(String message) { this.message = message; }
    }


    @Path("test")
    public static class TestResource {
        @GET
        @Produces({"application/json", "application/xml"})
        public Model get() {
            return new Model("Hello World");
        }
    }

    @Provider
    @PreMatching
    @Priority(3000)
    public static class QueryConnegFilter implements ContainerRequestFilter {

        private static final Map<String, String> mappings;

        static {
            Map<String, String> map = new HashMap<>();
            map.put("xml", MediaType.APPLICATION_XML);
            map.put("json", MediaType.APPLICATION_JSON);
            mappings = Collections.unmodifiableMap(map);
        }

        @Override
        public void filter(ContainerRequestContext request) throws IOException {
            final String format = request.getUriInfo().getQueryParameters().getFirst("format");
            if (format != null) {
                final String mediaType = mappings.get(format);
                if (mediaType != null) {
                    request.getHeaders().putSingle(HttpHeaders.ACCEPT, mediaType);
                }
            }
        }
    }

    @Override
    public ResourceConfig configure() {
        final Map<String, MediaType> mappings = new HashMap<>();
        mappings.put("json", MediaType.APPLICATION_JSON_TYPE);
        mappings.put("xml", MediaType.APPLICATION_XML_TYPE);
        return new ResourceConfig()
                .register(TestResource.class)
                .register(QueryConnegFilter.class)
                .register(new LoggingFilter(Logger.getAnonymousLogger(), true))
                .property(ServerProperties.MEDIA_TYPE_MAPPINGS, mappings);
    }

    @Test
    public void returnsXmlFromExtension() {
        final String expected = "<message>Hello World</message>";
        final String data = target("test.xml")
                .request()
                .get(String.class);
        assertThat(data, containsString(expected));
    }

    @Test
    public void returnsJsonFromExtension() {
        final String expected = "{\"message\":\"Hello World\"}";
        final String data = target("test.json")
                .request()
                .get(String.class);
        assertThat(data, is(expected));
    }

    @Test
    public void returnsXmlFromQuery() {
        final String expected = "<message>Hello World</message>";
        final String data = target("test")
                .queryParam("format", "xml")
                .request()
                .get(String.class);
        assertThat(data, containsString(expected));
    }

    @Test
    public void returnsJsonFromQuery() {
        final String expected = "{\"message\":\"Hello World\"}";
        final String data = target("test")
                .queryParam("format", "json")
                .request()
                .get(String.class);
        assertThat(data, is(expected));
    }
}

关于java - 动态响应格式依赖于 Jersey 的查询参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42541267/

相关文章:

java - 在 Android 中使用 SQLite 创建表时出现问题

java - 管理任务组的设计模式

javascript - 使用纯 JavaScript 进行 Soap 调用

java - 如何使异步调用同步

java - Python 请求 : POST JSON and file (multiform data) in 1 single request

java - 如何使用 RestFul Web 服务在 java 中打印请求响应中的属性?

java - 流行的 JSP/Servlet/Portlet 错误日志查看器?

java - Spring Batch 中 map 的项目阅读器?

asp.net - 没有端点监听并且错误 404

java - 使用 Jersey 从客户端调用 RESTFul Web 服务的问题