Lab 2 - Servlets
In this exercise, we will develop a simple time service using a Servlet. We will run/debug the application in the embedded gretty container, as well as a stand alone Tomcat instance.
Create a gradle project named lab2 similar to how we created a project in Lab1. Use mis283 as the groupId and lab2 as the artefactId. Leave the version blank to avoid adding a version number to the war file. Create the standard directory structure for the main application and test sources.
Gradle
Modify the gradle build file and add dependencies for gretty, servlet API and httpunit.
group 'mis283' apply plugin: 'java' apply plugin: 'war' apply from: 'https://raw.github.com/akhikhl/gretty/master/pluginScripts/gretty.plugin' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile 'javax.servlet:javax.servlet-api:3.1.0' testCompile group: 'junit', name: 'junit', version: '4.12' testCompile 'org.httpunit:httpunit:1.7.2' }
Iter 1
Create a new Java class named TimeOfDay under the mis283 package.
package mis283; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date; import static java.lang.String.format; @WebServlet(urlPatterns = "/*") public class TimeOfDay extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final Writer writer = resp.getWriter(); resp.setContentType("text/plain"); final String path = req.getPathInfo(); switch (path) { case "/dateTime": resp.setStatus(HttpServletResponse.SC_OK); writer.write(dateTime()); break; default: resp.setStatus(HttpServletResponse.SC_NOT_FOUND); writer.write(format("Path: %s not supported", path)); } writer.flush(); } private String dateTime() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); final Date date = new Date(); return sdf.format(date); } }
Iter 2
We will now add a date method that will return the current date without the time information to the servlet.
package mis283; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date; import static java.lang.String.format; @WebServlet(urlPatterns = "/*") public class TimeOfDay extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final Writer writer = resp.getWriter(); resp.setContentType("text/plain"); final String path = req.getPathInfo(); switch (path) { case "/dateTime": resp.setStatus(HttpServletResponse.SC_OK); writer.write(dateTime()); break; case "/date": resp.setStatus(HttpServletResponse.SC_OK); writer.write(date()); break; default: resp.setStatus(HttpServletResponse.SC_NOT_FOUND); writer.write(format("Path: %s not supported", path)); } writer.flush(); } private String dateTime() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); final Date date = new Date(); return sdf.format(date); } private String date() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); final Date date = new Date(); return sdf.format(date); } }
Iter 3
We will add additional methods to return the year, day, month and time of day. We will modify the servlet to make use of reflection to determine the method to route the request to, instead of manually mapping the request path to the appropriate method.
package mis283; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date; import static java.lang.String.format; @WebServlet(urlPatterns = "/*") public class TimeOfDay extends HttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final Writer writer = resp.getWriter(); resp.setContentType("text/plain"); final String path = req.getPathInfo(); try { final String value = getValue(req.getPathInfo()); resp.setStatus(HttpServletResponse.SC_OK); writer.write(value); } catch (final Throwable ignored) { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); writer.write(format("Path: %s not supported.%n%s", path, ignored.toString())); } writer.flush(); } private String getValue(final String path) throws Throwable { final Method method = getClass().getDeclaredMethod(path.substring(1)); method.setAccessible(true); return (String) method.invoke(this); } private String year() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy"); final Date date = new Date(); return sdf.format(date); } private String month() { final SimpleDateFormat sdf = new SimpleDateFormat("MMMMM"); final Date date = new Date(); return sdf.format(date); } private String day() { final SimpleDateFormat sdf = new SimpleDateFormat("dd"); final Date date = new Date(); return sdf.format(date); } private String date() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); final Date date = new Date(); return sdf.format(date); } private String time() { final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSXXX"); final Date date = new Date(); return sdf.format(date); } private String dateTime() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); final Date date = new Date(); return sdf.format(date); } }
Test
We will write a httpunit test class that exercises the various responses supported by our servlet. Since our test is a black-box test, we can run it against the embedded gretty container, or the external Tomcat.
package mis283; import com.meterware.httpunit.GetMethodWebRequest; import com.meterware.httpunit.WebConversation; import com.meterware.httpunit.WebRequest; import com.meterware.httpunit.WebResponse; import com.meterware.servletunit.ServletRunner; import com.meterware.servletunit.ServletUnitClient; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import static java.lang.String.format; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** * @author Rakesh Vidyadharan 2017-09-01 * @version $Id: TimeOfDayTest.java 5882 2017-09-01 20:32:18Z rakesh $ */ public class TimeOfDayTest { private static final Map<String, String> components = new HashMap<>(); private static final ServletRunner runner = new ServletRunner(); static { final Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy"); components.put("year", sdf.format(date)); sdf = new SimpleDateFormat("MM"); components.put("month", sdf.format(date)); sdf = new SimpleDateFormat("MMMMM"); components.put("Month", sdf.format(date)); sdf = new SimpleDateFormat("dd"); components.put("day", sdf.format(date)); sdf = new SimpleDateFormat("HH"); components.put("hour", sdf.format(date)); } @BeforeClass public static void register() { runner.registerServlet("/*", TimeOfDay.class.getName()); } @Test public void dateTime() throws Exception { final ServletUnitClient sc = runner.newClient(); final WebRequest request = new GetMethodWebRequest("http://localhost/dateTime"); final WebResponse response = sc.getResponse(request); assertEquals("Invalid content type", "text/plain", response.getContentType()); final String value = response.getText(); assertNotNull("No data received", value); assertTrue("Wrong year in response", value.startsWith(components.get("year"))); assertTrue("Wrong month in response", value.contains(format("-%s-", components.get("month")))); assertTrue("Wrong day in response", value.contains(format("-%sT", components.get("day")))); assertTrue("Wrong hour in response", value.contains(format("T%s:", components.get("hour")))); } @Test public void date() throws Exception { final ServletUnitClient sc = runner.newClient(); final WebRequest request = new GetMethodWebRequest("http://localhost/date"); final WebResponse response = sc.getResponse(request); assertEquals("Invalid content type", "text/plain", response.getContentType()); final String value = response.getText(); assertNotNull("No data received", value); assertTrue("Wrong year in response", value.startsWith(components.get("year"))); assertTrue("Wrong month in response", value.contains(format("-%s-", components.get("month")))); assertTrue("Wrong day in response", value.endsWith(components.get("day"))); } @Test public void year() throws Exception { final ServletUnitClient sc = runner.newClient(); final WebRequest request = new GetMethodWebRequest("http://localhost/year"); final WebResponse response = sc.getResponse(request); assertEquals("Invalid content type", "text/plain", response.getContentType()); final String value = response.getText(); assertNotNull("No data received", value); assertEquals("Wrong year in response", value, components.get("year")); } @Test public void month() throws Exception { final ServletUnitClient sc = runner.newClient(); final WebRequest request = new GetMethodWebRequest("http://localhost/month"); final WebResponse response = sc.getResponse(request); assertEquals("Invalid content type", "text/plain", response.getContentType()); final String value = response.getText(); assertNotNull("No data received", value); assertEquals("Wrong month in response", value, components.get("Month")); } @Test public void day() throws Exception { final ServletUnitClient sc = runner.newClient(); final WebRequest request = new GetMethodWebRequest("http://localhost/day"); final WebResponse response = sc.getResponse(request); assertEquals("Invalid content type", "text/plain", response.getContentType()); final String value = response.getText(); assertNotNull("No data received", value); assertEquals("Wrong day in response", value, components.get("day")); } @Test public void time() throws Exception { final ServletUnitClient sc = runner.newClient(); final WebRequest request = new GetMethodWebRequest("http://localhost/time"); final WebResponse response = sc.getResponse(request); assertEquals("Invalid content type", "text/plain", response.getContentType()); final String value = response.getText(); assertNotNull("No data received", value); assertTrue("Wrong hour in response", value.startsWith(components.get("hour"))); } @Test(expected = Exception.class) public void invalid() throws Exception { final ServletUnitClient sc = runner.newClient(); final WebRequest request = new GetMethodWebRequest("http://localhost/invalid"); sc.getResponse(request); } @AfterClass public static void shutdown() { runner.shutDown(); } }
Tomcat
Download the latest 8.5.x version of Tomcat and install to a location on choice. Start Tomcat using the startup.bat script under the bin directory within Tomcat.
To enable remote debugging, we will use the same remote debugging task in IntelliJ.
- Assemble the web application archive (war) file using the war gradle target.
- Copy the war file (build/libs/lab2.war) to Tomcat’s webapps directory.
- Tomcat should automatically deploy the war file and make it available under the same URL as gretty.
To enable remote debugging, we will use the same remote debugging task in IntelliJ.
- Stop Tomcat using the shutdown.bat script.
- Create a file name setenv.bat under Tomcat’s bin directory. Add the following line to the file:
set “CATALINA_OPTS=%CATALINA_OPTS% -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
- Start Tomcat.