Chapter 13. Server-Side Java

13.0 Introduction

Sockets form the underpinnings of almost all networking protocols. JDBC, RMI, CORBA, EJB, and the non-Java RPC (Remote Procedure Call) and NFS (Network File System) are all implemented by connecting various types of sockets together. Socket connections can be implemented in most any language, not just Java: C, C++, Perl, and Python are also popular, and many others are possible. A client or server written in any one of these languages can communicate with its opposite written in any of the other languages. Therefore, it’s worth taking a quick look at how the ServerSocket behaves, even if you wind up utilizing the higher-level services such as RMI, JDBC, CORBA, or EJB.

The discussion looks first at the ServerSocket itself, then at writing data over a socket in various ways. Finally, I show a complete implementation of a usable network server written in Java: the chat server from the client in the previous chapter.

Tip

Most production work in server-side Java uses the Java Enterprise Edition (Java EE), recently donated to the Eclipse Software Foundation and renamed to Jakarta, but still widely referred to by the previous name (and occasionally by its very old name, “J2EE”, which was retired in 2005). Java EE provides scalability and support for building well-structured, multi-tiered distributed applications. EE provides the “servlet” framework; a servlet is a strategy object that can be installed into any standard Java EE web server. EE also provides two web “view” technologies: the original JSP (JavaServer Pages) and the newer, component-based JSF (JavaServer Faces). Finally, EE provides a number of other network-based services, including EJB3 remote access and Java Messaging Service (JMS). These are unfortunately outside the scope of this book, and are covered in several other books such as Arun Gupta’s Java EE 7 Essentials: Enterprise Developer Handbook. This chapter is only for those who need or want to build their own server from the ground up.

13.1 Opening a Server Socket for Business

Problem

You need to write a socket-based server.

Solution

Create a ServerSocket for the given port number.

Discussion

The ServerSocket represents the “other end” of a connection, the server that waits patiently for clients to come along and connect to it. You construct a ServerSocket with just the port number.1 Because it doesn’t need to connect to another host, it doesn’t need a particular host’s address as the client socket constructor does.

Assuming the ServerSocket constructor doesn’t throw an exception, you’re in business. Your next step is to await client activity, which you do by calling accept(). This call blocks until a client connects to your server; at that point, the accept() returns to you a Socket object (not a ServerSocket) that is connected in both directions to the Socket object on the client (or its equivalent, if written in another language). Example 13-1 shows the code for a socket-based server.

Example 13-1. main/src/main/java/network/Listen.java
public class Listen {
    /** The TCP port for the service. */
    public static final short PORT = 9999;

    public static void main(String[] argv) throws IOException {
        ServerSocket sock;
        Socket  clientSock;
        try {
            sock = new ServerSocket(PORT);
            while ((clientSock = sock.accept()) != null) {

                // Process it, usually on a separate thread
                // to avoid blocking the accept() call.
                process(clientSock);
            }

        } catch (IOException e) {
            System.err.println(e);
        }
    }

    /** This would do something with one client. */
    static void process(Socket s) throws IOException {
        System.out.println("Accept from client " + s.getInetAddress());
        // The conversation would be here.
        s.close();
    }
}

You would normally use the same socket for both reading and writing, as shown in the next few recipes.

You may want to listen only on a particular network interface. Though we tend to think of network addresses as computer addresses, the two are not the same. A network address is actually the address of a particular network card, or network interface connection, on a given computing device. A desktop computer, laptop, tablet, or mobile phone might have only a single interface, hence a single network address. But a large server machine might have two or more interfaces, usually when it is connected to several networks. A network router is a box, either special purpose (e.g., a Cisco router), or general purpose (e.g., a Unix host), that has interfaces on multiple networks and has both the capability and the administrative permission to forward packets from one network to another. A program running on such a server machine might want to provide services only to its inside network or its outside network. One way to accomplish this is by specifying the network interface to be listened on. Suppose you want to provide a different view of web pages for your intranet than you provide to outside customers. For security reasons, you probably wouldn’t run both these services on the same machine. But if you wanted to, you could do this by providing the network interface addresses as arguments to the ServerSocket constructor.

However, to use this form of the constructor, you don’t have the option of using a string for the network address’s name, as you did with the client socket; you must convert it to an InetAddress object. You also have to provide a backlog argument, which is the number of connections that can queue up to be accepted before clients are told that your server is too busy. The complete setup is shown in Example 13-2.

Example 13-2. main/src/main/java/network/ListenInside.java
public class ListenInside {
    /** The TCP port for the service. */
    public static final short PORT = 9999;
    /** The name of the network interface. */
    public static final String INSIDE_HOST = "acmewidgets-inside";
    /** The number of clients allowed to queue */
    public static final int BACKLOG = 10;

    public static void main(String[] argv) throws IOException {
        ServerSocket sock;
        Socket  clientSock;
        try {
            sock = new ServerSocket(PORT, BACKLOG,
                InetAddress.getByName(INSIDE_HOST));
            while ((clientSock = sock.accept()) != null) {

                // Process it.
                process(clientSock);
            }

        } catch (IOException e) {
            System.err.println(e);
        }
    }

    /** Hold server's conversation with one client. */
    static void process(Socket s) throws IOException {
        System.out.println("Connected from  " + INSIDE_HOST +
            ": " + s.getInetAddress(  ));
        // The conversation would be here.
        s.close();
    }
}

The InetAddress.getByName() looks up the given hostname in a system-dependent way, referring to a configuration file in the /etc or windows directory, or to some kind of resolver such as the Domain Name System. Consult a good book on networking and system administration if you need to modify this data.

13.2 Finding Network Interfaces

Problem

You wish to find out about the computer’s networking arrangements.

Solution

Use the NetworkInterface class.

Discussion

Every computer on a network has one or more “network interfaces.” On typical desktop machines, a network interface represents a network card or network port or some software network interface, such as the loopback interface. Each interface has an operating system–defined name. On most versions of Unix, these devices have a two- or three-character device driver name plus a digit (starting from 0); for example, eth0 or en0 for the first Ethernet on systems that hide the details of the card manufacturer, or de0 and de1 for the first and second Digital Equipment2 DC21x4x-based Ethernet card, xl0 for a 3Com EtherLink XL, and so on. The loopback interface is almost invariably lo0 on all Unix-like platforms.

