Java-Web:Exercise - Introduction to Winstone

From Juneday education
Jump to: navigation, search

Work in progress

This chapter is a work in progress. Remove this section when the page is production-ready.

Prerequisite knowledge

These exercises assume that you have basic knowledge of Java, SQL and JDBC. We have books covering these basics, if you need to refresh them.

Introduction

The purpose of these exercises is to get you familiar with the Winstone servlet container. It is a small self-contained servlet container written in Java and contained in one single JAR-file. It allows you to run servlets on your own computer and accessing them on localhost, port 8080 (or some other port if you specify one).

The exercises will let you download the Winstone JAR file and configure some simple servlets for it.

In order to compile servlets, you'll need to have the servlet api jar file, which you'll download during the exercises. You'll also create a small database and access it from a servlet using JDBC.

You will need to refresh your knowledge on class paths (if you don't remember it), since you will need to use the -cp flags in order to compile some of the files in the exercise.

All exercises will be done using the command line with Bash and a basic editor. If you prefer to use an IDE, you are on your own. We recommend that you use the command line, however, since this is what we will use in the instructions.

Exercises

Task 1 - set up the environment

Download the following JAR files:

If you want, rename the files to the following, to keep our instructions general and shorter:

  • servlet-api.jar
  • winstone.jar
  • sqlite-jdbc.jar

Create the following directory structure (start in a new directory which you first create and enter):

.
`-- webroot
    `-- WEB-INF
        |-- classes
        `-- lib

Expand using link to the right to see a hint on how to create the directory structure.

$ mkdir -p webroot/WEB-INF/{classes,lib}

Put the servlet-api.jar and winstone.jar in the current directory. Put the sqlite-jdbc.jar in webroot/WEB-INF/lib/. You will not use JDBC until the later, but it is the correct place for it, so that Winstone finds the driver for SQLite in a later task.

The directory should now look like this:

.
|-- servlet-api.jar
|-- webroot
|   `-- WEB-INF
|       |-- classes
|       `-- lib
|           `-- sqlite-jdbc.jar
`-- winstone.jar

Next, use your favorite editor to create webroot/WEB-INF/web.xml with the following contents (you will add directives later):

<?xml version="1.0" encoding="utf-8"?>

<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">
 
<servlet>
   <servlet-name></servlet-name>
   <servlet-class></servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name></servlet-name>
  <url-pattern></url-pattern>
</servlet-mapping>
</web-app>

Task 2 - Create a HelloWorld servlet

Start by configuring Winstone using the web.xml file. In the <servlet> directive, set the servlet-name to hello and the servlet-class to servlets.HelloWorld . In the <servlet-mapping> directive, set the servlet-name to hello (in order to map them together) and the url-pattern to /hello.

Expand using link to the right to see a hint on what the web.xml file should look like.

<?xml version="1.0" encoding="utf-8"?>

<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">
 
<servlet>
   <servlet-name>hello</servlet-name>
   <servlet-class>servlets.HelloWorld</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>hello</servlet-name>
  <url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>

Don't forget the slash in the url-pattern!

Now, create a servlet in webroot/WEB-INF/classes. Make the servlet part of the package servlets, so you need to create the directory webroot/WEB-INF/classes/servlets/ directory. The servlet should be defined in a class called HelloWorld (in webroot/WEB-INF/classes/servlets/HelloWorld.java).

Override the doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException method and in the body of the method, create a local variable PrintWriter out and initialize it to the following:

new PrintWriter(new OutputStreamWriter(response.getOutputStream(), java.nio.charset.StandardCharsets.UTF_8), true);

Use out to println() an HTML page with the following contents:

<!DOCTYPE html>
<html>
<head>
  <title>Hello world servlet!</title>
</head>
<body>
<p style="color: red;">
Hello World!
</p>
</body>
</html>

Expand using link to the right to see what the servlet could look like.

package servlets;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.PrintWriter;
import java.io.OutputStreamWriter;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;

public class HelloWorld extends HttpServlet{
  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException{
    PrintWriter out =
      new PrintWriter(new OutputStreamWriter(response.getOutputStream(),
                                             UTF_8), true);
    StringBuilder sb = new StringBuilder("<!DOCTYPE html>\n")
      .append("<html>\n")
      .append("<head>\n")
      .append("  <title>Hello world servlet!</title>\n")
      .append("</head> \n")
      .append("<body>\n")
      .append("<p style=\"color: red;\">\n")
      .append("Hello World!\n")
      .append("</p>\n")
      .append("</body>\n")
      .append("</html>\n");
    out.println(sb.toString());
    out.close();
  }
}

Compile the servlet.HelloWorld class (in the HelloWorld.java source code file). You'll need to use a classpath containing the servlet-api.jar.

Expand using link to the right to see a hint.

$ javac -cp servlet-api.jar webroot/WEB-INF/classes/servlets/HelloWorld.java

Now, start up Winstone and access your servlet using http://localhost:8080/hello using a browser.

In order to start Winstone, you should use the following command line (and you must remain in the current directory, above the webroot directory):

$ java -jar winstone.jar --webroot webroot

The flag --webroot points out the path to the webroot of the web application. It is the directory which contains the WEB-INF directory with the configuration file and the classes for the servlets. That means that the above command line assumes that you are still in the directory containing webroot. You may start Winstone from any directory but you must then tell Winstone the path to the webroot directory. For these exercises here, we'll assume that you never leave the current directory (signified by the dot in the directory trees shown above). The paths used in the exercises are relative paths, so they only work if you remain in that directory.

Task 3 - create a database

Create an SQLite database called courses.db . You may use the following SQL file to create it:

PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS courses(id integer primary key not null, course_code text, course_name text);
INSERT INTO "courses" VALUES(1,'JAVA-101','Programming with Java');
INSERT INTO "courses" VALUES(2,'JAVA-102','More programming with Java');
INSERT INTO "courses" VALUES(3,'DB-101','Introduction to Databases');
INSERT INTO "courses" VALUES(4,'Bash-101','Introduction to Bash');
INSERT INTO "courses" VALUES(5,'C-101','Programming with C');
COMMIT;

Download the file here (github) into the current directory.

Expand using link to the right to see a hint.

$ wget https://raw.githubusercontent.com/progund/java-web/master/winstone-intro/servlet-example/exercises/create_courses.sql

Did you know that you can create the database directly from Bash, using the file and redirection? Just do the following:

$ sqlite3 courses.db < create_courses.sql

The argument to sqlite3, courses.db, is the name of the binary SQLite database file to create.

The database is very simple. It contains just one table, courses, with rows of courses for a fictional school. This is not a database book, so we've kept the database simple, so that you can get started easily, and practice how to make a servlet connect to and read from the database using JDBC.

Investigate the definition of the table courses, so that you know how to query the table.

Expand using link to the right to see a hint.

$ echo '.schema courses' | sqlite3 courses.db
CREATE TABLE courses(id integer primary key not null, course_code text, course_name text);

Select all rows from the database and verify that you get this result:

id          course_code  course_name          
----------  -----------  ---------------------
1           JAVA-101     Programming with Java
2           JAVA-102     More programming with
3           DB-101       Introduction to Datab
4           Bash-101     Introduction to Bash 
5           C-101        Programming with C

Expand using link to the right to see a hint.

$ echo -e '.mode column\n.headers on\nSELECT * FROM courses;' | sqlite3 courses.db
id          course_code  course_name          
----------  -----------  ---------------------
1           JAVA-101     Programming with Java
2           JAVA-102     More programming with
3           DB-101       Introduction to Datab
4           Bash-101     Introduction to Bash 
5           C-101        Programming with C

Explanation: echo -e allows for echoing special characters like \n, which is needed when sending three lines to the SQLite command. The first line .mode column makes the columns of the output aligned. The second line, .headers on, makes SQLite add the column names to the output as headers separated from the rows with a dotted line. The thirde line, SELECT * FROM courses; is the actual SQL query for all the rows.

Task 4 - Create a database driven servlet

Create a Servlet in a class called servlets.Courses in webroot/WEB-INF/classes/servlets/Courses.java. For now, just implement (and override) doGet() and output a single string saying "courses test" in it. We'll add the database stuff later, and want to make sure Winstone finds and invokes the servlet before we do any more work.

Expand using link to the right to see a hint.

package servlets;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.PrintWriter;
import java.io.OutputStreamWriter;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;

public class Courses extends HttpServlet{
  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException{
    PrintWriter out =
      new PrintWriter(new OutputStreamWriter(response.getOutputStream(),
                                             UTF_8), true);
    StringBuilder sb = new StringBuilder("<!DOCTYPE html>\n")
      .append("<html>\n")
      .append("<head>\n")
      .append("  <title>Hello world servlet!</title>\n")
      .append("</head> \n")
      .append("<body>\n")
      .append("<p style=\"color: red;\">\n")
      .append("Courses test!\n")
      .append("</p>\n")
      .append("</body>\n")
      .append("</html>\n");
    out.println(sb.toString());
    out.close();
  }
}

Note that you don't have to use HTML in your simple test version, if you don't want to. We'll use HTML soon, however, so you might copy the text from above if you want to.

Add a directive to the web.xml declaring a new servlet with name courses and class servlets.Courses. Put this directive under the existing <servlet> directive. Create a directive mapping courses to the url-pattern /courses. Put this directive under the existing <servlet-mapping> directive.

Expand using link to the right to see a hint.

<?xml version="1.0" encoding="utf-8"?>

<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">
 
<servlet>
   <servlet-name>hello</servlet-name>
   <servlet-class>servlets.HelloWorld</servlet-class>
</servlet>
<servlet>
   <servlet-name>courses</servlet-name>
   <servlet-class>servlets.Courses</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>hello</servlet-name>
  <url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet-mapping>
  <servlet-name>courses</servlet-name>
  <url-pattern>/courses</url-pattern>
</servlet-mapping>
</web-app>

Note that it is important (mandatory, actually) to put the servlet directives first and after each other, then the servlet-mapping directives after each other. You can't merge them in any other order.

Compile the new servlet and restart Winstone and verify that you can access it throw your browser pointed to http://localhost:8080/courses.

Expand using link to the right to see a hint.

$ javac -cp servlet-api.jar webroot/WEB-INF/classes/servlets/Courses.java && java -jar winstone.jar --webroot webroot

Explanation: In Bash, you can run two (or more) commands as an atomic unit, that is, run the first command and only if that succeeds, run the second command too. You use this syntax: $ command-1 && command-2. This works through the use of exit codes from the commands. An exit code of 0 means success, and an exit code of anything else, means failure (an error occurred). If you want to investigate what exit code the last command had, you can do $ echo $? and convince yourself that this works as we exaplained. Example:

$ ls create_courses.sql 
create_courses.sql
$ echo $?
0
$ ls wrong-trousers.sql
ls: cannot access 'wrong-trousers.sql': No such file or directory
$ echo $?
2
$ ls create_courses.sql && echo "Sweet success!"
create_courses.sql
Sweet success!

Task 5 - Add JDBC stuff to servlets.Courses

Now, add (override) the init() method to your Courses servlet. In the method body, load the SQLite JDBC driver class org.sqlite.JDBC into the JVM using Class.forName(). It requires a try-catch block, catching ClassNotFoundException in case the class couldn't be found. If you have put the JAR file for the driver in the webroot/WEB-INF/lib/ directory, it shouldn't happen, though.

Expand using link to the right to see a hint.

  @Override
  public void init() throws ServletException {
    try{
      Class.forName("org.sqlite.JDBC");
    }catch(ClassNotFoundException cnfe){
      System.err.println("Could not load driver: "+cnfe.getMessage());
    }
  }

Next, add an import statement importing java.sql.*; to your class source code.

In the doGet() method, add a try-catch catching SQLException around all the code in the method after the declaration and initialization of the PrintWriter and the catch just before you close the printwriter. In the catch clause, use out (the printwriter reference) to output an error message in HTML:

<em style="color: red;">Database error</em>

In the String for the message, append the exception's message before the closing of the em tag.

Expand using link to the right to see a hint.

  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException{
    PrintWriter out =
      new PrintWriter(new OutputStreamWriter(response.getOutputStream(),
                                             UTF_8), true);
    try{
      StringBuilder sb = new StringBuilder("<!DOCTYPE html>\n")
        .append("<html>\n")
        .append("<head>\n")
        .append("  <title>Hello world servlet!</title>\n")
        .append("</head> \n")
        .append("<body>\n")
        .append("<p style=\"color: red;\">\n")
        .append("Courses test!\n")
        .append("</p>\n")
        .append("</body>\n")
        .append("</html>\n");
      out.println(sb.toString());
    }catch(SQLException sqle){
      out.println("<em style=\"color: red;\">Database error: " +
                  sqle.getMessage() +
                  "</em>");
    }
    out.close();
  }

Now, create a java.sql.Connection reference and assign it the result of asking java.sql.DriverManager for a connection to "jdbc:sqlite:courses.db". Note that this is a relative path to the database file, which is why we created the database in the current directory, so that Winstone (and Java) can find it using this relative path. If you put the database file somewhere else, you'll have to modify the URL to the file accordingly.

Expand using link to the right to see a hint.

      Connection con = DriverManager.getConnection("jdbc:sqlite:courses.db");

This variable, and all the database related code must be put inside the try block.

After the connection is initiated, use it to create a java.sql.Statement reference.

Expand using link to the right to see a hint.

      Statement stm  = con.createStatement();

Now, all we need is the result from using the statement to execute a query for all the rows in the courses for us. Use the statement to execute the following SQL statement <course lang="SQL" inline>SELECT * FROM courses</source> and assign the result to a java.sql.ResultSet reference called rs.

Expand using link to the right to see a hint.

      ResultSet rs   = stm.executeQuery("SELECT * FROM courses");

Now, use out (the printwriter) to output an HTML list of all the course codes and course names, using a while loop with the condition rs.next(). Use appropriate getString() calls to get the course codes and course names. Refer to the courses table definition if you forgot the column names.

Expand using link to the right to see a hint.

  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException{
    PrintWriter out =
      new PrintWriter(new OutputStreamWriter(response.getOutputStream(),
                                             UTF_8), true);
    try{
      Connection con = DriverManager.getConnection("jdbc:sqlite:courses.db");
      Statement stm  = con.createStatement();
      ResultSet rs   = stm.executeQuery("SELECT * FROM courses");
      StringBuilder sb = new StringBuilder("<!DOCTYPE html>\n")
        .append("<html>\n")
        .append("<head>\n")
        .append("  <title>Hello world servlet!</title>\n")
        .append("</head> \n")
        .append("<body>\n")
        .append("<ul>\n");
      while(rs.next()){
        sb.append("<li>")
          .append(rs.getString("course_code"))
          .append(" - ")
          .append(rs.getString("course_name"))
          .append("</li>\n");
      }
        sb.append("</ul>\n")
        .append("</body>\n")
        .append("</html>\n");
      out.println(sb.toString());
    }catch(SQLException sqle){
      out.println("<em style=\"color: red;\">Database error: " +
                  sqle.getMessage() +
                  "</em>");
    }
    out.close();
  }

Recompile the servlet and restart Winstone. Verify that your browser displays the following list:

  • JAVA-101 - Programming with Java
  • JAVA-102 - More programming with Java
  • DB-101 - Introduction to Databases
  • Bash-101 - Introduction to Bash
  • C-101 - Programming with C

Try to make your servlet generate this HTML output (using the database):

<!DOCTYPE html>
<html>
<head>
  <title>Hello world servlet!</title>
</head> 
<body>
<ul>
<li>JAVA-101 - Programming with Java</li>
<li>JAVA-102 - More programming with Java</li>
<li>DB-101 - Introduction to Databases</li>
<li>Bash-101 - Introduction to Bash</li>
<li>C-101 - Programming with C</li>
</ul>
</body>
</html>

Task 6 (optional) - Verify that your error output works

As an optional task, try to mess up your SQL statement and verify that your output from the catch clause works as expected.

Ideally, you should when an error occurs like this change the status code from the servlet. We recommend status 500 for database errors. You can read here about HTTP status codes:

Navigation

Up next: HTTP response code from your servlet!

« PreviousBook TOCNext »