Newbie Java EE, Json webservice getting a HTTP 404, using Jersey SDK - java

Just started to write my JSON webservices for a carpool engine. I am getting a HTTP 404 error as I try to write my registration API's.
This is where my problem is
"http://localhost:8081/mCruiseOnCarPool4All/carpool4all/Registration"
HTTP/1.1 405 Method Not Allowed
"http://localhost:8081/mCruiseOnCarPool4All/carpool4all/Registration/Request"
HTTP/1.1 500 Internal Server Error
"http://localhost:8081/mCruiseOnCarPool4All/Registration/Request"
HTTP/1.1 404 Not Found
"http://localhost:8081/mCruiseOnCarPool4All/Registration"
HTTP/1.1 404 Not Found
I know I am missing something really silly here.
Web.xml (Jersey Library)
<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.mcruiseon.carpool4all</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/carpool4all/*</url-pattern>
RegistrationService.java, only coded the Post method. I am returning errors on all exceptions and ok with sessionkey when successful. You can ignore the code in the post method, I just wanted to share so that you understand my error handling.
package com.mcruiseon.carpool4all;
#Path("/Registration")
public class RegistrationService {
private ClientSession clientSession ;
private String sessionKey ;
private SessionManager sessionManager ;
#Context
UriInfo uriInfo;
#Context
Request request;
#POST
#Path ("Request")
#Consumes({ MediaType.APPLICATION_JSON })
public Response post(JAXBElement<AMessageStrategy> element) {
try {
clientSession = new ClientSession(God.mCruiseOnServer) ;
} catch (InvalidServerDNSorIPException e) {
e.printStackTrace();
return Response.serverError().build() ;
}
sessionKey = sessionManager.setClientSession(clientSession) ;
clientSession.setSessionKey(sessionKey) ;
clientSession.getSendQueue().sendRequest(element.getValue()) ;
try {
clientSession.waitAndGetResponse(element.getValue()) ;
} catch (WaitedLongEnoughException e) {
return Response.serverError().build() ;
} catch (UnableToResolveResponseException e) {
return Response.serverError().build() ;
}
return Response.ok(sessionKey).build();
}
}
Junit test case (removed all the HttpConnection code)
ClientIdentityConcrete clientIdentity = new ClientIdentityConcrete("username", "password", "secretkey") ;
RegistrationRequest register = new RegistrationRequest(clientIdentity);
String jsonStr = mapper.writeValueAsString(clientIdentity);
HttpPost request = new HttpPost("http://localhost:8081/mCruiseOnCarPool4All/Registration/Request");

The relevant connection is /mCruiseOnCarPool4All/carpool4all/Registration/Request
and it return a 500 error so you must have an error stacktrace on your server console.
The other URLs that you're showing are hitting 404 cause the URLs are not pointing to your Jersey servlet which seemed to be mapped to /carpool4all
Your URL pattern is :
<host>/<app>/<jerseyservlet>/<xml resource>/<method path>
with
- host = localhost:8081/ (obviously)
- app = mCruiseOnCarPool4All
- jerseyservlet = carpool4all
- xml resource = Registration
- method path = Request

Related

HTTP 403 Forbidden Jersey on Put Request

I am facing an 'HTTP 403 Forbidden' error while trying to consume PUT request of a restful resource from an angular client. I created this restful resource using jersey and I am using tomcat 7 as application server.
Here is my resource code:
#Path("/doc")
public class DocResource {
#PUT
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON)
#Path("file/upload")
public Response uploadFile(MultipartBody body, #QueryParam("ID") long ID) {
try {
Attachment attachment = body.getAttachment("file");
MultivaluedMap<String, String> headers = attachment.getHeaders();
String fileName = getFileName(headers);
DataHandler dataHandler = attachment.getDataHandler();
InputStream inputStream = dataHandler.getInputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] tmp = new byte[4096];
int ret = 0;
while ((ret = inputStream.read(tmp)) > 0) {
bos.write(tmp, 0, ret);
}
// TODO - Save contents as process attachment
byte[] contents = bos.toByteArray();
return Response.ok(getDocumentService().createAttachment(ID, fileName, contents, attachment.getContentType()), MediaType.APPLICATION_JSON).build();
} catch (Exception e) {
return handleException(e, "failed to upload Attachement");
}
}
}
Here is my angular js snippet
this.uploadFile = function uploadFile(callback, ID, file) {
var baseRestURL="http://localhost:8080/rest/doc"
// resource query
var query ;
// create form data
var formData = new FormData();
formData.append('file', file);
// set up the resource
var resource = $resource(baseRestURL + '/file/upload', {
ID: ID
}, {
'ID': ID,
'upload': {
method: 'PUT',
headers: {
'Content-Type': 'multipart/form-data'
}
}
});
resource.upload(query, formData).$promise.then(function success(response) {
if (callback) callback(response);
}, function error() {
//TODO handle error
});
};
I want to notice that other type of http calls such as DELETE, POST and GET are working properly. I have only problems with PUT calls.
I had a similar issue, but both DELETE and PUT were not working for me - returned HTTP 403 error. After I searched I stumbled across this link, and it pointed out where I went wrong.
I had a CORS filter added in my web.xml as part of some other R&D i was doing, and forgot to remove it.
This was from the original web.xml
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>
io.swagger.jaxrs.listing,
com.xxx.yyy.rest</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>
org.glassfish.jersey.jackson.JacksonFeature;
org.apache.catalina.filters.CorsFilter
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Once I removed the org.apache.catalina.filters.CorsFilter it started to work for me!
Hope this helps!

How to read JSON data in RestWebservice from AJAX request

I am trying to send a JSON string from a HTML page using AJAX call to a RESTWebservice. The methods in the server gets invoked however I am not able to retrieve the JSON data that I have set in the browser through AJAX call. I am usng jersy for the REST services.
Here is my HTML code.
<body>
<script type="text/javascript">
var userConfig = {};
userConfig.user = "arin_12";
userConfig.fullName = "Arindam";
var data = JSON.stringify(userConfig);
alert(data);
var req = new XMLHttpRequest();
req.open('POST', 'http://localhost:8080/LiveHive2/rest/hello', true);
req.setRequestHeader('Content-Type','application/json;charset=UTF-8');
req.onreadystatechange = function () {
if(req.readyState === 4 && req.status === 200) {
if(req.responseText) {
alert('The saving of data is ' + req.responseText);
}
}
}
req.send(data);
</script>
index page
</body>
Here is my JavaCode in RestWebservice.
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public String sayJSONHello2(UserConfig uc) {
System.out.println("req" + uc);
return "{\"Name\":\"Arindam\"}";
}
Web.xml looks like this.
<servlet>
<servlet-name>JerseyRESTService</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.vogella.jersey.first</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
So the body of the POST request will be
{
"user": "arin_12",
"fullName": "Arindam"
}
Write a Java class this JSON can be mappend to.
public class UserConfig {
private String user;
private String fullName;
// Constructor, Getter, Setter, ...
}
Then JAX-RS allows you to automatically convert the JSON to a class instance.
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public String sayJSONHello2(UserConfig userConfig) {
System.out.println("got UserConfig: " + userConfig);
return "{\"Name\":\"Arindam\"}";
}
It is also possible to let JAX-RS handle the mapping of a result to JSON. Let's write a second class for the response.
public class HelloResponse {
private String name;
// Constructor, Getter, Setter, ...
}
Change your JAX-RS method to return an instance of this class.
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public HelloResponse sayJSONHello2(UserConfig userConfig) {
System.out.println("got UserConfig: " + userConfig);
return new HelloResponse(userConfig.getName());
}

ajax call to java servlet results in 404 [duplicate]

This question already has answers here:
HTTP Status 404 - Servlet [ServletName] is not available
(4 answers)
Closed last year.
I'm trying to get a web page to send JSON data to a java servlet via a jQuery ajax POST.
I've already checked everything I could think of, but I still can't figure out why I keep getting a 404.
Even more confusing is that other calls to the same context path work correctly.
My web.xml
<web-app>
<servlet>
<servlet-name>Controller</servlet-name>
<servlet-class>com.vibridi.klyr.servlet.Controller</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>CustomerServlet</servlet-name>
<servlet-class>com.vibridi.klyr.servlet.CustomerServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Controller</servlet-name>
<url-pattern>/klyr</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Controller</servlet-name>
<url-pattern>/home</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>CustomerServlet</servlet-name>
<url-pattern>/klyr/customer/*</url-pattern>
</servlet-mapping>
My ajax call:
$.ajax({
url: "customer/save",
type: "POST",
data: JSON.stringify(o),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(obj) {
alert('Customer saved');
},
error: function(obj) {
alert('Error!');
}
});
My servlet:
public class CustomerServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static Logger logger = Logger.getLogger("KLYR_LOGGER");
private CustomerManager manager;
public void init(ServletConfig sconfig) throws ServletException {
super.init(sconfig);
manager = new CustomerManager();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//stuff
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
response.setContentType("application/json;charset=utf-8");
try {
StringBuffer sb = new StringBuffer();
String line = null;
BufferedReader reader = request.getReader();
while((line = reader.readLine()) != null) {
sb.append(line);
}
manager.saveCustomer(sb.toString());
} catch(Exception e) {
logger.log(Level.SEVERE, "Data processing failure: " + e.getMessage());
out.write(Convertor.createBaseJSON(JSONType.E).toString());
out.close();
}
out.write(Convertor.createBaseJSON(JSONType.S).toString());
out.close();
}}
}
I can see from the Chrome's debugger tools that the call is properly directed to http://localhost:8080/klyr/customer/save but it 404's, whereas http://localhost:8080/klyr does not.
Thanks a lot!
EDIT:
I've tried to switch the servlet mappings over, i.e. /klyr (the working one) on CustomerServlet and /customer/save on Controller, but nothing happens, in fact when I call /klyr from the browser bar instead of seeing the response from CustomerServlet.doGet I still see the welcome page as if Controller.doGet fired. It looks like tomcat isn't reloading the web.xml file even if I restart it. Any ideas?
This is obvious because your CustomerServlet does not bind to $.ajax({url: "customer/save", ... so it won't work, your should change the below code :
<servlet-mapping>
<servlet-name>CustomerServlet</servlet-name>
<url-pattern>/klyr/customer/*</url-pattern>
</servlet-mapping>
to something like:
<servlet-mapping>
<servlet-name>CustomerServlet</servlet-name>
<url-pattern>/customer/save</url-pattern>
</servlet-mapping>
in order to solve the problem ~
I've eventually found the culprit, gonna post it here as a reference for other people.
Both servlets mapped in my web.xml are loaded on startup.
The first servlet attempted to read a config file from an incorrect path inside its init() method, but couldn't find it and threw an exception. The Catalina startup routine exited before it could load the second servlet, hence the 404 error.

How to turn on tracing in Jersey WS calls, when using Guice servlet configuation

I'm trying to turn on tracing to debug a Jersey WS call. I tried adding the init-param described here, both in my Guice servlet config and in the web.xml, but can't seem to get it to work.
Here's what the Guice servlet config looks like:
public class GuiceServletConfig extends GuiceServletContextListener
{
private static final Logger log = LoggerFactory.getLogger(GuiceServletConfig.class);
public GuiceServletConfig()
{
super();
log.debug("creating GuiceServletConfig");
}
private static class ServletModule extends JerseyServletModule {
private InputStream getInputStream() throws FileNotFoundException {
File file = new File("tmp");
String pathToTempFile = file.getAbsolutePath();
log.debug("path to config: {}", pathToTempFile);
String pathWithoutTmp = pathToTempFile.substring(0,
pathToTempFile.indexOf(File.separator + "tmp"));
StringBuilder stringBldr = new StringBuilder(pathWithoutTmp)
.append(File.separator).append("extensions")
.append(File.separator).append("__lib__")
.append(File.separator).append("gelato.config.properties");
log.debug("loading guice properties from: {}", stringBldr.toString());
return new FileInputStream(new File(stringBldr.toString()));
}
#Override
protected void configureServlets() {
Properties properties = new Properties();
try {
InputStream is = getInputStream();
properties.load(is);
Names.bindProperties(binder(), properties);
} catch (Exception ex) {
log.error("Error binding properties: {}", ex.toString());
}
// Must configure at least one JAX-RS resource or the
// server will fail to start.
// Must configure at least one JAX-RS resource or the
// server will fail to start.
bind(UserResource.class);
bind(PlayersManager.class).to(GelatoPlayersManager.class);
bind(MessageHandler.class).to(SFSMessageHandler.class);
Map<String, String> params = new HashMap<String, String>();
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "org.buffalo.gelato.resources");
// Route all requests through GuiceContainer
params.put("com.sun.jersey.config.feature.Trace", "true");
serve("/rest/*").with(GuiceContainer.class, params);
}
}
and I also tried just putting it in the web.xml, but I suppose this is ignored, since I'm configured Jersey via Guice.
<servlet>
<servlet-name>Jersey REST Service for value codes</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.feature.Trace</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
If you want to see log message in your server log (your first configuration of tracing is the right one), you need to add ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS and/or ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS properties to your configuration, like:
params.put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, com.sun.jersey.api.container.filter.LoggingFilter.class);
params.put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, com.sun.jersey.api.container.filter.LoggingFilter.class);
See JavaDoc of container LoggingFilter.
The code as posted works fine. I was looking for trace messages in the logs. I didn't realize the trace messages are sent back in the response header as described here.
Same thing but with a ResourceConfig file.
In the web.xml:
<servlet>
<servlet-name>com.example.myapp</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.example.myapp.JerseyApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>com.example.myapp</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
And in the code :
package com.example.myapp;
import org.glassfish.jersey.server.ResourceConfig;
import java.util.HashMap;
import java.util.Map;
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
System.out.println("Processing Jersey2 configuration");
// Tracing properties (modification of the response HTTP headers)
Map<String, Object> params = new HashMap<String, Object>();
params.put("jersey.config.server.tracing.type","ALL");
params.put("jersey.config.server.tracing.threshold","TRACE");
addProperties(params);
// Scan packages for resources
packages(true,"com.example.myapp.resources");
}
}
And just add the properties as stated by Michal Gajdos above if you want server's side tracing.

How to get form parameters in request filter

I'm trying to get the form parameters of a request in a request filter:
#Override
public ContainerRequest filter(final ContainerRequest request) {
final Form formParameters = request.getFormParameters();
//logic
return request;
}
However, the form always seems to be empty. The HttpRequestContext.getFormParameters() documentation says:
Get the form parameters of the request entity.
This method will ensure that the request entity is buffered such that it may be consumed by the applicaton.
Returns:
the form parameters, if there is a request entity and the content type is "application/x-www-form-urlencoded", otherwise an instance containing no parameters will be returned.
My resource is annotated with #Consumes("application/x-www-form-urlencoded"), although it won't have been matched until after the request filter - is that why this isn't working?
I tried doing some research but couldn't find any conclusive evidence of whether this is possible. There was this 4-year old discussion, in which Paul Sandoz says:
If you are working in Jersey filters or with the HttpRequestContext you can get the form parameters as follows: [broken link to Jersey 1.1.1 HttpRequestContext.getFormParameters]
I also found this 3-year-old discussion about how to get multipart/form-data form fields in a request filter. In it, Paul Sandoz uses the following code:
// Buffer
InputStream in = request.getEntityInputStream();
if (in.getClass() != ByteArrayInputStream.class) {
// Buffer input
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ReaderWriter.writeTo(in, baos);
} catch (IOException ex) {
throw new ContainerException(ex);
}
in = new ByteArrayInputStream(baos.toByteArray());
request.setEntityInputStream(in);
}
// Read entity
FormDataMultiPart multiPart = request.getEntity(FormDataMultiPart.class);
I tried emulating that approach for Form instead, but the result of request.getEntityInputStream() is always an empty stream. And looking at the source of getFormParameters, that method is in fact doing the same thing already:
#Override
public Form getFormParameters() {
if (MediaTypes.typeEquals(MediaType.APPLICATION_FORM_URLENCODED_TYPE, getMediaType())) {
InputStream in = getEntityInputStream();
if (in.getClass() != ByteArrayInputStream.class) {
// Buffer input
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
ReaderWriter.writeTo(in, byteArrayOutputStream);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
in = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
setEntityInputStream(in);
}
ByteArrayInputStream byteArrayInputStream = (ByteArrayInputStream) in;
Form f = getEntity(Form.class);
byteArrayInputStream.reset();
return f;
} else {
return new Form();
}
}
I can't figure out what's slurping up the entity input stream before I get to it. Something in Jersey must be consuming it because the form params are later passed into the resource method. What am I doing wrong here, or is this impossible (and why)?
EDIT: Here's an example of a request being sent:
POST /test/post-stuff HTTP/1.1
Host: local.my.application.com:8443
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
form_param_1=foo&form_param_2=bar
Here's the (somewhat redundant) request logging:
INFO: 1 * Server in-bound request
1 > POST https://local.my.application.com:8443/test/post-stuff
1 > host: local.my.application.com:8443
1 > connection: keep-alive
1 > content-length: 33
1 > cache-control: no-cache
1 > origin: chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm
1 > user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
1 > content-type: application/x-www-form-urlencoded
1 > accept: */*
1 > accept-encoding: gzip,deflate,sdch
1 > accept-language: en-US,en;q=0.8
1 > cookie: [omitted]
1 >
Here are the response headers of that request, including the Jersey Trace:
Content-Type →application/json;charset=UTF-8
Date →Fri, 09 Aug 2013 18:00:17 GMT
Location →https://local.my.application.com:8443/test/post-stuff/
Server →Apache-Coyote/1.1
Transfer-Encoding →chunked
X-Jersey-Trace-000 →accept root resource classes: "/post-stuff"
X-Jersey-Trace-001 →match path "/post-stuff" -> "/post\-stuff(/.*)?", [...], "(/.*)?"
X-Jersey-Trace-002 →accept right hand path java.util.regex.Matcher[pattern=/post\-stuff(/.*)? region=0,11 lastmatch=/post-stuff]: "/post-stuff" -> "/post-stuff" : ""
X-Jersey-Trace-003 →accept resource: "post-stuff" -> #Path("/post-stuff") com.application.my.jersey.resource.TestResource#7612e9d2
X-Jersey-Trace-004 →match path "" -> ""
X-Jersey-Trace-005 →accept resource methods: "post-stuff", POST -> com.application.my.jersey.resource.TestResource#7612e9d2
X-Jersey-Trace-006 →matched resource method: public javax.ws.rs.core.Response com.application.my.jersey.resource.TestResource.execute(java.lang.String,java.lang.String)
X-Jersey-Trace-007 →matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider#b98df1f
X-Jersey-Trace-008 →matched message body writer: java.lang.String#f62, "application/json" -> com.sun.jersey.core.impl.provider.entity.StringProvider#1c5ddffa
Here is the (unremarkable) servlet config:
<servlet>
<servlet-name>jersey</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.application.my.jersey</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>com.application.my.jersey.MyFilterFactory</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.feature.Trace</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Here's the example resource:
#Path("/post-stuff")
#Produces(MediaType.APPLICATION_JSON)
public final class TestResource {
#POST
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response execute(
#FormParam("form_param_1") final String formParam1,
#FormParam("form_param_2") final String formParam2
) {
return Response.created(URI.create("/")).entity("{}").build();
}
}
I'm using Jersey 1.17.
For those interested, I'm trying to roll my own required parameter validation, as described in JERSEY-351. My solution here worked for query, cookie, and header params - form params are holding out on me.
This was a tricky one. I'd removed other Jersey filters to eliminate them from the problem, but missed a plain servlet filter hiding at the bottom of web.xml:
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.application.my.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Removing this filter fixed the issue - form params showed up in the Jersey filter. But why? I dug deeper, narrowing down the problem to a single statement in MyFilter:
request.getParameter("some_param")
I tried to simplify the problem even more by removing MyFilter and making the same call in the Jersey filter (by injecting HttpServletRequest) - but the form parameters still showed up. The issue appears to happen specifically when calling getParameter on the org.apache.catalina.connector.RequestFacade instance that gets passed into javax.servlet.Filter.doFilter. So is this in fact a Tomcat bug?
The documentation of ServletRequest.getParameter says:
If the parameter data was sent in the request body, such as occurs with an HTTP POST request, then reading the body directly via getInputStream() or getReader() can interfere with the execution of this method.
So maybe the reverse is true too - that calling getParameter might be allowed to interfere with the entity input stream? It's unclear to me whether the method's contract allows for this behavior, and whether it indicates a bug in Tomcat, Jersey, or neither.
Anyway, that old filter wasn't actually needed so my issue is solved but just removing it.
Here's a full reproduction of the problem (Tomcat 7.0):
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>test</display-name>
<servlet>
<servlet-name>jersey</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.application.my</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>com.application.my.TestFilterFactory</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.feature.Trace</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>servletFilter</filter-name>
<filter-class>com.application.my.TestServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>servletFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
TestServletFilter.java:
package com.application.my;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public final class TestServletFilter implements Filter {
#Override
public void init(FilterConfig config) { }
#Override
public void doFilter(
final ServletRequest request,
final ServletResponse response,
final FilterChain chain
) throws IOException, ServletException {
System.out.println("calling getParameter on " + request.getClass().getName());
request.getParameter("blah");
chain.doFilter(request, response);
}
#Override
public void destroy() { }
}
TestFilterFactory.java:
package com.application.my;
import java.util.Collections;
import java.util.List;
import com.sun.jersey.api.model.AbstractMethod;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilter;
import com.sun.jersey.spi.container.ResourceFilterFactory;
public final class TestFilterFactory implements ResourceFilterFactory {
#Override
public List<ResourceFilter> create(final AbstractMethod method) {
return Collections.<ResourceFilter>singletonList(new ResourceFilter() {
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(final ContainerRequest request) {
System.out.println("form: " + request.getFormParameters());
return request;
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return null;
}
});
}
}
TestResource.java:
package com.application.my;
import java.net.URI;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
#Path("/post-stuff")
#Produces(MediaType.APPLICATION_JSON)
public final class TestResource {
#POST
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response execute(
#FormParam("form_param_1") final String formParam1,
#FormParam("form_param_2") final String formParam2
) {
System.out.println("form param_1: " + formParam1);
System.out.println("form param_2: " + formParam2);
return Response.created(URI.create("/")).entity("{}").build();
}
}
Make sure your ResourceFilterFactory creates an instance of ResourceFilter for the TestResource#execute method, which then creates a ContainerRequestFilter instance:
public class MyFilterFactory implements ResourceFilterFactory {
#Override
public List<ResourceFilter> create(final AbstractMethod am) {
return new ArrayList<ResourceFilter>() {{
add(new ResourceFilter() {
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(final ContainerRequest request) {
System.out.println(request.getFormParameters());
return request;
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return null;
}
});
}};
}
}
From the trace you have provided I am not sure whether your ContainerRequestFilter is called. There should be one more trace header containing something like this:
→matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider#b98df1f
The whole trace from my test:
HTTP/1.1 201 Created
Location: http://localhost:8080/helloworld-webapp/helloworld/
Content-Type: text/plain
X-Jersey-Trace-000: accept root resource classes: "/helloworld"
X-Jersey-Trace-001: match path "/helloworld" -> "/application\.wadl(/.*)?", "/helloworld(/.*)?"
X-Jersey-Trace-002: accept right hand path java.util.regex.Matcher[pattern=/helloworld(/.*)? region=0,11 lastmatch=/helloworld]: "/helloworld" -> "/helloworld" : ""
X-Jersey-Trace-003: accept resource: "helloworld" -> #Path("/helloworld") com.sun.jersey.samples.helloworld.resources.HelloWorldResource#7449df0f
X-Jersey-Trace-004: match path "" -> ""
X-Jersey-Trace-005: accept resource methods: "helloworld", POST -> com.sun.jersey.samples.helloworld.resources.HelloWorldResource#7449df0f
X-Jersey-Trace-006: matched resource method: public javax.ws.rs.core.Response com.sun.jersey.samples.helloworld.resources.HelloWorldResource.execute(java.lang.String,java.lang.String)
X-Jersey-Trace-007: matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider#6bc1b916
X-Jersey-Trace-008: matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider#6bc1b916
X-Jersey-Trace-009: matched message body writer: java.lang.String#f62, "text/plain" -> com.sun.jersey.core.impl.provider.entity.StringProvider#4aae6c4e
Transfer-Encoding: chunked
Server: Jetty(6.1.24)
EDIT 1:
Enable request LoggingFilter:
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
<param-value>com.sun.jersey.api.container.filter.LoggingFilter</param-value>
</init-param>
EDIT 2:
Also make sure no other Servlet or Jersey filter has read the InputStream before. In such a case the entity input stream may no longer be available (but you can still inject #FormParam into your resource method - as in this case).

Categories