So what? Most of the time this is of no consequence to you. If you have only one network connection, like a cable link to your ISP, you really don’t care. Where this matters is on a server, where you might need to find the address for a given network, for example. The NetworkInterface class lets you find out. It has static methods for listing the interfaces and other methods for finding the addresses associated with a given interface. The program in Example 13-3 shows some examples of using this class. Running it prints the names of all the local interfaces. If you happen to be on a computer named laptop, it prints the machine’s network address; if not, you probably want to change it to accept the local computer’s name from the command line; this is left as an exercise for the reader.

Example 13-3. main/src/main/java/network/NetworkInterfaceDemo.java
public class NetworkInterfaceDemo {
    public static void main(String[] a) throws IOException {
        Enumeration<NetworkInterface> list = NetworkInterface.getNetworkInterfaces();
        while (list.hasMoreElements()) {
            // Get one NetworkInterface
            NetworkInterface iface = list.nextElement();
            // Print its name
            System.out.println(iface.getDisplayName());
            Enumeration<InetAddress> addrs = iface.getInetAddresses();
            // And its address(es)
            while (addrs.hasMoreElements()) {
                InetAddress addr = addrs.nextElement();
                System.out.println(addr);
            }

        }
        // Try to get the Interface for a given local (this machine's) address
        InetAddress destAddr = InetAddress.getByName("laptop");
        try {
            NetworkInterface dest = NetworkInterface.getByInetAddress(destAddr);
            System.out.println("Address for " + destAddr + " is " + dest);
        } catch (SocketException ex) {
            System.err.println("Couldn't get address for " + destAddr);
        }
    }
}

13.3 Returning a Response (String or Binary)

Problem

You need to write a string or binary data to the client.

Solution

The socket gives you an InputStream and an OutputStream. Use them.

Discussion

The client socket examples in the previous chapter called the getInputStream() and getOutputStream() methods. These examples do the same. The main difference is that these ones get the socket from a ServerSocket’s accept() method. Another distinction is, by definition, that normally the server creates or modifies the data and sends it to the client. Example 13-4 is a simple Echo server, which the Echo client of Recipe 12.5 can connect to. This server handles one complete connection with a client, then goes back and does the accept() to wait for the next client.

Example 13-4. main/src/main/java/network/EchoServer.java
public class EchoServer {
    /** Our server-side rendezvous socket */
    protected ServerSocket sock;
    /** The port number to use by default */
    public final static int ECHOPORT = 7;
    /** Flag to control debugging */
    protected boolean debug = true;

    /** main: construct and run */
    public static void main(String[] args) {
        int p = ECHOPORT;
        if (args.length == 1) {
            try {
                p = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                System.err.println("Usage: EchoServer [port#]");
                System.exit(1);
            }
        }
        new EchoServer(p).handle();
    }

    /** Construct an EchoServer on the given port number */
    public EchoServer(int port) {
        try {
            sock = new ServerSocket(port);
        } catch (IOException e) {
            System.err.println("I/O error in setup");
            System.err.println(e);
            System.exit(1);
        }
    }

    /** This handles the connections */
    protected void handle() {
        Socket ios = null;
        while (true) {
            try {
                System.out.println("Waiting for client...");
                ios = sock.accept();
                System.err.println("Accepted from " +
                    ios.getInetAddress().getHostName());
                try (BufferedReader is = new BufferedReader(
                            new InputStreamReader(ios.getInputStream(), "8859_1"));
                        PrintWriter os = new PrintWriter(
                            new OutputStreamWriter(ios.getOutputStream(), "8859_1"),
                            true);) {
                    String echoLine;
                    while ((echoLine = is.readLine()) != null) {
                        System.err.println("Read " + echoLine);
                        os.print(echoLine + "
");
                        os.flush();
                        System.err.println("Wrote " + echoLine);
                    }
                    System.err.println("All done!");
                }
            } catch (IOException e) {
                System.err.println(e);
            }
        }
        /* NOTREACHED */
    }
}

To send a string across an arbitrary network connection, some authorities recommend sending both the carriage return and the newline character; many protocol specifications require that you do so. This explains the in the code. If the other end is a DOS program or a Telnet-like program, it may be expecting both characters. On the other hand, if you are writing both ends, you can simply use println()—followed always by an explicit flush() before you read—-to prevent the deadlock of having both ends trying to read with one end’s data still in the PrintWriter’s buffer!

If you need to process binary data, use the data streams from java.io instead of the readers/writers. I need a server for the DaytimeBinary program of Recipe 12.6. In operation, it should look like the following:

C:javasrc
etwork>java network.DaytimeBinary
Remote time is 3161316799
BASE_DIFF is 2208988800
Time diff == 952284799
Time on localhost is Sun Mar 08 19:33:19 GMT 2014

C:javasrc
etwork>time/t
Current time is  7:33:23.84p

C:javasrc
etwork>date/t
Current date is Sun 03-08-2014

C:javasrc
etwork>

Well, it happens that I have such a program in my arsenal, so I present it in Example 13-5. Note that it directly uses certain public constants defined in the client class. Normally these are defined in the server class and used by the client, but I wanted to present the client code first.

Example 13-5. main/src/main/java/network/DaytimeServer.java
public class DaytimeServer {
    /** Our server-side rendezvous socket */
    ServerSocket sock;
    /** The port number to use by default */
    public final static int PORT = 37;

    /** main: construct and run */
    public static void main(String[] argv) {
        new DaytimeServer(PORT).runService();
    }

