使用JSON参数简化REST URL

时间:2019-02-07 11:11:49

标签: java json rest maven jersey

我被分配了一项任务,我真的不知道该如何开始解决它,因此我们将不胜感激。考虑以下示例:

@Path("/v1/{server}:{port}/instance")  
public class WSInstance {
        private static final Log        log = LogFactory.getLog(WSInstance.class);

        private final String    PLANNING_PROPNAME = "**PLNG_NAME**";
        private final String    PLANNING_PROPVAL = "**CALENDAR_NAME**";

        @GET
        @Path("/{instanceName}")
        @Produces("text/plain")
        public String getInstanceStatus(@Context HttpHeaders headers, 
                @PathParam("server")String server, 
                @PathParam("port")int port,
                @PathParam("instanceName") String instName,
                @DefaultValue("") @QueryParam("date") String date,
                @DefaultValue("") @QueryParam("instnum") String numexec)
        {
            return getInstanceStatus(Utils.extractUserInfo(headers), server, port, instName, numexec, date);    
        }

调用上述方法的示例如下所示:

/v1/serverName:portNumber/instance/toto?date=21090207&instnum=0000

任务要求的是用json替换该URL中的所有变量(serverNameportNumbertotodateinstnum)参数。这是为了简化REST URL。

任何想法从哪里开始?


** 编辑:感谢大家的回答,您当然对我有很大帮助。到目前为止,这是我所做的:

我决定采用“更简单”的类和方法来熟悉该过程。所以我拿了这个:

@Path("/v2/{server}:{port}/admin/")
public class WSAdmin {
    private static final Log log    = LogFactory.getLog(WSAdmin.class);

    @PUT
    @Path("/device")
    @Produces("text/plain")
    @Consumes("application/json")
    public String putDevice(String jsonObject, @Context HttpHeaders headers,
                                    @PathParam("server")String server,
                                    @PathParam("port")int port)
    {
        ObjectMapper mapper = new ObjectMapper();

        try
        {
            return updateDevice(mapper.readTree(jsonObject), Utils.extractUserInfo(headers), server, port);
        }
        catch (JsonProcessingException e)
        {
            return e.getMessage();
        }
        catch (IOException e)
        {
            return e.getMessage();
        }
    }

我这样更改它:

@Path("/v2/admin/")
public class WSAdmin {
    private static final Log log    = LogFactory.getLog(WSAdmin.class);

    @POST
    @Path("/device")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response putDevice(Admin admin)
    {
            String output = admin.toString();
            return Response.status(200).entity(output).build();
    }

然后我创建了相应的POJO:

@XmlRootElement
public class Admin {

    private String server;
    private int port;
    private Date date;
    private String instnum;

// Constructors + getters + setters

