I'm a university student, currently developing an Android app for a module. Working on connecting it to a server to perform logins, etc. The department have given us a server to use and instructed us to use Servlets. I would rather do it a Restful manner, seeming as it's an industry standard. Here is the code I have written so far:
import javax.ws.rs.QueryParam;
/**
*
* #author Tom
*/
public class Login {
public boolean doLogin(#QueryParam("email") String email) {
return checkCredentials(email);
}
private boolean checkCredentials(String email){
boolean result = false;
if (email != ""){
try {
result = DBConnection.checkLogin(email);
} catch (Exception e) {
result = false;
}
} else {
result = false;
}
return result;
}
}
I wrote another class, DBConnection, but this seems to work ok (using JDBC to connect to the MySQL database).
The problem I'm having, is that when I run the webserver (using Jetty, and ant is the build tool) and try to access the page on the server through my browser, it just gives me a 503, Servlet not initialised error. I assume this is because I'm not extending the HttpServlet class? Here is some example Servlet code they gave us:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class Product extends HttpServlet
{
protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException
{
httpServletResponse.setContentType("text/plain");
PrintWriter out = httpServletResponse.getWriter();
out.println("Hello");
out.close();
}
}
They've given us a file called JettyStart.java, which starts the web server when you run ant:
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.ServletHttpContext;
public class JettyStart
{
public static void main(String[] args) throws Exception
{
//A server running at http://localhost:8085 is created
Server server = new Server();
server.addListener(":8085");
ServletHttpContext context = (ServletHttpContext) server.getContext("/");
context.addServlet("/path/to/Login", "package.name.Login");
server.start();
}
}
So how would I go about integrating Servlets into my Restful approach to communicating with the server/database? Or am I doing it all wrong?
I'm not sure if this is an answer, but you may have better luck making your project maven-based and using the jetty-maven-plugin. I have, personally. It's easy to set up if you have an IDE which can produce a simple maven archetype. You just drop the plugin into your pom and run mvn jetty:run from the command line. Wiring up the JAX-RS web services isn't too complicated, you just give them the correct annotations like so:
package com.my.project.services;
// imports here
#Path("/login")
public class Login extends HttpServlet {
#GET
#Produces({"text/html", MediaType.TEXT_HTML})
public String getLoginInfo(#QueryParam("email") String email) {
// ...
}
}
And that should be enough to get them picked up by the jetty servlet container as long as your web.xml is set up properly. If you use a maven webapp archetype this may be done for you, otherwise you'll have to poke around a bit, but if it helps this is what (the relevant parts of) my web.xml looks like in one of my projects:
<web-app>
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.my.project.services</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>rest</servlet-name>
<url-pattern>/myproject/rest/*</url-pattern>
</servlet-mapping>
</web-app>
The mapping here is saying take the servlet-name "rest" and map it to the package com.my.project.services where all my JAX-RS services live, with the #Path value appended to the end (so the login service above would be located at /myproject/rest/login). You can set the mapping paths up any way you want.
edit: should mention my project is also using Jersey. Here's a good guide to setting up a project like this: http://crunchify.com/how-to-build-restful-service-with-java-using-jax-rs-and-jersey/
Related
I am making a distributed system as a school project and I need to have a REST service. This will be a simple service with a login/register function and some information transfer.
I have made the REST API in Java in NetBeans. It works fine locally, but I am having difficulties to put it on my AWS server. I have no experience with servers, so I don't really know how it works. I thought that it should easy to get the service up and running on a server.
So far I have used this guide for the REST and tried to deploy the war-file with Elastic Beanstalk.
My Java code:
ApplicationConfig.java
package dk.dtu.ds.login;
import java.util.Set;
import javax.ws.rs.core.Application;
#javax.ws.rs.ApplicationPath("CoL")
public class ApplicationConfig extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new java.util.HashSet<>();
addRestResourceClasses(resources);
return resources;
}
/**
* Do not modify addRestResourceClasses() method.
* It is automatically populated with
* all resources defined in the project.
* If required, comment out calling this method in getClasses().
*/
private void addRestResourceClasses(Set<Class<?>> resources) {
resources.add(dk.dtu.ds.login.Login.class);
}
}
Login.java
package dk.dtu.ds.login;
import cleanoutloudserver.ICleanOutLoud;
import java.net.MalformedURLException;
import java.net.URL;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
#Path("login")
public class Login {
// HTTP Get Method
#GET
#Path("dologin")
// Produces JSON as response
#Produces(MediaType.APPLICATION_JSON)
// Query parameters are parameters: http://localhost/colrest/CoL/login/dologin?username=s150157&password=1234
public String doLogin(#QueryParam("username") String uname, #QueryParam("password") String pwd) throws MalformedURLException, Exception {
URL url = new URL("http://ec2-52-43-233-138.us-west-2.compute.amazonaws.com:3769/col?wsdl");
QName qname = new QName("http://cleanoutloudserver/", "CleanOutLoudImplService");
Service service = Service.create(url, qname);
ICleanOutLoud col = service.getPort(ICleanOutLoud.class);
String token = col.login(uname, pwd);
token = Utility.constructJSON(token);
System.out.println("\nChecking credentials = true\n");
return token;
}
}
web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>RESTWebApp</display-name>
<servlet>
<servlet-name>jersey-servlet</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>dk.dtu.ds.login</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey-servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
When I then try to open the path for the service, I get a blank page. My Chrome console says "GET (link) 404 (Not Found)"
Since I am not that familiar with HTTP and servers, I don't know what to do.
Isn't there an easy way to deploy a simple REST service with AWS or have I done something wrong?
I have really tried to search google to find help, but there has been no success so far.
It seems like i got too confused by all the guides out there.
I found an easy solution and installed Tomcat on the EC2 instance, so I didn't even need to use Beanstalk.
All I did was following this guide and uploaded the war-file in the Web Application Manager and now it works fine.
Thanks for the comments they helped me on the way to find a solution.
I am attempting to recreate the most excellent vogella tutorial for create REST with java, JAX-RS and Jersey.
I'm using eclipse Kepler with Java-EE perspective, tomcat 7.0.
I have create the Todo class, the TodoResource class with the appropriate annotations and deployed on tomcat 7. I have imported the jaxrs-ri libs into the WEB-INF/lib folder as instructed.
Todo class:
package com.vogella.jersey.jaxb.model;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Todo {
private String summary;
private String description;
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
TodoResource with annotations:
package com.vogella.jersey.jaxb.model;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/todo")
public class TodoResource {
// This method is called if XMLis request
#GET
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Todo getXML() {
Todo todo = new Todo();
todo.setSummary("This is my first todo");
todo.setDescription("This is my first todo");
return todo;
}
// This can be used to test the integration with the browser
#GET
#Produces({ MediaType.TEXT_XML })
public Todo getHTML() {
Todo todo = new Todo();
todo.setSummary("This is my first Todo");
todo.setDescription("This is my first Todo");
return todo;
}
}
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" 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>com.vogella.jersey.first</display-name>
<servlet>
<servlet-name>Jersey REST Service</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.jaxb</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
I have also created the client as instructed.
Test.java:
package com.vogella.jersey.first.client;
import java.net.URI;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientResponse;
import org.glassfish.jersey.message.internal.MediaTypes;
public class Test {
public static void main(String[] args) {
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
WebTarget target = client.target(getBaseURI());
System.out.println(target.path("rest").path("todo").request()
.accept(MediaType.APPLICATION_XML ).get(Response.class)
.toString());
System.out.println(target.path("rest").path("todo").request()
.accept(MediaType.APPLICATION_JSON ).get(Response.class)
.toString());
}
private static URI getBaseURI() {
return UriBuilder.fromUri("http://localhost:8080/com.vogella.jersey.jaxb").build();
}
}
Everything works perfectly for the MediaType.APPLICATION_XML - the server returns:
InboundJaxrsResponse{ClientResponse{method=GET, uri=http://localhost:8080/com.vogella.jersey.jaxb/rest/todo, status=200, reason=OK}}
However, for the MediaType APPLICATION_JSON - which is what I actually need, I get an error:
InboundJaxrsResponse{ClientResponse{method=GET, uri=http://localhost:8080/com.vogella.jersey.jaxb/rest/todo, status=500, reason=Internal Server Error}}
Tomcat clearly shows me the problem - it seems to me it doesn't know how to return a JSON response -
Nov 02, 2014 11:59:19 AM org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
SEVERE: MessageBodyWriter not found for media type=application/json, type=class com.vogella.jersey.jaxb.model.Todo, genericType=class com.vogella.jersey.jaxb.model.Todo.
My understanding is that the jaxrs-ri 2.13 bundle includes everything required including dependencies to let me do this - and that I don't need to add any kind of JSON provider. I have done so anyway, I have tried adding gson for example, I have downloaded the moxy jars and attempting to add them to my WEB-INF/lib folder and deploy - all to no avail. I don't know if I'm completely out in the weeds, or if I'm missing something simple?
My understanding is that the jaxrs-ri 2.13 bundle includes everything required including dependencies to let me do this - and that I don't need to add any kind of JSON provider.
That's actually incorrect. As stated at the Jersey User Guide 8.1. JSON
Jersey JSON support comes as a set of extension modules where each of these modules contains an implementation of a Feature that needs to be registered into your Configurable instance (client/server). There are multiple frameworks that provide support for JSON processing and/or JSON-to-Java binding. The modules listed below provide support for JSON representations by integrating the individual JSON frameworks into Jersey. At present, Jersey integrates with the following modules to provide JSON support:
MOXy - JSON binding support via MOXy is a default and preferred way of supporting JSON binding in your Jersey applications since Jersey 2.0. When JSON MOXy module is on the class-path, Jersey will automatically discover the module and seamlessly enable JSON binding support via MOXy in your applications. (See Section 4.3, “Auto-Discoverable Features”.)
Among a few others
So the main Jersey download doesn't come with these extra modules. We need to obtain them separately. That being said, the easiest way to get the required jersey-media-moxy is through Maven.
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.13</version>
</dependency>
If you're not using Maven (which looking through the tutorial, it doesn't), you're going to have to do some searching for the dependencies. The jersey-media-moxy artifact has 16 dependencies, but Fortunately, most are contained within the Jersey distribution. So after filtering out what was already included in the Jersey distro, these are the remaining jars you will have to find on your own (I just created a User Library to test out)
Adding these dependencies will get the example up and running. Tested and works as expected after adding these.
Now you have Eclipse, which I assume came with the Maven (m2e) plugin. So maybe the easiest way to get these dependencies is to create a new Maven project, and add the dependency shown above. After you build the project, maven should download all the extra dependencies into your local Maven Repo. Just grab them from there for your main project.
Other Resources/Notes
Jersey User Guide
I would download the Jersey Example package, which is more up to date then the tutorial you are using.
If you don't know Maven, I would strongly suggest learning at least the basic of dependency management, and let the build framework grab all the dependencies for you. Also all the examples in the examples package uses Maven, so it would help to know the basics.
If your are using Jersey-2
Web.xml Configuration
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servletclass>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.oracle.restful</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
List of Libraries required
org.eclipse.persistence.moxy-2.6.3.jar
org.eclipse.persistence.core-2.6.3.jar
org.eclipse.persistence.asm-2.6.3.jar
org.eclipse.persistence.antlr-2.6.3.jar
jersey-media-moxy-2.23.1.jar
jersey-entity-filtering-2.23.1.jar
Run project it will work. Also check your JAXB classed because it will internally use xml annotation to convert pojo object to JAXB
Have you written any custom MessageBodyWriter for your marshalling from Java to JSON. If yes then you need to have the #Produces annotation with 'application/json' in the provider implementation. For more details refer http://h2labz.blogspot.in/2014/12/marshalling-java-to-json-in-jax-rs.html
I have written a sample REST service using Jersey2.
Here is my web.xml:
<web-app>
<display-name>jerseysample</display-name>
<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.adaequare.rest.config.JerseyResourceInitializer</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
Here is my sample class:
package com.adaequare.resource;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/hello")
public class Hello {
#GET
#Produces(MediaType.TEXT_HTML)
public String sayHtmlHello(){
return "<html><title>Hello Jersey</title><body><h1>Hello Jersey</h1></body></html>";
}
#GET
#Produces(MediaType.TEXT_PLAIN)
public String sayPlainTextHello() {
return "Hello Jersey";
}
// This method is called if XML is request
#GET
#Produces(MediaType.TEXT_XML)
public String sayXMLHello() {
return "<?xml version=\"1.0\"?>" + "<hello> Hello Jersey" + "</hello>";
}
}
I have deployed it to Tomcat and am able to access the following URL:
http://localhost:8080/jerseysample/rest/hello
I tried writing a unit test this way:
package com.adaequare.client;
public class MyResourceTest {
public static final URI BASE_URI = UriBuilder.fromUri("http://localhost").port(8080).build();
private HttpServer server;
private WebTarget target;
#Before
public void setUp() throws Exception {
ResourceConfig rc = new ResourceConfig(Hello.class);
server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, rc);
server.start();
Client c = ClientBuilder.newClient();
target = c.target(BASE_URI);
}
#After
public void tearDown() throws Exception {
server.shutdownNow();
}
#Test
public void testGetIt() {
String responseMsg = target.path("jerseysample").path("rest").path("hello").request().get(String.class);
System.out.println("I am here");
assertEquals("Got it!", responseMsg);
}
}
This class also throws the exception.
On executing this class, I am getting the following exception:
Exception in thread "main" javax.ws.rs.NotFoundException: HTTP 404 Not Found
at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:917)
at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:770)
at org.glassfish.jersey.client.JerseyInvocation.access$500(JerseyInvocation.java:90)
at org.glassfish.jersey.client.JerseyInvocation$2.call(JerseyInvocation.java:671)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:228)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:423)
at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:667)
at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:396)
at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:296)
at com.adaequare.client.TestClient.main(TestClient.java:14)
I am sure I am missing some configuration stuff. I have browsed to see the root cause of the issue but to no avail. Can someone please let me know if I am missing something?
Your service is mapped to (and you are saying you can access it): http://localhost:8080/jerseysample/rest/hello but using your client you are calling http://localhost:8080/restserver/rest/hello which is different URL. What is the surprise?
Try
WebTarget target = ClientBuilder.newClient().target("http://localhost:8080/jerseysample/rest/").path("hello");
As for the second test, try calling getUri() on your WebTarget to see what URL you are actually calling, it should help you see where is the problem.
After your update:
Well first thing is, you haven't specified (in terms of content negotiation) what content your client accepts (you did this in your previous example, which you deleted). But that should not be a problem since in that case server should send you any of implemented ones since by not specifying it you are stating you are supporting all kind of responses. But the problem probably is putting String.class into get() method. There should go an entity you want Jersey to transform the response into. If you want to get String I would do something like this:
Response response = target.path("jerseysample").path("rest").path("hello").
request().get();
StringWriter responseCopy = new StringWriter();
IOUtils.copy((InputStream) response.getEntity(), responseCopy);
But you can't tell for sure which one of your three method is going to be called since it is on the same PATH, so you should also specify the content by passing it to request method.
Hope this helps anyone who can be facing the same problem. In my case, I created my web service RESTful project with the Netbeans Wizard. By any reason, I didn't know why, it missed the ApplicationConfig.java class which contains the annotation #javax.ws.rs.ApplicationPath("webresources"). I don't know why when I generated the client it showed me the correct path that I was expecting.
So, the solution for me was to copy another ApplicationConfig.java from other project and add my facade to the resources.
if you don't config web.xml to lookup the rest classes you need use #ApplicationPath to indicate the classes that keep the Rest resources.
#ApplicationPath("/rest")
public class AplicationRest extends Application
{
#Override
public Set<Class<?>> getClasses()
{
Set<Class<?>> resources = new java.util.HashSet<>();
resources.add(com.acme.SomeRestService.class);
return resources;
}
}
There is an error in web.xml
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>
com.adaequare.rest.config.JerseyResourceInitializer
</param-value>
</init-param>
please try below
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>
com.adaequare.resource.config.JerseyResourceInitializer
</param-value>
</init-param>
I'm currently trying to run a servlet that check a GET value and write the value.
Here my class :
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Servlet extends HttpServlet {
/**
*
*/
public Servlet() {
// TODO Auto-generated constructor stub
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
final String myValue = request.getParameter("MyValue");
if (myValue != null && !myValue.isEmpty()) {
response.getWriter().write(myValue);
}
}
}
It's quit simple, right ?
I already tried many url to be able to see my result like the following :
- http://localhost:8080/Servlet/servlet/Servlet
- http://localhost:8080/Servlet/servlet/Servlet?MyValue=Test
- http://127.0.0.1:8080/Servlet/servlet/Servlet
- http://127.0.0.1:8080/Servlet/servlet/Servlet?MyValue=Test
Is there something wrong with my code or is it a problem with my eclipse?
Thx
You didn't tell anything about the problem symptoms, but I'll assume that you're getting a HTTP 404 error page on all the attempts, right?
You need to map the servlet on an URL pattern. First, you need to assure that the servlet class is placed in a package (we'll assume com.example in this answer).
If you're still on Java EE 5 (or even J2EE..), register it in the webapp's /WEB-INF/web.xml (Eclipse should have autogenerated one):
<servlet>
<servlet-name>servlet</servlet-name>
<servlet-class>com.example.Servlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet</servlet-name>
<url-pattern>/servlet</url-pattern>
</servlet-mapping>
(the servlet name is basically the instance variable name, there's only one applicationwide; the servlet class is obviously the FQN; the URL pattern the webcontent-relative URL of the servlet)
Or when you're already on the latest Java EE 6, then annotate it with #WebServlet wherein you specify the URL pattern as value:
package com.example;
// ...
#WebServlet("/servlet")
public class Servlet extends HttpServlet {
// ...
}
Either way, it's basically telling that the servlet should listen on webcontent-relative URLs matching /servlet. So, assuming that your web context root path is /Servlet, then this should do:
http://localhost:8080/Servlet/servlet
In the future, it'd be easier if you created the servlet class by New > Servlet instead of New > Class, then this all will be automagically taken into account in the wizard.
See also:
Our Servlets wiki page - contains some Hello World examples - you can get to this page by hovering your mouse a while above the servlets until a black info box shows up and then clicking the info link therein.
You can find your Context Root by right clicking your project in eclipse -> Properties -> Web Project Settings.
I would like to have my JAX-RX Application start at the root context so my URLs will be
http://example.com/restfullPath
and not
http://example.com/rest/restfullPath
I switched my Application's annotation from this
#ApplicationPath("/rest/*")
to this
#ApplicationPath("/*")
But then it seems that it takes over serving files such as /index.html
Is there a way to run a JAX-RS on the root application context but still have static pages served?
Seems this was asked before on the JBOSS forum, but the solution is not really practical
It's probably not so much a bug as a limitation of the Servlet spec. The details of how a JAX-RS #ApplicationPath is handled is implementation specific, and I can't speak for all implementations, but I'd guess the typical approach is to simply use it as a servlet URL pattern. Taking a look at Jersey's ServletContainerInitializer implementation as one example, you'll find that the addServletWithApplication() method is responsible for creating the servlet and mapping to handle requests, and you can see that it does, indeed, use the path from the #ApplicationPath as the Jersey ServletContainer's mapped path.
Unfortunately, since time immemorial, the Servlet spec has allowed only a small handful of ways of mapping servlets to URL paths. The current options with Servlet 3.0, given in Section 12.2 of the spec--sadly only available as a PDF, so not linkable by section--are:
/.../* where the initial /... is zero or more path elements
*.<ext> where <ext> is some extension to match
the empty string, which maps only to the empty path/context root
/, the single slash, which indicates the "default" servlet in the context, which handles anything that doesn't match anything else
any other string, which is treated as a literal value to match
The same section of the spec also has specific rules for the order in which the matching rules should apply, but the short version is this: to make your resource class answer requests at the context root, you have to use either / or /* as the path. If you use /, then you're replacing the container's default servlet, which would normally be responsible for handling static resources. If you use /*, then you're making it too greedy and saying it should match everything all the time, and the default servlet will never be invoked.
So if we accept that we're inside the box determined by the limitations of servlet URL patterns, our options are fairly limited. Here are the ones I can think of:
1) Use #ApplicationPath("/"), and explicitly map your static resources by name or by extension to the container's default servlet (named "default" in Tomcat and Jetty, not sure about others). In a web.xml, it would look like
<!-- All html files at any path -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<!-- Specifically index.html at the root -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/index.html</url-pattern>
</servlet-mapping>
or with a ServletContextInitializer, like
public class MyInitializer implements ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx) {
ctx.getServletRegistration("default").addMapping("*.html");
ctx.getServletRegistration("default").addMapping("/index.html");
}
}
Because of the way the matching rules are written, an extension pattern wins over the default servlet, so you'd only need to add a mapping per static file extension as long as there's no overlap between those and any "extensions" that might occur in your API. This is pretty close to the undesirable option mentioned in the forum post you linked, and I just mention it for completeness and to add the ServletContextInitializer part.
2) Leave your API mapped to /rest/*, and use a Filter to identify requests for the API and forward them to that path. This way, you break out of the servlet URL pattern box and can match URLs any way you want. For example, assuming that all your REST calls are to paths that either begin with "/foo" or are exactly "/bar" and all other requests should go to static resources, then something like:
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.regex.Pattern;
#WebFilter(urlPatterns = "/*")
public class PathingFilter implements Filter {
Pattern[] restPatterns = new Pattern[] {
Pattern.compile("/foo.*"),
Pattern.compile("/bar"),
};
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
String path = ((HttpServletRequest) request).getServletPath();
for (Pattern pattern : restPatterns) {
if (pattern.matcher(path).matches()) {
String newPath = "/rest/" + path;
request.getRequestDispatcher(newPath)
.forward(request, response);
return;
}
}
}
chain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void destroy() {}
}
With the above, you essentially translate requests as follows:
http://example.org/foo -> http://example.org/rest/foo
http://example.org/foox -> http://example.org/rest/foox
http://example.org/foo/anything -> http://example.org/rest/foo/anything
http://example.org/bar -> http://example.org/rest/bar
http://example.org/bart -> http://example.org/bart
http://example.org/index.html -> http://example.org/index.html
3) Realize that the previous option is basically URL rewriting and use an existing implementation, such as Apache's mod_rewrite, the Tuckey rewrite filter, or ocpsoft Rewrite.
I have found another solution that involves internal Jersey classes, I assume it's probably just not yet part of the JAX-RS spec. (based on: http://www.lucubratory.eu/simple-jerseyrest-and-jsp-based-web-application/)
web.xml
<web-app version="3.0" 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_3_0.xsd">
<display-name>jersey-rest-jsp-frame-1</display-name>
<filter>
<filter-name>jersey</filter-name>
<filter-class>
com.sun.jersey.spi.container.servlet.ServletContainer
</filter-class>
<init-param>
<param-name>
com.sun.jersey.config.property.JSPTemplatesBasePath
</param-name>
<param-value>/WEB-INF/jsp</param-value>
</init-param>
<init-param>
<param-name>
com.sun.jersey.config.property.WebPageContentRegex
</param-name>
<param-value>
(/(image|js|css)/?.*)|(/.*\.jsp)|(/WEB-INF/.*\.jsp)|
(/WEB-INF/.*\.jspf)|(/.*\.html)|(/favicon\.ico)|
(/robots\.txt)
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jersey</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
WEB-INF/jsp/index.jsp
<%# page contentType="text/html; charset=UTF-8" language="java" %>
<html>
<body>
<h2>Hello ${it.foo}!</h2>
</body>
</html>
IndexModel.java
package example;
import com.sun.jersey.api.view.Viewable;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.HashMap;
#Path("/")
#Produces(MediaType.TEXT_HTML)
public class IndexModel {
#GET
public Response root() {
return Response.seeOther(URI.create("/index")).build();
}
#GET
#Path("index")
public Viewable index(#Context HttpServletRequest request) {
HashMap<String, String> model = new HashMap<String, String>();
model.put("foo","World");
return new Viewable("/index.jsp", model);
}
}
This seems to work, but I wonder if it is / will be part of JAX-RS spec / implementation.
You can try to look for DefaultServlet of your servlet container and add servlet-mapping for it by hands in web.xml to handle page files such as *.html, *.jsp or any other.
E.g. for Tomcat 5.5 it's described here: http://tomcat.apache.org/tomcat-5.5-doc/default-servlet.html.
Quoting #damo for Jersey 2.0 from another post
"Alternatively, you might be able to pull something off with some kind of redirection. For example, with a Pre-matching Filter. I've never done anything like this, but the documentation suggests that "you can even modify request URI"."
Use #ApplicationPath("/") instead (without asterisk). It will help in your case.
Here is a sample REST web service:
1. JaxRsActivator.java
package com.stackoverflow;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
#ApplicationPath("/")
public class JaxRsActivator extends Application {
}
2. HelloService.java
package com.stackoverflow;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/hello")
public class HelloService {
#GET
#Produces(MediaType.TEXT_HTML)
public String hello() {
return "hello";
}
}
I used Eclipse to export this Dynamic Web project to a WAR file named as helloservice.war and deployed it to WildFly which was running on my local machine. Its URL: http://localhost:8080/helloservice/hello.
When accessing this link it returned:
hello