    /** Construct a DaytimeServer on the given port number */
    public DaytimeServer(int port) {
        try {
            sock = new ServerSocket(port);
        } catch (IOException e) {
            System.err.println("I/O error in setup
" + e);
            System.exit(1);
        }
    }

    /** This handles the connections */
    protected void runService() {
        Socket ios = null;
        DataOutputStream os = null;
        while (true) {
            try {
                System.out.println("Waiting for connection on port " + PORT);
                ios = sock.accept();
                System.err.println("Accepted from " +
                    ios.getInetAddress().getHostName());
                os = new DataOutputStream(ios.getOutputStream());
                long time = System.currentTimeMillis();

                time /= 1000;    // Daytime Protocol is in seconds

                // Convert to Java time base.
                time += RDateClient.BASE_DIFF;

                // Write it, truncating cast to int since it is using
                // the Internet Daytime protocol which uses 4 bytes.
                // This will fail in the year 2038, along with all
                // 32-bit timekeeping systems based from 1970.
                // Remember, you read about the Y2038 crisis here first!
                os.writeInt((int)time);
                os.close();
            } catch (IOException e) {
                System.err.println(e);
            }
        }
    }
}

13.4 Returning Object Information Across a Network Connection

Problem

You need to return an object across a network connection.

Solution

Create the object you need, and write it using an ObjectOutputStream created on top of the socket’s output stream.

Discussion

The program in Example 12-7 in the previous chapter reads a Date object over an ObjectInputStream. Example 13-6, the DaytimeObjectServer (the other end of that process), is a program that constructs a Date object each time it’s connected to and returns it to the client.

Example 13-6. main/src/main/java/network/DaytimeObjectServer.java
public class DaytimeObjectServer {
    /** The TCP port for the object time service. */
    public static final short TIME_PORT = 1951;

    public static void main(String[] argv) {
        ServerSocket sock;
        Socket  clientSock;
        try {
            sock = new ServerSocket(TIME_PORT);
            while ((clientSock = sock.accept()) != null) {
                System.out.println("Accept from " +
                    clientSock.getInetAddress());
                ObjectOutputStream os = new ObjectOutputStream(
                    clientSock.getOutputStream());

                // Construct and write the Object
                os.writeObject(LocalDateTime.now());

                os.close();
            }

        } catch (IOException e) {
            System.err.println(e);
        }
    }
}

13.5 Handling Multiple Clients

Problem

Your server needs to handle multiple clients.

Solution

Use a thread for each.

Discussion

In the C world, several mechanisms allow a server to handle multiple clients. One is to use a special “system call” select() or poll(), which notifies the server when any of a set of file/socket descriptors is ready to read, ready to write, or has an error. By including its rendezvous socket (equivalent to our ServerSocket) in this list, the C-based server can read from any of a number of clients in any order. Java does not provide this call, because it is not readily implementable on some Java platforms. Instead, Java uses the general-purpose Thread mechanism, as described in Chapter 16 (threads are now commonplace in many programming languages, though not always under that name). Each time the code accepts a new connection from the ServerSocket, it immediately constructs and starts a new thread object to process that client.3

The Java code to implement accepting on a socket is pretty simple, apart from having to catch IOExceptions:

/** Run the main loop of the Server. */
void runServer( ) {
    while (true) {
        try {
            Socket clntSock = sock.accept( );
            new Handler(clntSock).start( );
        } catch(IOException e) {
            System.err.println(e);
        }
    }
}

To use a thread, you must either subclass Thread or implement Runnable. The Handler class must be a subclass of Thread for this code to work as written; if Handler instead implemented the Runnable interface, the code would pass an instance of the Runnable into the constructor for Thread, as in:

Thread t = new Thread(new Handler(clntSock));
t.start( );

But as written, Handler is constructed using the normal socket returned by accept(), and normally calls the socket’s getInputStream() and getOutputStream() methods and holds its conversation in the usual way. I’ll present a full implementation, a threaded echo client. First, a session showing it in use:

$ java network.EchoServerThreaded
EchoServerThreaded ready for connections.
Socket starting: Socket[addr=localhost/127.0.0.1,port=2117,localport=7]
Socket starting: Socket[addr=darian/192.168.1.50,port=13386,localport=7]
Socket starting: Socket[addr=darian/192.168.1.50,port=22162,localport=7]
Socket ENDED: Socket[addr=darian/192.168.1.50,port=22162,localport=7]
Socket ENDED: Socket[addr=darian/192.168.1.50,port=13386,localport=7]
Socket ENDED: Socket[addr=localhost/127.0.0.1,port=2117,localport=7]

Here, I connected to the server once with my EchoClient program and, while still connected, called it up again (and again) with an operating system–provided Telnet client. The server communicated with all the clients concurrently, sending the answers from the first client back to the first client, and the data from the second client back to the second client. In short, it works. I ended the sessions with the end-of-file character in the program and used the normal disconnect mechanism from the Telnet client. Example 13-7 is the code for the server.

Example 13-7. main/src/main/java/network/EchoServerThreaded.java
public class EchoServerThreaded {

    public static final int ECHOPORT = 7;

    public static void main(String[] av) {
        new EchoServerThreaded().runServer();
    }

    public void runServer() {
        ServerSocket sock;
        Socket clientSocket;

        try {
            sock = new ServerSocket(ECHOPORT);

            System.out.println("EchoServerThreaded ready for connections.");

            /* Wait for a connection */
            while (true) {
                clientSocket = sock.accept();
                /* Create a thread to do the communication, and start it */
                new Handler(clientSocket).start();
            }
        } catch (IOException e) {
            /* Crash the server if IO fails. Something bad has happened */
            System.err.println("Could not accept " + e);
            System.exit(1);
        }
    }

    /** A Thread subclass to handle one client conversation. */
    class Handler extends Thread {
        Socket sock;

        Handler(Socket s) {
            sock = s;
        }

        public void run() {
            System.out.println("Socket starting: " + sock);
            try (BufferedReader is = new BufferedReader(
                        new InputStreamReader(sock.getInputStream()));
                    PrintStream os = new PrintStream(
                        sock.getOutputStream(), true);) {
                String line;
                while ((line = is.readLine()) != null) {
                    os.print(line + "
");
                    os.flush();
                }
                sock.close();
            } catch (IOException e) {
                System.out.println("IO Error on socket " + e);
                return;
            }
            System.out.println("Socket ENDED: " + sock);
        }
    }
}

A lot of short transactions can degrade performance, because each client causes the creation of a new threaded object. If you know or can reliably predict the degree of concurrency that is needed, an alternative paradigm involves the precreation of a fixed number of threads. But then how do you control their access to the ServerSocket? A look at the ServerSocket class documentation reveals that the accept() method is not synchronized, meaning that any number of threads can call the method concurrently. This could cause bad things to happen. So I use the synchronized keyword around this call to ensure that only one client runs in it at a time, because it updates global data. When no clients are connected, you will have one (randomly selected) thread running in the ServerSocket object’s accept() method, waiting for a connection, plus n-1 threads waiting for the first thread to return from the method. As soon as the first thread manages to accept a connection, it goes off and holds its conversation, releasing its lock in the process so that another randomly chosen thread is allowed into the accept() method. Each thread’s run() method has an infinite loop beginning with an accept() and then holding the conversation. The result is that client connections can get started more quickly, at a cost of slightly greater server startup time. Doing it this way also avoids the overhead of constructing a new Handler or Thread object each time a request comes along. This general approach is similar to what the popular Apache web server does, although it normally creates a number or pool of identical processes (instead of threads) to handle client connections. Accordingly, I have modified the EchoServerThreaded class shown in Example 13-7 to work this way, as you can see in Example 13-8.

Example 13-8. main/src/main/java/network/EchoServerThreaded2.java
public class EchoServerThreaded2 {

