JAX-RS(Jersey 2 实现)与 URL 扩展名 .xml 或 .json 的内容协商
JAX-RS (Jersey 2 implementation) content negotiation with URL extension .xml or .json
我见过一个 Java RESTFUL 网络服务,它允许在 URL 中请求 content-type 并在末尾添加扩展名,例如
.xml
.json
这是我在自己的 Web 服务中努力实现的内容协商风格。
我知道 @Produces
注释,事实上一个方法可以使用 (value = {})
语法解析多个类型,通过添加一个 Accept header,比如 Postman, Chrome 分机。
但我不确定如何在一种方法中有效地提取信息,然后委托给另一种方法。
我假设 REGEX 可以与 @Path
和 @PathParam
一起使用,但我的尝试尚未取得成果。
谁能举个例子?
这是我迄今为止的尝试:
package com.extratechnology.caaews;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.extratechnology.caaews.model.Log;
@Path("et")
@Produces(MediaType.APPLICATION_JSON)
public class CAAEWS {
@GET
@Path("\.{format}")
@Produces(value = {MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
public Log getLog(
@PathParam("format") String format
){
Log result = null;
switch (format) {
case "json":
result = this.getJSON();
case "xml":
result = this.getXML();
}
return result;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Log getJSON() {
return new Log("JSON!");
}
@GET
@Produces(MediaType.TEXT_XML)
public Log getXML() {
return new Log("XML!");
}
}
package com.extratechnology.caaews.model;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Log {
private String log;
public Log(String log) {
this.log = log;
}
public String getLog() {
return log;
}
public void setLog(String log) {
this.log = log;
}
}
可以使用 Spring 工具 Suite/Eclipse 设置项目,方法是使用以下方法创建 Maven 项目(类似,但比 here circa 4:50 更新):
- org.glassfish.jersey.archetypes
- jersey.quickstart.webapp
- 2.26
然后您取消注释 pom.xml 提供的部分以启用 JSON 支持,这有效地向您的 WAR.
添加了一些 JARS
我发现我也有一些讨厌的 BCEL 错误,并且不得不将一些条目附加到 catalina.properties 文件中,在键下:
tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\
....
javax.json-api-1.1.jar, javax.json.bind-api-1.0.jar, javax.json-1.1.jar, \
yasson-1.0.jar
http://localhost:18080/caaews/webapi/et
产量:
{"log":"JSON!"}
http://localhost:18080/caaews/webapi/et.xml 或
http://localhost:18080/caaews/webapi/et.json
产量:
HTTP Status 404 - Not Found
我也想知道是否有某种 HTTP 拦截器类型的方法可以解决这个问题。我的 Java 有点生疏,但它是 servlet 过滤器,还是类似于建议之前的 AOP 的东西。
感谢@user1803551 我在 switch 语句中加入了中断。
感谢@callmepills,我对代码做了一些调整。
class 级 @Path 注释现在有这个。
@Produces(value = {MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
getLog @Path 注释是“.{format}”。
为了调用和委托 getLog,您必须对 URL:
使用此语法
http://localhost:18080/caaews/webapi/et
http://localhost:18080/caaews/webapi/et/.xml
http://localhost:18080/caaews/webapi/et/.json
路径中需要有一个“/”不是我想要的,所以我想我可能必须解决 servlet 过滤器而不是 @PathParam 方法..
您是否尝试过在 class 级别删除 @Path
注释?您的方法级注释将是:
@Path("et.{format}")
我认为您当前的实现正在创建一个匹配如下路径的子资源:
/et/{format}
您的 JAX-RS 代码存在几个问题:
@Path
中的正则表达式
@Path
注解的 value 仅在参数模板内部和 :
字符之后解析正则表达式。您正在尝试在参数模板 "\.{format}"
之外使用正则表达式,因此它不会将其解析为正则表达式。
路径解析
一个方法的路径包括 class 路径的部分,后面跟着它自己的路径部分。当您尝试调用 /et.{format}
时,您的代码建议路径 /et/.{format}
和 /et
,这在任何地方都没有定义,因此是 404.
这是一个可以根据您的代码运行的示例:
@Path("et")
public class Resource {
private static final String JSON = "json";
private static final String XML = "xml";
@GET
@Path(".{format:(" + JSON + "|" + XML + ")}")
@Produces(value = { MediaType.APPLICATION_JSON, MediaType.TEXT_XML }) // not XML?
public String getLog(@PathParam("format") String format) {
switch (format) {
case JSON:
this.getJSON();
break;
case XML:
this.getXML();
}
return format;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public void getJSON() {
System.out.println("in JSON");
}
@GET
@Path("otherPath")
@Produces(MediaType.APPLICATION_XML)
public void getXML() {
System.out.println("in XML");
}
}
您的有效请求现在为:
- http://localhost:18080/caaews/webapi/et(JSON 到
getJSON
)
- http://localhost:18080/caaews/webapi/et/otherPath(XML 到
getXML
)
- http://localhost:18080/caaews/webapi/et/.xml(XML 到
getLog
)
- http://localhost:18080/caaews/webapi/et/.json(JSON 到
getLog
)
根据需要更改路径。 XML方法我用了"otherPath"
,因为它不能和空路径JSON方法冲突。我不推荐这种约定。
备注:
- 在
switch
语句中使用 break
。
- 为了减少出现错误的机会,请像我对您的自定义格式类型所做的那样,对可重用字符串等使用常量。
enum
会更好。
编辑:
请求现在有一个路径 /et/<something>.{format}
。如果我们扩展路径参数的范围以包括整个段 <something>.{format}
然后以编程方式提取格式,就可以实现这一点:
@GET
@Path("{segment:[a-zA-Z0-9_]*\.(" + JSON + "|" + XML + ")}")
@Produces(value = { MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
public String getLog(@PathParam("segment") String segment) {
String format = segment.substring(segment.indexOf('.') + 1);
switch (format) {
case JSON:
this.getJSON();
break;
case XML:
this.getXML();
}
return format;
}
正则表达式 [a-zA-Z0-9_]*
表示任何字母数字或下划线一次或多次。您可以用您想要的任何限制替换该部分。请参阅 URL 规范以了解允许的字符。
当我用 Google 搜索 "servlet filter with jax-rs example" 时,this 位于列表的顶部。粗略扫了一下代码,我觉得这很符合我的需要。
这是我的解决方案(到目前为止...请参阅脚注警告)
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- This web.xml file is not required when using Servlet 3.0 container,
see implementation details http://jersey.java.net/nonav/documentation/latest/jax-rs.html -->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<filter>
<filter-name>accept-filter</filter-name>
<filter-class>com.extratechnology.filters.AcceptFilter</filter-class>
<init-param>
<param-name>xml</param-name>
<param-value>text/xml</param-value>
</init-param>
<init-param>
<param-name>json</param-name>
<param-value>application/json</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>accept-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.extratechnology.caaews</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/webapi/*</url-pattern>
</servlet-mapping>
</web-app>
AcceptFilter.java
package com.extratechnology.filters;
import java.io.IOException;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class AcceptFilter implements Filter {
private final Map<String,String> extensions = new HashMap<String,String>();
public void init(FilterConfig config) throws ServletException {
Enumeration<String> exts = config.getInitParameterNames();
while (exts.hasMoreElements()) {
String ext = exts.nextElement();
if (ext != null && !ext.isEmpty()) {
this.extensions.put(ext.toLowerCase(), config.getInitParameter(ext));
}
}
}
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest)request;
String uri = httpRequest.getRequestURI();
String ext = this.getExtension(uri);
String accept = this.extensions.get(ext);
if (accept == null) {
accept = httpRequest.getHeader("accept");
if (accept != null && accept.indexOf("text/html") > 0) {
// patch WebKit-style Accept headers by elevating "text/html"
accept = "text/html,"+accept;
request = new RequestWrapper(httpRequest, uri, accept);
}
} else {
// remove extension and remap the Accept header
uri = uri.substring(0, uri.length() - ext.length()-1);
request = new RequestWrapper(httpRequest, uri, accept);
}
// add "Vary: accept" to the response headers
HttpServletResponse httpResponse = (HttpServletResponse)response;
httpResponse.addHeader("Vary", "accept");
chain.doFilter(request, response);
}
private String getExtension(String path) {
String result = "";
int index = path.lastIndexOf('.');
if (!(index < 0 || path.lastIndexOf('/') > index)) {
result = path.substring(index+1).toLowerCase();
}
return result;
}
private static class RequestWrapper extends HttpServletRequestWrapper {
private final String uri;
private final String accept;
public RequestWrapper(HttpServletRequest request, String uri, String accept) {
super(request);
this.uri = uri;
this.accept = accept;
}
@Override
public String getRequestURI() {
return this.uri;
}
@Override
public Enumeration<String> getHeaders(String name) {
Enumeration<String> result;
if ("accept".equalsIgnoreCase(name)) {
Vector<String> values = new Vector<String>(1);
values.add(this.accept);
result = values.elements();
} else {
result = super.getHeaders(name);
}
return result;
}
}
}
CAAEWS.java
package com.extratechnology.caaews;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.extratechnology.caaews.model.Log;
@Path("et")
@Produces(value = {MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
public class CAAEWS {
@GET
@Produces(MediaType.APPLICATION_JSON)
public Log getJSON() {
return new Log("JSON!");
}
@GET
@Produces(MediaType.TEXT_XML)
public Log getXML() {
return new Log("XML!");
}
}
Log.java
package com.extratechnology.caaews.model;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Log {
private String log;
public Log(String log) {
this.log = log;
}
public String getLog() {
return log;
}
public void setLog(String log) {
this.log = log;
}
}
唯一让我有点好奇的是 HTTP 有两种内容类型 XML。
- text/xml
- application/xml
它在 web.xml 中是可配置的,但我必须调整注释。为什么是两个?
--
脚注:
写完这篇文章后,我发现我遇到了 HTTP 500 错误。
当您 运行 Eclipse 中的服务器时,日志似乎在某个不起眼的文件夹中:
Documents\workspace-sts-3.8.3.RELEASE\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\logs
然后我将其写入日志:
0:0:0:0:0:0:0:1 - - [25/Nov/2017:16:56:00 +0000] "GET /caaews/webapi/et.xml HTTP/1.1" 500 1082
有没有人知道如何获得更合理的日志信息?或者我需要做什么来捕获更有意义的堆栈跟踪?
看来 Log class 需要一个无参数构造函数来克服这个问题。但我承认,@peeskillet 的答案远没有那么繁琐,并且使用了 Jersey 的内置功能。
我也想知道 javax.servlet.filters 在查看示例后是否不能很好地使用 JAX-RS 2.0 here...
对于这个问题的其他相关 answers/comments,我最终实现了一个异常处理程序,因此您可以获得有关泽西岛 HTTP 500 消息的更多信息..
这是帮助指向 Log.java 需要无参数构造函数的方法的代码..
错误信息
package com.extratechnology.caaews.model;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class ErrorMessage {
private String errorMessage;
private String errorStackTrace;
private String cause;
private String causeStackTrace;
private int errorCode;
public ErrorMessage() {
}
public ErrorMessage(
String errorMessage,
String errorStackTrace,
String cause,
String causeStackTrace,
int errorCode
) {
this.errorMessage = errorMessage;
this.errorStackTrace = errorStackTrace;
this.cause = cause;
this.causeStackTrace = causeStackTrace;
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getErrorStackTrace() {
return errorStackTrace;
}
public void setErrorStackTrace(String errorStackTrace) {
this.errorStackTrace = errorStackTrace;
}
public String getCause() {
return cause;
}
public void setCause(String cause) {
this.cause = cause;
}
public String getCauseStackTrace() {
return causeStackTrace;
}
public void setCauseStackTrace(String causeStackTrace) {
this.causeStackTrace = causeStackTrace;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
}
GenericExceptionMapper.java
package com.extratechnology.caaews.exception;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import com.extratechnology.caaews.model.ErrorMessage;
@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable>{
@Override
public Response toResponse(Throwable ex) {
System.out.println("Stack Trace:");
ex.printStackTrace();
System.out.println("Cause:");
Throwable cause = ex.getCause();
if (cause != null) {
cause.printStackTrace();
}
ErrorMessage message = new ErrorMessage(
ex.getMessage(),
GenericExceptionMapper.getStackTrack(ex),
cause.getMessage(),
GenericExceptionMapper.getStackTrack(cause),
Status.INTERNAL_SERVER_ERROR.getStatusCode()
);
return Response
.status(Status.INTERNAL_SERVER_ERROR)
.entity(message)
.build();
}
private static String getStackTrack(Throwable ex) {
StringBuilder sb = new StringBuilder();
String ls = System.lineSeparator();
if (ex != null) {
StackTraceElement[] steAll = ex.getStackTrace();
for (StackTraceElement ste : steAll) {
sb.append(ste.toString());
sb.append(ls);
}
}
return sb.toString();
}
}
system.out.println 在调试时给出控制台消息,并且在出现错误时您也会在 Web 浏览器中返回有效负载。
例如:
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<errorMessage>
<cause>1 counts of IllegalAnnotationExceptions</cause>
<causeStackTrace>
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException$Builder.check(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(Unknown Source) com.sun.xml.internal.bind.v2.ContextFactory.createContext(Unknown Source) sun.reflect.GeneratedMethodAccessor20.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) java.lang.reflect.Method.invoke(Unknown Source) javax.xml.bind.ContextFinder.newInstance(Unknown Source) javax.xml.bind.ContextFinder.newInstance(Unknown Source) javax.xml.bind.ContextFinder.find(Unknown Source) javax.xml.bind.JAXBContext.newInstance(Unknown Source) javax.xml.bind.JAXBContext.newInstance(Unknown Source) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getStoredJaxbContext(AbstractJaxbProvider.java:312) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getJAXBContext(AbstractJaxbProvider.java:297) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getMarshaller(AbstractJaxbProvider.java:264) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getMarshaller(AbstractJaxbProvider.java:231) org.glassfish.jersey.jaxb.internal.AbstractRootElementJaxbProvider.writeTo(AbstractRootElementJaxbProvider.java:175) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:266) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:251) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:109) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1135) org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:662) org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:395) org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:385) org.glassfish.jersey.server.ServerRuntime.run(ServerRuntime.java:280) org.glassfish.jersey.internal.Errors.call(Errors.java:272) org.glassfish.jersey.internal.Errors.call(Errors.java:268) org.glassfish.jersey.internal.Errors.process(Errors.java:316) org.glassfish.jersey.internal.Errors.process(Errors.java:298) org.glassfish.jersey.internal.Errors.process(Errors.java:268) org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:289) org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:256) org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:703) org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:416) org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:370) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:389) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:342) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:229) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:217) org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Unknown Source)
</causeStackTrace>
<errorCode>500</errorCode>
<errorMessage>HTTP 500 Internal Server Error</errorMessage>
<errorStackTrace>
org.glassfish.jersey.jaxb.internal.AbstractRootElementJaxbProvider.writeTo(AbstractRootElementJaxbProvider.java:183) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:266) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:251) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:109) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1135) org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:662) org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:395) org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:385) org.glassfish.jersey.server.ServerRuntime.run(ServerRuntime.java:280) org.glassfish.jersey.internal.Errors.call(Errors.java:272) org.glassfish.jersey.internal.Errors.call(Errors.java:268) org.glassfish.jersey.internal.Errors.process(Errors.java:316) org.glassfish.jersey.internal.Errors.process(Errors.java:298) org.glassfish.jersey.internal.Errors.process(Errors.java:268) org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:289) org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:256) org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:703) org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:416) org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:370) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:389) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:342) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:229) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:217) org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Unknown Source)
</errorStackTrace>
</errorMessage>
即使你没有标记 jersey, your question shows you are using Jersey, so I am going to post a Jersey-specific solution. What Jersey offers is a property that you can use to set the media type mappings
ServerPropeties.MEDIA_TYPE_MAPPINGS
public static final String MEDIA_TYPE_MAPPINGS
Defines mapping of URI extensions to media types. The property is used by UriConnegFilter
. See it's javadoc for more information on media type mappings.
The property value MUST be an instance of String
, String[]
or Map<String, MediaType>
. Each String instance represents one or more uri-extension-to-media-type map entries separated by a comma (","). Each map entry is a key-value pair separated by a colon (":"). Here is an example of an acceptable String value mapping txt extension to text/plain and xml extension to application/xml:
txt : text/plain, xml : application/xml
A default value is not set.
The name of the configuration property is "jersey.config.server.mediaTypeMappings".
带有 Java 配置的示例
final Map<String, MediaType> mediaTypeMappings = new HashMap<>();
mediaTypeMappings.put("xml", MediaType.APPLICATION_XML_TYPE);
mediaTypeMappings.put("json", MediaType.APPLICATION_JSON_TYPE);
final ResourceConfig rc = new ResourceConfig()
.packages("com.example.jersey")
.property(ServerProperties.MEDIA_TYPE_MAPPINGS, mediaTypeMappings);
带有 web.xml 配置的示例
<servlet>
<servlet-name>JerseyApplication</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.example</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.mediaTypeMappings</param-name>
<param-value>xml:application/xml, json:application/json</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
我见过一个 Java RESTFUL 网络服务,它允许在 URL 中请求 content-type 并在末尾添加扩展名,例如
.xml
.json
这是我在自己的 Web 服务中努力实现的内容协商风格。
我知道 @Produces
注释,事实上一个方法可以使用 (value = {})
语法解析多个类型,通过添加一个 Accept header,比如 Postman, Chrome 分机。
但我不确定如何在一种方法中有效地提取信息,然后委托给另一种方法。
我假设 REGEX 可以与 @Path
和 @PathParam
一起使用,但我的尝试尚未取得成果。
谁能举个例子?
这是我迄今为止的尝试:
package com.extratechnology.caaews;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.extratechnology.caaews.model.Log;
@Path("et")
@Produces(MediaType.APPLICATION_JSON)
public class CAAEWS {
@GET
@Path("\.{format}")
@Produces(value = {MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
public Log getLog(
@PathParam("format") String format
){
Log result = null;
switch (format) {
case "json":
result = this.getJSON();
case "xml":
result = this.getXML();
}
return result;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Log getJSON() {
return new Log("JSON!");
}
@GET
@Produces(MediaType.TEXT_XML)
public Log getXML() {
return new Log("XML!");
}
}
package com.extratechnology.caaews.model;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Log {
private String log;
public Log(String log) {
this.log = log;
}
public String getLog() {
return log;
}
public void setLog(String log) {
this.log = log;
}
}
可以使用 Spring 工具 Suite/Eclipse 设置项目,方法是使用以下方法创建 Maven 项目(类似,但比 here circa 4:50 更新):
- org.glassfish.jersey.archetypes
- jersey.quickstart.webapp
- 2.26
然后您取消注释 pom.xml 提供的部分以启用 JSON 支持,这有效地向您的 WAR.
添加了一些 JARS我发现我也有一些讨厌的 BCEL 错误,并且不得不将一些条目附加到 catalina.properties 文件中,在键下:
tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\
....
javax.json-api-1.1.jar, javax.json.bind-api-1.0.jar, javax.json-1.1.jar, \
yasson-1.0.jar
http://localhost:18080/caaews/webapi/et
产量:
{"log":"JSON!"}
http://localhost:18080/caaews/webapi/et.xml 或
http://localhost:18080/caaews/webapi/et.json
产量:
HTTP Status 404 - Not Found
我也想知道是否有某种 HTTP 拦截器类型的方法可以解决这个问题。我的 Java 有点生疏,但它是 servlet 过滤器,还是类似于建议之前的 AOP 的东西。
感谢@user1803551 我在 switch 语句中加入了中断。
感谢@callmepills,我对代码做了一些调整。
class 级 @Path 注释现在有这个。 @Produces(value = {MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
getLog @Path 注释是“.{format}”。
为了调用和委托 getLog,您必须对 URL:
使用此语法http://localhost:18080/caaews/webapi/et
http://localhost:18080/caaews/webapi/et/.xml
http://localhost:18080/caaews/webapi/et/.json
路径中需要有一个“/”不是我想要的,所以我想我可能必须解决 servlet 过滤器而不是 @PathParam 方法..
您是否尝试过在 class 级别删除 @Path
注释?您的方法级注释将是:
@Path("et.{format}")
我认为您当前的实现正在创建一个匹配如下路径的子资源:
/et/{format}
您的 JAX-RS 代码存在几个问题:
@Path
中的正则表达式
@Path
注解的 value 仅在参数模板内部和 :
字符之后解析正则表达式。您正在尝试在参数模板 "\.{format}"
之外使用正则表达式,因此它不会将其解析为正则表达式。
路径解析
一个方法的路径包括 class 路径的部分,后面跟着它自己的路径部分。当您尝试调用 /et.{format}
时,您的代码建议路径 /et/.{format}
和 /et
,这在任何地方都没有定义,因此是 404.
这是一个可以根据您的代码运行的示例:
@Path("et")
public class Resource {
private static final String JSON = "json";
private static final String XML = "xml";
@GET
@Path(".{format:(" + JSON + "|" + XML + ")}")
@Produces(value = { MediaType.APPLICATION_JSON, MediaType.TEXT_XML }) // not XML?
public String getLog(@PathParam("format") String format) {
switch (format) {
case JSON:
this.getJSON();
break;
case XML:
this.getXML();
}
return format;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public void getJSON() {
System.out.println("in JSON");
}
@GET
@Path("otherPath")
@Produces(MediaType.APPLICATION_XML)
public void getXML() {
System.out.println("in XML");
}
}
您的有效请求现在为:
- http://localhost:18080/caaews/webapi/et(JSON 到
getJSON
) - http://localhost:18080/caaews/webapi/et/otherPath(XML 到
getXML
) - http://localhost:18080/caaews/webapi/et/.xml(XML 到
getLog
) - http://localhost:18080/caaews/webapi/et/.json(JSON 到
getLog
)
根据需要更改路径。 XML方法我用了"otherPath"
,因为它不能和空路径JSON方法冲突。我不推荐这种约定。
备注:
- 在
switch
语句中使用break
。 - 为了减少出现错误的机会,请像我对您的自定义格式类型所做的那样,对可重用字符串等使用常量。
enum
会更好。
编辑:
请求现在有一个路径 /et/<something>.{format}
。如果我们扩展路径参数的范围以包括整个段 <something>.{format}
然后以编程方式提取格式,就可以实现这一点:
@GET
@Path("{segment:[a-zA-Z0-9_]*\.(" + JSON + "|" + XML + ")}")
@Produces(value = { MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
public String getLog(@PathParam("segment") String segment) {
String format = segment.substring(segment.indexOf('.') + 1);
switch (format) {
case JSON:
this.getJSON();
break;
case XML:
this.getXML();
}
return format;
}
正则表达式 [a-zA-Z0-9_]*
表示任何字母数字或下划线一次或多次。您可以用您想要的任何限制替换该部分。请参阅 URL 规范以了解允许的字符。
当我用 Google 搜索 "servlet filter with jax-rs example" 时,this 位于列表的顶部。粗略扫了一下代码,我觉得这很符合我的需要。
这是我的解决方案(到目前为止...请参阅脚注警告)
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- This web.xml file is not required when using Servlet 3.0 container,
see implementation details http://jersey.java.net/nonav/documentation/latest/jax-rs.html -->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<filter>
<filter-name>accept-filter</filter-name>
<filter-class>com.extratechnology.filters.AcceptFilter</filter-class>
<init-param>
<param-name>xml</param-name>
<param-value>text/xml</param-value>
</init-param>
<init-param>
<param-name>json</param-name>
<param-value>application/json</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>accept-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.extratechnology.caaews</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/webapi/*</url-pattern>
</servlet-mapping>
</web-app>
AcceptFilter.java
package com.extratechnology.filters;
import java.io.IOException;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class AcceptFilter implements Filter {
private final Map<String,String> extensions = new HashMap<String,String>();
public void init(FilterConfig config) throws ServletException {
Enumeration<String> exts = config.getInitParameterNames();
while (exts.hasMoreElements()) {
String ext = exts.nextElement();
if (ext != null && !ext.isEmpty()) {
this.extensions.put(ext.toLowerCase(), config.getInitParameter(ext));
}
}
}
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest)request;
String uri = httpRequest.getRequestURI();
String ext = this.getExtension(uri);
String accept = this.extensions.get(ext);
if (accept == null) {
accept = httpRequest.getHeader("accept");
if (accept != null && accept.indexOf("text/html") > 0) {
// patch WebKit-style Accept headers by elevating "text/html"
accept = "text/html,"+accept;
request = new RequestWrapper(httpRequest, uri, accept);
}
} else {
// remove extension and remap the Accept header
uri = uri.substring(0, uri.length() - ext.length()-1);
request = new RequestWrapper(httpRequest, uri, accept);
}
// add "Vary: accept" to the response headers
HttpServletResponse httpResponse = (HttpServletResponse)response;
httpResponse.addHeader("Vary", "accept");
chain.doFilter(request, response);
}
private String getExtension(String path) {
String result = "";
int index = path.lastIndexOf('.');
if (!(index < 0 || path.lastIndexOf('/') > index)) {
result = path.substring(index+1).toLowerCase();
}
return result;
}
private static class RequestWrapper extends HttpServletRequestWrapper {
private final String uri;
private final String accept;
public RequestWrapper(HttpServletRequest request, String uri, String accept) {
super(request);
this.uri = uri;
this.accept = accept;
}
@Override
public String getRequestURI() {
return this.uri;
}
@Override
public Enumeration<String> getHeaders(String name) {
Enumeration<String> result;
if ("accept".equalsIgnoreCase(name)) {
Vector<String> values = new Vector<String>(1);
values.add(this.accept);
result = values.elements();
} else {
result = super.getHeaders(name);
}
return result;
}
}
}
CAAEWS.java
package com.extratechnology.caaews;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.extratechnology.caaews.model.Log;
@Path("et")
@Produces(value = {MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
public class CAAEWS {
@GET
@Produces(MediaType.APPLICATION_JSON)
public Log getJSON() {
return new Log("JSON!");
}
@GET
@Produces(MediaType.TEXT_XML)
public Log getXML() {
return new Log("XML!");
}
}
Log.java
package com.extratechnology.caaews.model;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Log {
private String log;
public Log(String log) {
this.log = log;
}
public String getLog() {
return log;
}
public void setLog(String log) {
this.log = log;
}
}
唯一让我有点好奇的是 HTTP 有两种内容类型 XML。
- text/xml
- application/xml
它在 web.xml 中是可配置的,但我必须调整注释。为什么是两个?
--
脚注:
写完这篇文章后,我发现我遇到了 HTTP 500 错误。 当您 运行 Eclipse 中的服务器时,日志似乎在某个不起眼的文件夹中:
Documents\workspace-sts-3.8.3.RELEASE\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\logs
然后我将其写入日志:
0:0:0:0:0:0:0:1 - - [25/Nov/2017:16:56:00 +0000] "GET /caaews/webapi/et.xml HTTP/1.1" 500 1082
有没有人知道如何获得更合理的日志信息?或者我需要做什么来捕获更有意义的堆栈跟踪?
看来 Log class 需要一个无参数构造函数来克服这个问题。但我承认,@peeskillet 的答案远没有那么繁琐,并且使用了 Jersey 的内置功能。
我也想知道 javax.servlet.filters 在查看示例后是否不能很好地使用 JAX-RS 2.0 here...
对于这个问题的其他相关 answers/comments,我最终实现了一个异常处理程序,因此您可以获得有关泽西岛 HTTP 500 消息的更多信息..
这是帮助指向 Log.java 需要无参数构造函数的方法的代码..
错误信息
package com.extratechnology.caaews.model;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class ErrorMessage {
private String errorMessage;
private String errorStackTrace;
private String cause;
private String causeStackTrace;
private int errorCode;
public ErrorMessage() {
}
public ErrorMessage(
String errorMessage,
String errorStackTrace,
String cause,
String causeStackTrace,
int errorCode
) {
this.errorMessage = errorMessage;
this.errorStackTrace = errorStackTrace;
this.cause = cause;
this.causeStackTrace = causeStackTrace;
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getErrorStackTrace() {
return errorStackTrace;
}
public void setErrorStackTrace(String errorStackTrace) {
this.errorStackTrace = errorStackTrace;
}
public String getCause() {
return cause;
}
public void setCause(String cause) {
this.cause = cause;
}
public String getCauseStackTrace() {
return causeStackTrace;
}
public void setCauseStackTrace(String causeStackTrace) {
this.causeStackTrace = causeStackTrace;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
}
GenericExceptionMapper.java
package com.extratechnology.caaews.exception;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import com.extratechnology.caaews.model.ErrorMessage;
@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable>{
@Override
public Response toResponse(Throwable ex) {
System.out.println("Stack Trace:");
ex.printStackTrace();
System.out.println("Cause:");
Throwable cause = ex.getCause();
if (cause != null) {
cause.printStackTrace();
}
ErrorMessage message = new ErrorMessage(
ex.getMessage(),
GenericExceptionMapper.getStackTrack(ex),
cause.getMessage(),
GenericExceptionMapper.getStackTrack(cause),
Status.INTERNAL_SERVER_ERROR.getStatusCode()
);
return Response
.status(Status.INTERNAL_SERVER_ERROR)
.entity(message)
.build();
}
private static String getStackTrack(Throwable ex) {
StringBuilder sb = new StringBuilder();
String ls = System.lineSeparator();
if (ex != null) {
StackTraceElement[] steAll = ex.getStackTrace();
for (StackTraceElement ste : steAll) {
sb.append(ste.toString());
sb.append(ls);
}
}
return sb.toString();
}
}
system.out.println 在调试时给出控制台消息,并且在出现错误时您也会在 Web 浏览器中返回有效负载。
例如:
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<errorMessage>
<cause>1 counts of IllegalAnnotationExceptions</cause>
<causeStackTrace>
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException$Builder.check(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(Unknown Source) com.sun.xml.internal.bind.v2.ContextFactory.createContext(Unknown Source) sun.reflect.GeneratedMethodAccessor20.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) java.lang.reflect.Method.invoke(Unknown Source) javax.xml.bind.ContextFinder.newInstance(Unknown Source) javax.xml.bind.ContextFinder.newInstance(Unknown Source) javax.xml.bind.ContextFinder.find(Unknown Source) javax.xml.bind.JAXBContext.newInstance(Unknown Source) javax.xml.bind.JAXBContext.newInstance(Unknown Source) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getStoredJaxbContext(AbstractJaxbProvider.java:312) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getJAXBContext(AbstractJaxbProvider.java:297) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getMarshaller(AbstractJaxbProvider.java:264) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getMarshaller(AbstractJaxbProvider.java:231) org.glassfish.jersey.jaxb.internal.AbstractRootElementJaxbProvider.writeTo(AbstractRootElementJaxbProvider.java:175) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:266) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:251) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:109) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1135) org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:662) org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:395) org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:385) org.glassfish.jersey.server.ServerRuntime.run(ServerRuntime.java:280) org.glassfish.jersey.internal.Errors.call(Errors.java:272) org.glassfish.jersey.internal.Errors.call(Errors.java:268) org.glassfish.jersey.internal.Errors.process(Errors.java:316) org.glassfish.jersey.internal.Errors.process(Errors.java:298) org.glassfish.jersey.internal.Errors.process(Errors.java:268) org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:289) org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:256) org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:703) org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:416) org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:370) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:389) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:342) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:229) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:217) org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Unknown Source)
</causeStackTrace>
<errorCode>500</errorCode>
<errorMessage>HTTP 500 Internal Server Error</errorMessage>
<errorStackTrace>
org.glassfish.jersey.jaxb.internal.AbstractRootElementJaxbProvider.writeTo(AbstractRootElementJaxbProvider.java:183) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:266) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:251) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:109) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1135) org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:662) org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:395) org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:385) org.glassfish.jersey.server.ServerRuntime.run(ServerRuntime.java:280) org.glassfish.jersey.internal.Errors.call(Errors.java:272) org.glassfish.jersey.internal.Errors.call(Errors.java:268) org.glassfish.jersey.internal.Errors.process(Errors.java:316) org.glassfish.jersey.internal.Errors.process(Errors.java:298) org.glassfish.jersey.internal.Errors.process(Errors.java:268) org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:289) org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:256) org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:703) org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:416) org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:370) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:389) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:342) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:229) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:217) org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Unknown Source)
</errorStackTrace>
</errorMessage>
即使你没有标记 jersey, your question shows you are using Jersey, so I am going to post a Jersey-specific solution. What Jersey offers is a property that you can use to set the media type mappings
ServerPropeties.MEDIA_TYPE_MAPPINGS
public static final String MEDIA_TYPE_MAPPINGS
Defines mapping of URI extensions to media types. The property is used by
UriConnegFilter
. See it's javadoc for more information on media type mappings.The property value MUST be an instance of
String
,String[]
orMap<String, MediaType>
. Each String instance represents one or more uri-extension-to-media-type map entries separated by a comma (","). Each map entry is a key-value pair separated by a colon (":"). Here is an example of an acceptable String value mapping txt extension to text/plain and xml extension to application/xml:txt : text/plain, xml : application/xml
A default value is not set.
The name of the configuration property is "jersey.config.server.mediaTypeMappings".
带有 Java 配置的示例
final Map<String, MediaType> mediaTypeMappings = new HashMap<>();
mediaTypeMappings.put("xml", MediaType.APPLICATION_XML_TYPE);
mediaTypeMappings.put("json", MediaType.APPLICATION_JSON_TYPE);
final ResourceConfig rc = new ResourceConfig()
.packages("com.example.jersey")
.property(ServerProperties.MEDIA_TYPE_MAPPINGS, mediaTypeMappings);
带有 web.xml 配置的示例
<servlet>
<servlet-name>JerseyApplication</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.example</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.mediaTypeMappings</param-name>
<param-value>xml:application/xml, json:application/json</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>