    @Override
    public String toString() {
        return new StringBuffer("Server: ").append(this.server)
                .append("Port: ").append(this.port).append("Date: ")
                .append(this.date).append("InstNum: ")
                .append(this.instnum).toString();
    }
}

然后,我编辑了 web.xml 文件以能够封送和取消封送Java Objets:

<init-param>
    <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
    <param-value>true</param-value>
</init-param>

但是由于某些原因,我从邮递员拨打电话时遇到以下错误:

GRAVE [http-nio-8080-exec-5] com.sun.jersey.spi.container.ContainerRequest.getEntity A message body reader for Java class com.ws.v3.models.Admin, and Java type class com.ws.v3.models.Admin, and MIME media type application/json was not found.
The registered message body readers compatible with the MIME media type are:
application/json ->
  com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$App
  com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$App
  com.sun.jersey.json.impl.provider.entity.JSONListElementProvider$App
*/* ->
  com.sun.jersey.core.impl.provider.entity.FormProvider
  com.sun.jersey.core.impl.provider.entity.StringProvider
  com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
  com.sun.jersey.core.impl.provider.entity.FileProvider
  com.sun.jersey.core.impl.provider.entity.InputStreamProvider
  com.sun.jersey.core.impl.provider.entity.DataSourceProvider
  com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General
  com.sun.jersey.core.impl.provider.entity.ReaderProvider
  com.sun.jersey.core.impl.provider.entity.DocumentProvider
  com.sun.jersey.core.impl.provider.entity.SourceProvider$StreamSourceReader
  com.sun.jersey.core.impl.provider.entity.SourceProvider$SAXSourceReader
  com.sun.jersey.core.impl.provider.entity.SourceProvider$DOMSourceReader
  com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$General
  com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General
  com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General
  com.sun.jersey.core.impl.provider.entity.XMLRootObjectProvider$General
  com.sun.jersey.core.impl.provider.entity.EntityHolderReader
  com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$General
  com.sun.jersey.json.impl.provider.entity.JSONListElementProvider$General

有类似错误的人通过在pom.xml中添加gersonjersey-json使其消失。我添加了它们,但问题没有解决。

有什么主意吗?

3 个答案:

答案 0 :(得分:0)

可能您必须将方法类型更改为POST,然后在请求的正文中将数据作为json传递。

GET请求

GET /v1/yourServerName:8080/instance/toto?date=21090207&instnum=0000

可以成为以下POST请求

POST /v1/instance
{
  "serverName":"yourServerName",
  "portNumber":8080,
  "date":21090207,
  "instnum":"0000"
}

请注意,instnum不是数字字段,因为您传递了不能表示为数字值的字符串0000。相反,portNumberdate可以是数字值。

答案 1 :(得分:0)

考虑使用Jackson。 Jackson映射JSON <->对象。在此处了解如何在Jersey(REST)中使用它: https://www.mkyong.com/webservices/jax-rs/json-example-with-jersey-jackson/

答案 2 :(得分:0)

specificationGET请求的有效载荷是未定义的。因此,您应该避免发送带有GET请求的正文。正如Davide所建议的那样,您应该在这里切换到POST,因为这里接收到的有效负载的语义是由您(服务器/ API维护者)定义的。

但是,用标记了您的帖子后,您可能应该考虑复制20多年来在Web中使用的概念,并将其转换为您的API设计。首先,REST体系结构不关心URI的结构。 URI本身只是指向资源的指针,客户端不需要解释它或修改它。首先,服务器应提供客户做出独特选择所需的所有信息。由于客户端不应该解析和解释URI,因此如何确定客户端是否使用URI?

那么,我们人类如何与网页中的URI交互?通常,它们用人类可读的文本注释,该文本总结了该链接的内容(与上述规范相同)。这种简短但有意义的名称通常称为链接关系名称,应“附加”到每个URI。如果服务器需要更改其URI结构,则读取此类链接关系名称并仅调用随附URI的客户端将能够继续其任务。这是将客户端与服务器分离的重要一步。此类链接关系名称应为standardized,但也可以在common knowlege中指定或在媒体类型本身中指定。

许多所谓的“ REST API”的常见错误是仅支持application/xml和/或application/json。在REST架构中,那些媒体类型非常差,因为它们仅定义要使用的语法,而没有定义各个元素的语义。因此,对于客户而言,很难确定此类文档的意图,并且很难陷入typed resource陷阱中并假定某个资源具有某种类型。如果这种(非标准化的)表示形式发生变化,则客户很可能会中断以进一步与该服务/ API进行互操作。

媒体类型或多或少是某种接收到的有效负载的标准化处理规则,应有助于使接收者了解内容的含义以及它可能会做什么。 HTML可能是一种众所周知的媒体类型,它定义了某些元素何时可行以及每个元素具有的约束。它还定义了如何渲染某些元素以及如何与旧版本向后兼容。当支持链接和链接关系时,它是事实上的标准。尽管HALCollection+JSON,...在支持链接和关系名称方面朝着正确的方向迈出了一步,但它们提供的语义与HTML的语义相去甚远,尽管它们应该比HTML更可取。纯JSON,因为它们不仅指定语法,而且还指定某些元素(例如_links)的语义,即有助于客户端区分链接与内容。

就内容类型协商而言,媒体类型尤其重要,在该类型中,客户端请求服务器返回客户端可以理解的表示格式。如果服务器不能产生这样的表示,它将向客户端通知一个足够有表现力的错误代码(406)。如果服务器无法处理客户端提供的媒体类型(在POST,PUT,PATCH,...操作上),则服务器还将通知客户端它不理解这种格式(415)。

关于设计REST API的一般建议是从Web服务器的角度考虑API,并像这样设计与之的整个交互。即如果客户端必须执行证书输入,则不应仅将带有某些随机字段(在某些外部文档中指定)的playin JSON文档发送到服务器,而是服务器应教会客户端如何发送这样的请求作为开始。类似于Web表单,人们应该在Web表单中输入文本和内容,REST API应该返回一种表示公式的媒体类型,该公式可以告诉客户端服务器期望的字段,使用的操作以及将有效负载发送到的目标。

关于实际问题,我不确定您的员工为什么如此热衷于从URI中删除参数。如我在发送有效载荷的第一段中所述,您需要切换到POST,因此,除了自动执行safe操作的保证的idempotentGET功能之外,默认情况下不可缓存。

您可以做的就是允许用户或您的同事预先配置某些查询,并为这些预先配置的URI创建简短的URL。在这里,您应该为客户端提供一种类似于表单的媒体类型,在该媒体类型中,客户端可以选择要选择的选项并输入其他必要的数据。收到这样的请求后,您将存储这样的预配置,并在响应的Location标头中返回该预配置的简短URL。您还应该在常规响应中添加预配置的链接,以便客户端在持久化后未立即存储时就能够调用它。这样,您仍然具有GET操作的优势,同时可以灵活地动态添加新查询或自定义现有查询。如前所述,客户端将无法大量使用纯application/json表示形式的此类链接。如果客户端支持application/hal+json,则它可能至少知道什么是链接关系和链接,因此能够通过其附带的链接关系名称来查找和调用URI。基本上,您可以根据需要自由地稍后更改URI结构,而不会影响客户端。