    public static final int ECHOPORT = 7;

    public static final int NUM_THREADS = 4;

    /** Main method, to start the servers. */
    public static void main(String[] av) {
        new EchoServerThreaded2(ECHOPORT, NUM_THREADS);
    }

    /** Constructor */
    public EchoServerThreaded2(int port, int numThreads) {
        ServerSocket servSock;

        try {
            servSock = new ServerSocket(port);

        } catch (IOException e) {
            /* Crash the server if IO fails. Something bad has happened */
            throw new RuntimeException("Could not create ServerSocket ", e);
        }

        // Create a series of threads and start them.
        for (int i = 0; i < numThreads; i++) {
            new Handler(servSock, i).start();
        }
    }

    /** A Thread subclass to handle one client conversation. */
    class Handler extends Thread {
        ServerSocket servSock;
        int threadNumber;

        /** Construct a Handler. */
        Handler(ServerSocket s, int i) {
            servSock = s;
            threadNumber = i;
            setName("Thread " + threadNumber);
        }

        public void run() {
            /*
             * Wait for a connection. Synchronized on the ServerSocket while
             * calling its accept() method.
             */
            while (true) {
                try {
                    System.out.println(getName() + " waiting");

                    Socket clientSocket;
                    // Wait here for the next connection.
                    synchronized (servSock) {
                        clientSocket = servSock.accept();
                    }
                    System.out.println(
                        getName() + " starting, IP=" + clientSocket.getInetAddress());
                    try (BufferedReader is = new BufferedReader(
                            new InputStreamReader(clientSocket.getInputStream()));
                            PrintStream os = new PrintStream(
                                clientSocket.getOutputStream(), true);) {
                        String line;
                        while ((line = is.readLine()) != null) {
                            os.print(line + "
");
                            os.flush();
                        }
                        System.out.println(getName() + " ENDED ");
                        clientSocket.close();
                    }
                } catch (IOException ex) {
                    System.out.println(getName() + ": IO Error on socket " + ex);
                    return;
                }
            }
        }
    }
}

It is quite possible to implement a server of this sort with NIO, the “new” (back in J2SE 1.4) IO package. However, the code to do so outweighs anything in this chapter, and it is fraught with “issues.” There are several good tutorials on the Internet for the person who truly needs the performance gain of using NIO to manage server connections.

13.6 Serving the HTTP Protocol

Problem

You want to serve up a protocol such as HTTP.

Solution

Create a ServerSocket and write some code that “speaks” the particular protocol. Or, better, use a Java-powered web server such as Apache Tomcat or a Java Enterprise Edition (Java EE) server such as JBoss WildFly.

Discussion

You can implement your own HTTP protocol server for very simple applications, which we’ll do here. For any serious development, you want to use the Java Enterprise Edition; see the note at the beginning of this chapter.

This example just constructs a ServerSocket and listens on it. When connections come in, they are replied to using the HTTP protocol. So it is somewhat more involved than the simple Echo server presented in Recipe 13.3. However, it’s not a complete web server; the filename in the request is ignored, and a standard message is always returned. This is thus a very simple web server; it follows only the bare minimum of the HTTP protocol needed to send its response back. For a real web server written in Java, get Tomcat from the Apache Tomcat website or any of the Jakarta/JavaEE Application Servers. The code shown in Example 13-9, however, is enough to understand how to build a simple server that responds to requests using a protocol.

Example 13-9. main/src/main/java/network/WebServer0.java
public class WebServer0 {
    public static final int HTTP = 80;
    public static final String CRLF = "
";
    ServerSocket s;
    /** A link to the source of this program, used in error message */
    static final String VIEW_SOURCE_URL =
    "https://github.com/IanDarwin/javasrc/tree/master/main/src/main/java/network";

    /**
     * Main method, just creates a server and call its runServer().
     */
    public static void main(String[] args) throws Exception {
        System.out.println("DarwinSys JavaWeb Server 0.0 starting...");
        WebServer0 w = new WebServer0();
        int port = HTTP;
        if (args.length == 1) {
            port = Integer.parseInt(args[0]);
            }
        w.runServer(port);        // never returns!!
    }

    /** Get the actual ServerSocket; deferred until after Constructor
     * so subclass can mess with ServerSocketFactory (e.g., to do SSL).
     * @param port The port number to listen on
     */
    protected ServerSocket getServerSocket(int port) throws Exception {
        return new ServerSocket(port);
    }

    /** RunServer accepts connections and passes each one to handler. */
    public void runServer(int port) throws Exception {
        s = getServerSocket(port);
        while (true) {
            try {
                Socket us = s.accept();
                Handler(us);
            } catch(IOException e) {
                System.err.println(e);
                return;
            }

        }
    }

    /** Handler() handles one conversation with a Web client.
     * This is the only part of the program that "knows" HTTP.
     */
    public void Handler(Socket s) {
        BufferedReader is;    // inputStream, from Viewer
        PrintWriter os;        // outputStream, to Viewer
        String request;        // what Viewer sends us.
        try {
            String from = s.getInetAddress().toString();
            System.out.println("Accepted connection from " + from);
            is = new BufferedReader(new InputStreamReader(s.getInputStream()));
            request = is.readLine();
            System.out.println("Request: " + request);

            os = new PrintWriter(s.getOutputStream(), true);
            os.print("HTTP/1.0 200 Here is your data" + CRLF);
            os.print("Content-type: text/html" + CRLF);
            os.print("Server-name: DarwinSys NULL Java WebServer 0" + CRLF);
            String reply1 = "<html><head>" +
                "<title>Wrong System Reached</title></head>
" +
                "<h1>Welcome, ";
            String reply2 = ", but...</h1>
" +
                "<p>You have reached a desktop machine " +
                "that does not run a real Web service.
" +
                "<p>Please pick another system!</p>
" +
                "<p>Or view <a href="" + VIEW_SOURCE_URL + "">" +
                "the WebServer0 source on github</a>.</p>
" +
                "<hr/><em>Java-based WebServer0</em><hr/>
" +
                "</html>
";
            os.print("Content-length: " +
                (reply1.length() + from.length() + reply2.length()) + CRLF);
            os.print(CRLF);
            os.print(reply1 + from + reply2 + CRLF);
            os.flush();
            s.close();
        } catch (IOException e) {
            System.out.println("IOException " + e);
        }
        return;
    }
}

13.7 Securing a Web Server with SSL and JSSE

Problem

You want to protect your network traffic from prying eyes or malicious modification, while the data is in transit.

Solution

Use the Java Secure Socket Extension, JSSE, to encrypt your traffic.

Discussion

JSSE provides services at a number of levels, but the simplest way to use it is simply to get your ServerSocket from an SSLServerSocketFactory instead of using the ServerSocket constructor directly. SSL is the Secure Sockets Layer; a revised version is known as TLS. It is specifically for use on the Web. To secure other protocols, you’d have to use a different form of the SocketFactory.

The SSLServerSocketFactory returns a ServerSocket that is set up to do SSL encryption. Example 13-10 uses this technique to override the getServerSocket() method in Recipe 13.6. If you’re thinking this is too easy, you’re wrong!

Example 13-10. main/src/main/java/network/JSSEWebServer0
/**
 * JSSEWebServer - subclass trivial WebServer0 to make it use SSL.
 * N.B. You MUST have set up a server certificate (see the
 * accompanying book text), or you will get the dreaded
 * javax.net.ssl.SSLHandshakeException: no cipher suites in common
 * (because without it JSSE can't use any of its built-in ciphers!).
 */
public class JSSEWebServer0 extends WebServer0 {

    public static final int HTTPS = 8443;

    public static void main(String[] args) throws Exception {
        if (System.getProperty("javax.net.ssl.keyStore") == null) {
            System.err.println(
                "You must pass in a keystore via -D; see the documentation!");
            System.exit(1);
        }
        System.out.println("DarwinSys JSSE Server 0.0 starting...");
        JSSEWebServer0 w = new JSSEWebServer0();
        w.runServer(HTTPS);        // never returns!!
    }

    /** Get an HTTPS ServerSocket using JSSE.
     * @see WebServer0#getServerSocket(int)
     * @throws ClassNotFoundException if the SecurityProvider cannot be instantiated.
     */
    protected ServerSocket getServerSocket(int port) throws Exception {

        SSLServerSocketFactory ssf =
            (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();

        return ssf.createServerSocket(port);
    }

}

That is, indeed, all the Java code one needs to write. You do have to set up an SSL Certificate. For demonstration purposes, this can be a self-signed certificate; the steps in https://darwinsys.com/java/selfsigncert.html (Steps 1–4) will suffice. You have to tell the JSSE layer where to find your keystore:

java -Djavax.net.ssl.keyStore=/home/ian/.keystore -Djavax.net.ssl.
keyStorePassword=secrit JSSEWebServer0

The typical client browser raises its eyebrows at a self-signed certificate (see Figure 13-1), but, if the user OKs it, will accept the certificate.

jcb4 1301
Figure 13-1. Browser caution

Figure 13-2 shows the output of the simple WebServer0 being displayed over the HTTPS protocol (notice the padlock in the lower-right corner).

jcb4 1302
Figure 13-2. With encryption

See Also

JSSE can do much more than encrypt web server traffic; this is, however, sometimes seen as its most exciting application. For more information on JSSE, see the Sun website or Java Security by Scott Oaks (O’Reilly).

13.8 Creating a REST Service with JAX-RS

Problem

You want to implement a RESTful server, using the provided Java EE/Jakarta EE APIs.

Solution

Use JAX-RS annotations on a class that provides a service; install it in an enterprise application server.

Discussion

This operation consists of both coding and configuration.

The coding steps consist of creating a class that extends the JAX-RS Application class, and adding annotations to a class that provides a service.

Here is a minimal Application class:

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("")
public class RestApplication extends Application {
	// Empty
}

Here is a “hello world” type service class with the annotations needed to make it a service class and to have three sample methods.

Example 13-11. restdemo/src/main/java/rest/RestService.java
@Path("")
@ApplicationScoped
public class RestService {

    public RestService() {
        System.out.println("RestService.init()");
    }

    @GET @Path("/timestamp")
    @Produces(MediaType.TEXT_PLAIN)
    public String getDate() {
        return LocalDateTime.now().toString();
    }

    /** A Hello message method
     */
    @GET @Path("/greeting/{userName}")
    @Produces("text/html")
    public String doGreeting(@PathParam("userName")String userName) {
        System.out.println("RestService.greeting()");
        if (userName == null || userName.trim().length() <= 3) {
            return "Missing or too-short username";
        }
        return String.format("<h1>Welcome %s</h1><p>%s, We are glad to see you back!",
            userName, userName);
    }

    /** Used to download all items */
    @GET @Path("/names")
    @Produces(MediaType.APPLICATION_JSON)
    public List<String> findTasksForUser() {
        return List.of("Robin", "Jedunkat", "Lyn", "Glen");
    }
}

Now the class must be deployed. If we have created a proper Maven project structure (see Recipe 1.7) and have provided an application-server-specific Maven plug-in, and our development server is running, we can use some variation on mvn deploy. In the present case I have set this up, in the rest subdirectory, for deployment to WildFly, a server from the JBoss open source community funded by RedHat Inc. I need only say mvn wildfly:deploy to have the application compiled, package, and deployed to my server.

For deploying REST services as a micro-service based on Eclipse MicroProfile, you may wish to investigate the Quarkus Framework.

Once the service is deployed, you can explore it interactively with a browser or, for simple GET requests, a Telnet client:

$ telnet localhost 8080 # output cleaned up
Escape character is '^]'.
GET /rest/timestamp HTTP/1.0
Connection: keep-alive

HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8

2019-10-16T19:54:31.42

GET /rest/greeting/Ian%20Darwin HTTP/1.0

HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8

<h1>Welcome Ian Darwin</h1><p>Ian Darwin, We are glad to see you back!

get /rest/names HTTP/1.0
Accept: Application/JSON

HTTP/1.1 200 OK
Content-Type: application/json

["Robin","Jedunkat","Lyn","Glen"]
^] (CTRL/C)
$

An issue with REST is that there is not an official standard for documenting the API or protocol offered by a server (there are several competing specifications). So people writing clients must either rely on plain documentation offered by the server’s developers, or use trial and error to discover the protocol. Our example here is simple enough that we don’t have this problem, but imagine a class with 20 or 30 methods in it.

The Spring Framework offers an API that is very similar to the JAX-RS API used here; if you are already using Spring, it may be simpler to use their annotations.

13.9 Network Logging

Problem

Your class is running inside a server container, and its debugging output is hard to obtain.

Solution

Use a network-based logger like the Java Logging API (JUL), Apache Logging Services Project’s log4j, or the simple one shown here.

Discussion

Getting the debug output from a desktop client is fairly easy on most operating systems. But if the program you want to debug is running in a “container” like a servlet engine or an EJB server, it can be difficult to obtain debugging output, particularly if the container is running on a remote computer. It would be convenient if you could have your program send messages back to a program on your desktop machine for immediate display. Needless to say, it’s not that hard to do this with Java’s socket mechanism.

Many logging APIs can handle this:

  • Java has had for years a standard logging API JUL (discussed in Recipe 13.12) that talks to various logging mechanisms including Unix syslog.

  • The Apache Logging Services Project produces log4j, which is used in many open source projects that require logging (see Recipe 13.11).

  • The Apache Jakart Commons Logging (JCL). Not discussed here; similar to the others.

  • SLF4J (Simple Logging Facade For Java, see Recipe 13.10) is the newest and, as the name implies, a facade that can use the others.

  • And, before these became widely used, I wrote a small, simple API to handle this type of logging function. My netlog is not discussed here because it is preferable to use one of the standard logging mechanisms; its code is in the logging subdirectory of the javasrc repo if you want to exhume it.

The JDK logging API, log4j, and SFL4J are more fully fleshed out and can write to such destinations as a file, an OutputStream or Writer, or a remote log4j, Unix syslog, or Windows Event Log server.

The program being debugged is the “client” from the logging API’s point of view—even though it may be running in a server-side container such as a web server or application server—because the “network client” is the program that initiates the connection. The program that runs on your desktop machine is the “server” program for sockets because it waits for a connection to come along.

If you want to run any network-based logger reachable from any public network, you need to be more aware of security issues. One common form of attack is a simple denial-of-service (DoS), during which the attacker makes a lot of connections to your server in order to slow it down. If you are writing the log to disk, for example, the attacker could fill up your disk by sending lots of garbage. In common use, your log listener would be behind a firewall and not reachable from outside, but if this is not the case, beware of the DoS attack.

13.10 Setting Up SLF4J

Problem

You want to use a logging API that lets you use any of the other logging APIs, for example, so your code can be used in other projects without requiring them to switch logging APIs.

Solution

Use SLF4J: get a Logger from the LoggerFactory, and use its various methods for logging.

Discussion

Using SLF4j requires only one JAR file to compile, slf4j-api-1.x.y.jar (where x and y will change over time). To actually get logging output, you need to add one of several implementation JARs to your runtime classpath, the simplest of which is slf4j-simple-1.x.y.jar (where x and y should match between the two files).

Once you’ve added those JAR files to your build script or on your classpath, you can get a Logger by calling LoggerFactory.getLogger(), passing either the string name of a class or package, or just the current Class reference. Then call the logger’s logging methods. A simple example is in Example 13-12.

Example 13-12. main/src/main/java/logging/Slf4jDemo.java
public class Slf4jDemo {

    final static Logger theLogger =
            LoggerFactory.getLogger(Slf4jDemo.class);

    public static void main(String[] args) {

        Object o = new Object();
        theLogger.info("I created this object: " + o);

    }
}

There are various methods used to log information at different levels of severity, which are shown in Table 13-1.

Table 13-1. SLF4j logging methods
Name Meaning

trace

Verbose debugging (disabled by default)

debug

Verbose debugging

info

low-level informational message

warn

Possible error

error

Serious error

One of the advantages of SLF4j over most of the other logging APIs is the avoidance of the “dead string” anti-pattern. In the use of many other logger APIs you may find code like:

logger.log("The value is " + object + "; this is not good");

This can lead to a performance problem, in that the object’s toString() is implicitly called, and two string concatenations performed, before we even know if the logger is going to use them! If this is in code that is called repeatedly, a lot of overhead can be wasted.

This led the other logging packages to offer “code guards,” based on logger methods that can find out very quickly if a logger is enabled, leading to code like the following:

if (logger.isEnabled()) {
	logger.log("The value is " + object + "; this is not good");
}

This solves the performance problem, but clutters the code! SLF4J’s solution is to use a mechanism similar to (but not quite compatible with) Java’s MessageFormat mechanism, as shown in Example 13-13.

Example 13-13. main/src/main/java/logging/Slf4jDemo2.java
public class Slf4jDemo2 {

    final static Logger theLogger = LoggerFactory.getLogger(Slf4jDemo2.class);

    public static void main(String[] args) {

        try {
            Person p = new Person();
            // populate person's fields here...
            theLogger.info("I created an object {}", p);

            if (p != null) {    // bogus, just to show logging
                throw new IllegalArgumentException("Just testing");
            }
        } catch (Exception ex) {
            theLogger.error("Caught Exception: " + ex, ex);
        }
    }
}

Although this doesn’t demonstrate network logging, it is easy to accomplish this in conjunction with a logging implementation like Log4J or JUL (Java Util Logging, a standard part of the JDK) which allow you to provide configurable logging. Log4J is described in the next recipe.

See Also

The SLF4J website contains a Getting Started manual that discusses the various classpath options. There are also some Maven artifacts for the various options.

13.11 Network Logging with log4j

Problem

You wish to write log file messages using log4j.

Solution

Get a Logger and use its log() method or the convenience methods. Control logging by changing a properties file. Use the org.apache.logging.log4j.net package to make it network based.

Discussion

Warning

This recipe describes Version 2 of the log4j API. Between Version 1 and Version 2, there are changes to the package names, file names, and the method used to obtain a logger. If you see code using, for example, Logger.getLogger("class name"), that code is written to the older API, which is no longer maintained (the Log4J web site refers to Log4j 1.2, and versions up to 2.12, as “legacy”; we are using 2.13 in this recipe). A good degree of compatibility is offered for code written to the 1.x API; see https://logging.apache.org/log4j/2.x/manual/compatibility.html.

Logging using log4j is simple, convenient, and flexible. You need to get a Logger object from the static method LogManager.getLogger(), The Logger has public void methods (debug(), info(), warn(), error(), and fatal()), each of which takes one Object to be logged (and an optional Throwable). As with System.out.println(), if you pass in anything that is not a String, its toString() method is called. A generic logging method is also included:

public void log(Level level, Object message);

The Level class is defined in the log4j2 API. The standard levels are, in order, DEBUG < INFO < WARN < ERROR < FATAL. That is, debug messages are considered the least important, and fatal the most important. Each Logger has a level associated with it; messages whose level is less than the Logger’s level are silently discarded.

A simple application can log messages using these few statements:

public class Log4JDemo {

    private static Logger myLogger = LogManager.getLogger();

    public static void main(String[] args) {

        Object o = new Object();
        myLogger.info("I created an object: " + o);

    }
}

If you compile and run this program with no log4j2.properties file, it does not produce any logging output (see the log4j2demos script in the source folder). We need to create a configuration file, whose default name is log4j2.properties. You can also provide the logfile name via System Properties: -Dlog4j.configurationFile=URL.

Tip

Log4j configuration is very flexible, and therefore very complex. Even their own documentation admits that “Trying to configure Log4j without understanding [the logging architecture] will lead to frustration.” See this Apache website for full details on the logging configuration file location and format.

Every Logger has a Level to specify what level of messages to write, and an Appender, which is the code that writes the messages out. A ConsoleAppender writes to System.out, of course; other loggers write to files, operating system–level loggers, and so on. A simple configuration file looks something like this:

# Log4J2 properties file for the logger demo programs.
# tag::generic[] # Ensure file gets copied for Java Cookbook

# WARNING - log4j2.properties must be on your CLASSPATH,
# not necessarily in your source directory.

# The configuration file for Version 2 is different from V1!

rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT

appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %m%n
appender.console.filter.threshold.type = ThresholdFilter
appender.console.filter.threshold.level = debug

This file gives the root logger a level of DEBUG, which causes it to write all messages. The config file also sets up an appender of APPENDER1, which is configured on the next few lines. Note that I didn’t have to refer to the com.darwinsys Logger. Because every Logger inherits from the root logger, a simple application needs to configure only the root logger. The properties file can also be an XML document or you can write your own configuration parser (almost nobody does this).

Warning

If the logging configuration file is not found, the default root logger defaults the root logger to Level.ERROR, so you will not see any output below the ERROR level.

With the configuration file in place, the demonstration works better. Running this program (with the appropriate classpath as done in the scripts) produces this output:

$ java Log4j2Demo
I created an object: java.lang.Object@477b4cdf
$

A common use of logging is to log a caught Exception, as shown in Example 13-14.

Example 13-14. main/src/main/java/Log4JDemo2.java - Log4j—catching and logging
public class Log4JDemo2 {

    private static Logger myLogger = LogManager.getLogger();

    public static void main(String[] args) {

        try {
            Object o = new Object();
            myLogger.info("I created an object: " + o);
            if (o != null) {    // bogus, just to show logging
                throw new IllegalArgumentException("Just testing");
            }
        } catch (Exception ex) {
            myLogger.error("Caught Exception: " + ex, ex);
        }
    }
}

When run, Log4JDemo2 produces the expected output:

$ java Log4JDemo2
I created an object: java.lang.Object@477b4cdf
Caught Exception: java.lang.IllegalArgumentException: Just testing
java.lang.IllegalArgumentException: Just testing
	at logging.Log4JDemo2.main(Log4JDemo2.java:17) [classes/:?]
$

Much of the flexibility of log4j2 stems from its use of external configuration files; you can enable or disable logging without recompiling the application. A properties file that eliminates most logging might have this entry:

rootLogger.level = fatal

Only fatal error messages print; all levels less than that are ignored.

To log from a client to a server on a remote machine, the SocketAppender can be used. There is also an SmtpAppender to send urgent notices via email. See https://logging.apache.org/log4j/2.x/manual/appenders.html for details on all the supported Appenders. Here is log4j2-network.properties, the socket-based networking version of the configuration file:

# Log4J2 properties file for the NETWORKED logger demo programs.
# tag::generic[] # Ensure file gets copied for Java Cookbook

# WARNING - log4j2.properties must be on your CLASSPATH,
# not necessarily in your source directory.

# The configuration file for Version 2 is different from V1!

rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT

appender.console.type = Socket
appender.console.name = STDOUT
appender.console.host = localhost
appender.console.port = 6666
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %m%n
appender.console.filter.threshold.type = ThresholdFilter
appender.console.filter.threshold.level = debug

This file gets passed to the demo programs via a Java System Property in the netdemos script:

build=../../../../target/classes
log4j2_jar=
${HOME}/.m2/repository/org/apache/logging/log4j/log4j-api/2.13.0/log4j-api-2.13.0.jar:
${HOME}/.m2/repository/org/apache/logging/log4j/log4j-core/2.13.0/log4j-core-2.13.0.jar

echo "==> Log4JDemo"
java -Dlog4j.configurationFile=log4j2-network.properties 
	-classpath ".:${build}:${log4j2_jar}" logging.Log4JDemo

echo "==> Log4JDemo2"
java -Dlog4j.configurationFile=log4j2-network.properties 
	-classpath ".:${build}:${log4j2_jar}" logging.Log4JDemo2

When run with the log4j2-network.properties file, you have to arrange for a listener on the other end. On Unix systems the nc (or netcat) program will work fine:

$ nc -kl 6666
I created an object: java.lang.Object@37ceb1df
I created an object: java.lang.Object@37ceb1df
Caught Exception: java.lang.IllegalArgumentException: Just testing
java.lang.IllegalArgumentException: Just testing
	at logging.Log4JDemo2.main(Log4JDemo2.java:17) [classes/:?]
^C
$

Netcat option -l says to listen on the numbered port; -k tells it to _k_eep listening, that is, to re-open the connection when the client closes it, as happens when each demo program exits.

There is a performance issue with some logging calls. Consider some expensive operation, like a toString() or two along with several string concatenations passed to a Log.info() call in an often-used piece of code. If this is placed into production with a higher logging level, all the work will be done but the resultant string will never be used. In older APIs we used to use “code guards”, methods like “isLoggerEnabled(Level)”, to determine whether to bother creating the string. Nowadays, the preferred method is to create the string inside a Lambda expression (see Chapter 9). All the log methods have an overload that accepts a Supplier argument:

Example 13-15. main/src/main/java/logging/Log4J2Lambda.java
public class Log4JLambda {

    private static Logger myLogger = LogManager.getLogger();

    public static void main(String[] args) {

        Person customer = getPerson();
        myLogger.info( () -> String.format(
            "Value %d from Customer %s", customer.value, customer) );

    }

This way the string operations will only be performed if needed: if the logger is operating at the INFO level it will call the Supplier and if not, it won’t do the expensive operation.

When run as part of the log4j2demos script, this prints:

Value 42 from Customer Customer[Robin]

For more information on log4j, visit its main website. log4j2 is free software, distributed under the Apache Software Foundation license.

13.12 Network Logging with java.util.logging

Problem

You wish to write logging messages using the Java logging mechanism.

Solution

Get a Logger, and use it to log your messages and/or exceptions.

Discussion

The Java Logging API (package java.util.logging) is similar to, and was obviously inspired by, the log4j package. You acquire a Logger object by calling the static Logger.getLogger() with a descriptive String. You then use instance methods to write to the log; these methods include:

public void log(java.util.logging.LogRecord);
public void log(java.util.logging.Level,String);
// and a variety of overloaded log(  ) methods
public void logp(java.util.logging.Level,String,String,String);
public void logrb(java.util.logging.Level,String,String,String,String);

// Convenience routines for tracing program flow
public void entering(String,String);
public void entering(String,String,Object);
public void entering(String,String,Object[]);
public void exiting(String,String);
public void exiting(String,String,Object);
public void throwing(String,String,Throwable);

// Convenience routines for log(  ) with a given level
public void severe(String);
public void warning(String);
public void info(String);
public void config(String);
public void fine(String);
public void finer(String);
public void finest(String);

As with log4j, every Logger object has a given logging level, and messages below that level are silently discarded:

public void setLevel(java.util.logging.Level);
public java.util.logging.Level getLevel(  );
public boolean isLoggable(java.util.logging.Level);

As with log4j, objects handle the writing of the log. Each logger has a Handler:

public synchronized void addHandler(java.util.logging.Handler);
public synchronized void removeHandler(java.util.logging.Handler);
public synchronized java.util.logging.Handler[] getHandlers(  );

and each Handler has a Formatter, which formats a LogRecord for display. By providing your own Formatter, you have more control over how the information being passed into the log gets formatted.

Unlike log4j, the Java SE logging mechanism has a default configuration, so Example 13-16 is a minimal logging example program.

Example 13-16. main/src/main/java/logging/JulLogDemo.java

Running it prints the following:

$ juldemos
Jan 31, 2020 1:03:27 PM logging.JulLogDemo main
INFO: I created an object: java.lang.Object@5ca881b5
$ 

As with log4j, one common use is in logging caught exceptions; the code for this is in Example 13-17.

Example 13-17. main/src/main/java/logging/JulLogDemo2.java — Catching and Logging an Exception

As with log4j, java.util.logging accepts a Lambda expression (and has done since Java 8):

Example 13-18. main/src/main/java/logging/JulLambdaDemo.java
/** Demonstrate how Java 8 Lambdas avoid extraneous object creation
 * @author Ian Darwin
 */
public class JulLambdaDemo {
    public static void main(String[] args) {

        Logger myLogger = Logger.getLogger("com.darwinsys.jullambda");

        Object o = new Helper();

        // If you change the log call from finest to info,
        // you see both the systrace from the toString,
        // and the logging output. As it is here,
        // you don't see either, so the toString() is not called!
        myLogger.finest(() -> "I created this object: " + o);
    }

    static class Helper {
        public String toString() {
            System.out.println("JulLambdaDemo.Helper.toString()");
            return "failure!";
        }
    }
}

See Also

A good general reference on this chapter’s topic is Java Network Programming (O’Reilly).

The server side of any network mechanism is extremely sensitive to security issues. It is easy for one misconfigured or poorly written server program to compromise the security of an entire network! Of the many books on network security, two stand out: Firewalls and Internet Security by William R. Cheswick, Steven M. Bellovin, and Aviel D. Rubin (Addison-Wesley) and a series of books with Hacking Exposed in the title, the first in the series by Stuart McClure, Joel Scambray, and George Kurtz (McGraw-Hill).

This completes my discussion of server-side Java using sockets. A chat server could be implemented using several technologies, such as RMI (Remote Methods Invocation), an HTTP web service, JMS (Java Message Service), and a Java Enterprise API that handles store-and-forward message processing. This is beyond the scope of this book, but there’s an example of an RMI chat server in the chat folder of the source distribution, and a JMS chat server in Java Message Service by Mark Richards, Richard Monson-Haefel, and David Chappell.

1 You may not be able to pick just any port number for your own service, of course. Certain well-known port numbers are reserved for specific services and listed in your services file, such as 22 for Secure Shell, 25 for SMTP, and hundreds more. Also, on server-based operating systems, ports below 1024 are considered “privileged” ports and require root or administrator privilege to create. This was an early security mechanism; today, with zillions of single-user desktops connected to the Internet, it provides little real security, but the restriction remains.

2 Digital Equipment was absorbed by Compaq, which was then absorbed by HP, but the name remains de because the engineers who name such things don’t care for corporate mergers anyway.

3 There are some limits to how many threads you can have, which affect only very large, enterprise-scale servers. You can’t expect to have thousands of threads running in the standard Java runtime. For large, high-performance servers, you may wish to resort to native code (see Recipe 18.6) using select() or poll().

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.216.75