Networking - Java 8 Recipes, 2th Edition (2014)

Java 8 Recipes, 2th Edition (2014)

CHAPTER 21. Networking

Today, writing an application that does not communicate over the Internet in some fashion is rare. From sending data to another machine, to scraping information off remote web pages, networking plays an integral part in today’s computing world. Java makes it easy to communicate over a network using the New I/O (NIO) and more new I/O features for the Java platform (NIO.2) APIs. Java SE 7 included a few new features, enabling easier multicasting among other things. With the addition of these new features, the Java platform contains a plethora of programming interfaces to help accomplish network tasks.

This chapter does not attempt to cover every networking feature that is part of the Java language, as the topic is quite large. However, it does provide a handful of recipes that are the most useful to a broad base of developers. You learn about a few of the standard networking concepts, such as sockets, as well as some newer concepts that were introduced with the latest release of the Java language. If you find this chapter interesting and want to learn more about Java networking, you can find lots of resources online. Perhaps the best place to go for learning more is the Oracle documentation at http://download.oracle.com/javase/tutorial/networking/index.html.

21-1. Listening for Connections on the Server

Problem

You want to create a server application that will listen for connections from a remote client.

Solution

Set up a server-side application that makes use of java.net.ServerSocket to listen for requests on a specified port. The following Java class is representative of one that would be deployed onto a server, and it listens for incoming requests on port 1234. When a request is received, the incoming message is printed to the command line and a response is sent back to the client.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServer {

public static void main(String a[]) {
final int httpd = 1234;
ServerSocket ssock = null;
try {
ssock = new ServerSocket(httpd);
System.out.println("have opened port 1234 locally");

Socket sock = ssock.accept();
System.out.println("client has made socket connection");

communicateWithClient(sock);

System.out.println("closing socket");
} catch (Exception e) {
System.out.println(e);
} finally {
try{
ssock.close();
} catch (IOException ex) {
System.out.println(ex);
}
}
}
public static void communicateWithClient(Socket socket) {
BufferedReader in = null;
PrintWriter out = null;

try {
in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(
socket.getOutputStream(), true);

String s = null;
out.println("Server received communication!");
while ((s = in.readLine()) != null) {
System.out.println("received from client: " + s);
out.flush();
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
out.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}

Executing this program along with the client that is built in Recipe 21-2 will result in the following output from the SocketServer:

have opened port 1234 locally
client has made socket connection
received from client: Here is a test.
closing socket

Image Note To run the two recipes so that they work with each other, first start the SocketServer program so that the client can create a socket using the port that is opened in the server program. After the SocketServer starts, initiate the SocketClient program to see the two work together.

Image Caution This SocketServer program opens a port on your machine (1234). Be sure that you have a firewall set running on your machine; otherwise, you will be opening port 1234 to everyone. This could result in your machine being attacked. Open ports create vulnerabilities for attackers to break into machines, kind of like leaving a door in your house open. Note that the example in this recipe has a minimal attack profile because the server is run through only one pass and will print only a single message from the client before the session is closed.

How It Works

Server applications can be used to enable work to be performed on a server via direct communication from one or more client applications. Client applications normally communicate to the server application, send messages or data to the server for processing, and then disconnect. The server application typically listens for client applications, and then performs some processing against a client request once a connection is received and accepted. In order for a client application to connect to a server application, the server application must be listening for connections and then processing them somehow. You cannot simply run a client against any given host and port number combination because doing so would likely result in a refused connection. The server-side application must do three things: open a port, accept and establish client connections, and then communicate with the client connections in some way. In the solution to this recipe, the SocketServer class does all three.

Starting with the main() method, the class begins by opening a new socket on port 1234. This is done by creating a new instance of ServerSocket and passing a port number to it. The port number must not conflict with any other port that is currently in use on the server. It is important to note that ports below 1024 are usually reserved for operating system use, so choose a port number above that range. If you attempt to open a port that is already in use, the ServerSocket will not successfully be created, and the program will fail. Next, the ServerSocketobject’s accept() method is called, returning a new Socket object. Calling the accept() method will do nothing until a client attempts to connect to the server program on the port that has been set up. The accept() method will wait idly until a connection is requested and then it will return the new Socket object bound to the port that was set up on the ServerSocket. This socket also contains the remote port and hostname of the client attempting the connection, so it contains the information on two endpoints and uniquely identifies the TCP connection.

At this point, the server program can communicate with the client program, and it does so using the PrintWriter and BufferedReader objects. In the solution to this recipe, the communicateWithClient() method contains all the code necessary to accept messages from the client program, sends messages back to the client, and then returns control to the main() method that closes the ServerSocket. A new BufferedReader object can be created by generating a new InputStreamReader instance using the socket’s input stream. Similarly, a newPrintWriter object can be created using the socket’s output stream. Notice that this code must be wrapped in a try-catch block in case these objects are not successfully created.

in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);

Once these objects have been successfully created, the server can communicate with the client. It uses a loop to do so, reading from the BufferedReader object (the client input stream) and sending messages back to the client using the PrintWriter object. In the solution to this recipe, the server closes the connection by issuing a break, which causes the loop to end. Control then returns to the main() method.

out.println("Server received communication!");
while ((s = in.readLine()) != null) {
System.out.println("received from client: " + s);
out.flush();
break;
}

In a real-life server program, the server would most likely listen endlessly without using a break to end communication. To handle multiple concurrent clients, each client connection would spawn a separate Thread to handle communication. The server would do something useful with the client communication as well. In the case of an HTML server, it would send back an HTML message to the client. On an SMTP server, the client would send an e-mail message to the server, and the server would then process the e-mail and send it. Socket communication is used for just about any TCP transmission, and both the client and servers create new sockets to perform a successful communication.

21-2. Defining a Network Connection to a Server

Problem

You need to establish a connection to a remote server.

Solution

Create a Socket connection to the remote server using its name and port number where the server is listening for incoming client requests. The following example class creates a Socket connection to a remote server. The code then sends a textual message to the server and receives a response. In the example, the server that the client is attempting to contact is named server-name and the port number is 1234.

Image Tip To create a connection to a local program running on the client machine, set the server-name equal to 127.0.0.1. This is done within the source listing for this recipe. Usually local connections such as this are used only for testing purposes.

public class SocketClient {

public static Socket socket = null;
public static PrintWriter out;
public static BufferedReader in;

public static void main(String[] args) {
createConnection("127.0.0.1", 1234);
}

public static void createConnection(String host, int port) {

try {
//Create socket connection
socket = new Socket(host, port);
// Obtain a handle on the socket output
out = new PrintWriter(socket.getOutputStream(),
true);
// Obtain a handle on the socket input
in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
testConnection();
System.out.println("Closing the connection...");
out.flush();
out.close();
in.close();
socket.close();
System.exit(0);
} catch (UnknownHostException e) {
System.out.println(e);
System.exit(1);
} catch (IOException e) {
System.out.println(e);
System.exit(1);
}
}

public static void testConnection() {
String serverResponse = null;
if (socket != null && in != null && out != null) {
System.out.println("Successfully connected, now testing...");

try {
// Send data to server
out.println("Here is a test.");
// Receive data from server
while((serverResponse = in.readLine()) != null)
System.out.println(serverResponse);
} catch (IOException e) {
System.out.println(e);
System.exit(1);
}
}
}
}

If you’re testing this client against a server that successfully accepts the request, you will see the following result:

Successfully connected, now testing...

Image Note This program will do nothing on its own. To create a server-side socket application that will accept this connection for a complete test, see Recipe 21-1. If you attempt to run this class without specifying a server host that is listening on the provided port, you will receive this exception: java.net.ConnectException: Connection refused.

How It Works

Every client/server connection occurs via a socket, which is an endpoint in a communication link between two different programs. Sockets have port numbers assigned to them, which act as an identifier for the Transmission Control Protocol/Internet Protocol (TCP/IP) layer to use when attempting a connection. A server program that accepts requests from client machines typically listens for new connections on a specified port number. When a client wants to make a request to the server, it creates a new socket utilizing the hostname of the server and the port on which the server is listening and attempts to establish a connection with that socket. If the server accepts the socket, then the connection is successful.

This recipe discusses the client side of the socket connection, so we will not go into the details of what occurs on the server side at this time. However, more information regarding the server side of a connection is covered in Recipe 21-1. The example class in the solution to this recipe is representative of how a client-side program attempts and establishes connections to a server-side program. In this recipe, a method named createConnection() performs the actual connection. It accepts a server hostname and port number, which will be used to create the socket. Within the createConnection() method, the server hostname and port number are passed to the Socket class constructor, creating a new Socket object. Next, a PrintWriter object is created using the Socket object’s output stream, and a BufferedReader object is created using the Socket object’s input stream.

//Create socket connection
socket = new Socket(host, port);
// Obtain a handle on the socket output
out = new PrintWriter(socket.getOutputStream(),
true);
// Obtain a handle on the socket input
in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));

After creating the socket and obtaining the socket’s output stream and input stream, the client can write to the PrintWriter in order to send data to the server. Similarly, to receive a response from the server, the client reads from the BufferedReader object. ThetestConnection() method is used to simulate a conversation between the client and the server program using the newly created socket. To do this, the socket, in, and out variables are checked to ensure that they are not equal to null. If they are not equal to null, the client attempts to send a message to the server by sending a message to the output stream using out.println("Here is a test."). A loop is then created to listen for a response from the server by calling the in.readLine() method until nothing else is received. It then prints the messages that are received.

if (socket != null && in != null && out != null) {
System.out.println("Successfully connected, now testing...");

try {
// Send data to server
out.println("Here is a test.");
// Receive data from server
while((serverResponse = in.readLine()) != null)
System.out.println(serverResponse);
} catch (IOException e) {
System.out.println(e);
System.exit(1);
}
}

The java.net.Socket class is true to the nature of the Java programming language. It enables developers to code against a platform-independent API in order to communicate with network protocols that are specific to different platforms. It abstracts the details of each platform from the developer and provides a straightforward and consistent implementation for enabling client/server communications.

21-3. Bypassing TCP for InfiniBand to Gain Performance Boosts

Problem

Your application, which is deployed on Linux or Solaris, needs to move data very quickly and efficiently, and you need to remove any bottlenecks that could slow things down.

Solution

Use the Sockets Direct Protocol (SDP) to bypass TCP, a possible bottleneck in the process. In order to do this, create an SDP configuration file and set the system property to specify the configuration file location.

Image Note The SDP was added to the Java SE 7 release for applications deployed in the Solaris or Linux operating systems only. SDP was developed to support stream connections over InfiniBand fabric, which Solaris and Linux both support. The Java SE 7 release supports the 1.4.2 and 1.5 versions of OpenFabrics Enterprise Distribution (OFED).

This configuration file is an example of one that could be used to enable the use of SDP:

# Use SDP when binding to 192.0.2.1
bind 192.0.2.1 *

# Use SDP when connecting to all application services on 192.0.2.*
connect 192.0.2.0/24 1024-*

# Use SDP when connecting to the HTTP server or a database on myserver.org
connect myserver.org 8080
connect myserver.org 1521

The following excerpt is taken from the terminal. It is the execution of a Java application named SDPExample, specifying the SDP system property:

% java -Dcom.sun.sdp.conf=sdp.conf -Djava.net.preferIPv4Stack=true SDPExample

How It Works

Sometimes it is essential that an application be as fast as possible while performing network communications. Transfers over TCP can sometimes decrease performance, so bypassing TCP can be beneficial. Since the release of Java SE 7, support for the SDP has been included for certain platforms. The SDP supports stream connections over InfiniBand fabric. Both Solaris and Linux include support for InfiniBand, so SDP can be useful on those platforms.

You don’t need to make any programmatic changes to your applications in order to support SDP. The only differences when using SDP are that you must create an SDP configuration file, and the JVM must be told to use the protocol by passing a flag when running the application. Because the implementation is transparent, applications can be written for any platform, and those that support SDP can merely include the configuration file and bypass TCP.

The SDP configuration file is a text file that is composed of bind and connect rules. A bind rule indicates that the SDP protocol transport should be used when a TCP socket binds to an address and port that match the given rule. A connect rule indicates that the SDP protocol transport should be used when an unbound TCP socket attempts to connect to an address and port that match the given rule. The rule begins with either the bind or connect keyword indicating the rule type, followed by the hostname or IP address, and a single port number or range of port numbers. Per the online documentation, a rule has the following form:

("bind"|"connect")1*LWSP-char(hostname|ipaddress)["/"prefix])1*LWSP-char("*"|port)Image
["-"("*"|port)]

In the rule format shown here, 1*LWSP-char means that any number of tabs or spaces can separate the tokens. Anything contained within square brackets indicates optional text, and quotes indicate literal text. In the solution to the recipe, the first rule indicates that SDP can be used for any port (* indicates a wildcard) on the IP address of 192.0.2.1, a local address. Each local address that is assigned to an InfiniBand adaptor should be specified with a bind rule in the configuration file. The first connect rule in the configuration file specifies that SDP should be used whenever connecting to the IP address of 192.0.2.*, using a port of 1024 or greater.

connect 192.0.2.0/24 1024-*

This rule uses some special syntax that should be noted. Specifically, the /24 suffix of the IP address indicates that the first 24 bits of the 32-bit IP address should match a specified address. Because each portion of an IP address is eight bits, this means that the 192.0.2 should match exactly, and the final byte can be any value. The dash -* within the port identifier specifies the range of 1024 or greater because the wildcard character is used. The third and fourth connect rules in the configuration file specify that SDP should be used with the hostname ofmyserver.org and a port of 8080 or 1521.

Next, in order to enable SDP, the –Dcom.sun.sdp.conf property should be specified along with the location to the SDP configuration file when starting the application. Also, notice in the solution that the property -Djava.net.preferIPv4Stack is set to true. This indicates that the IPv4 address format will be used. This is necessary because IPv4 addresses mapped to IPv6 are currently not available in the Solaris OS or under Linux.

Although the SDP is available only with Solaris or Linux, it is a nice addition to the JDK for users of those platforms. Any performance booster is always viewed as a bonus, and the solution to this recipe certainly falls into that category.

21-4. Broadcasting to a Group of Recipients

Problem

You want to broadcast datagrams to zero or more hosts identified by a single address.

Solution

Make use of datagram multicasting using the DatagramChannel class. The DatagramChannel class enables more than one client to connect to a group and listen for datagrams that have been broadcasted from a server. The following sets of code demonstrate this technique using a client/server approach. The class demonstrates a multicast client.

package org.java8recipes.chapter21.recipe21_4;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.MembershipKey;

public class MulticastClient {

public MulticastClient() {
}

public static void main(String[] args) {
try {
// Obtain Supported network Interface
NetworkInterface networkInterface = null;
java.util.Enumeration<NetworkInterface> enumNI = NetworkInterface.getNetworkInterfaces();
java.util.Enumeration<InetAddress> enumIA;
NetworkInterface ni;
InetAddress ia;
ILOOP:
while (enumNI.hasMoreElements()) {
ni = enumNI.nextElement();
enumIA = ni.getInetAddresses();
while (enumIA.hasMoreElements()) {
ia = enumIA.nextElement();
if (ni.isUp() && ni.supportsMulticast()
&& !ni.isVirtual() && !ni.isLoopback()
&& !ia.isSiteLocalAddress()) {
networkInterface = ni;
break ILOOP;
}
}
}

// Address within range
int port = 5239;
InetAddress group = InetAddress.getByName("226.18.84.25");

final DatagramChannel client = DatagramChannel.open(StandardProtocolFamily.INET);

client.setOption(StandardSocketOptions.SO_REUSEADDR, true);
client.bind(new InetSocketAddress(port));
client.setOption(StandardSocketOptions.IP_MULTICAST_IF, networkInterface);

System.out.println("Joining group: " + group + " with network interface " +
networkInterface);
// Multicasting join
MembershipKey key = client.join(group, networkInterface);
client.open();

// receive message as a client
final ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
buffer.clear();
System.out.println("Waiting to receive message");
// Configure client to be passive and non.blocking
// client.configureBlocking(false);
client.receive(buffer);
System.out.println("Client Received Message:");
buffer.flip();
byte[] arr = new byte[buffer.remaining()];
buffer.get(arr, 0, arr.length);

System.out.println(new String(arr));
System.out.println("Disconnecting...performing a single test pass only");
client.disconnect();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

Next, a server class can be used to broadcast datagrams to the address that multicast clients are connected to. The following code demonstrates a multicast server:

package org.java8recipes.chapter21.recipe21_4;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

public class MulticastServer extends Thread {

protected ByteBuffer message = null;

public MulticastServer() {
}

public static void main(String[] args) {

MulticastServer server = new MulticastServer();
server.start();

}

@Override
public void run() {

try {

// send the response to the client at "address" and "port"
InetAddress address = InetAddress.getByName("226.18.84.25");
int port = 5239;

DatagramChannel server = DatagramChannel.open().bind(null);
System.out.println("Sending datagram packet to group " + address + " on port " + port);
message = ByteBuffer.wrap("Hello to all listeners".getBytes());
server.send(message, new InetSocketAddress(address, port));

server.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}

The server can broadcast a message to each client that is a member of the group. The client should be initiated first, followed by the server. Once the server is started, it will broadcast the message, and the client will receive it.

How It Works

Multicasting is the ability to broadcast a message to a group of listeners in a single transmission. A good analogy of multicasting is radio. Thousands of people can tune into a single broadcast event and listen to the same message. Computers can do similar things when sending messages to listeners. A group of client machines can tune into the same address and port number to receive a message that a server broadcasts to that address and port. The Java language provides multicasting functionality via datagram messaging. Datagrams are independent, nonguaranteed messages that can be delivered over the network to clients. (Being nonguaranteed means that the arrival, arrival time, and content are not predictable.) Unlike messages sent over TCP, sending a datagram is a nonblocking event, and the sender is not notified of the receipt of the message. Datagrams are sent using the User Datagram Protocol (UDP) rather than TCP. The ability to send multicast messages via UDP is one benefit over TCP, as long as the ordering, reliability, and data integrity of the message are not mission-critical.

Java facilitates multicast messaging via the MulticastChannel interface. Classes that implement the MulticastChannel interface have multicasting enabled and can therefore broadcast to groups and receive group broadcasts. One such class is the DatagramChannel, which is a selectable channel for datagram-oriented sockets. In the solution to this recipe, both a client and a server program are used to communicate via multicast messaging, and the DatagramChannel class is used on both sides of the communication. A DatagramChannel must be configured in a specific way if it is to be used for accepting multicast messages. Specifically, there are options that need to be set on the DatagramChannel client that is opened. We will discuss those options shortly. The following steps are required for creating a client that receives multicast messages.

1. Open a DatagramChannel.

2. Set the DatagramChannel options that are required to multicast.

3. Join the client to a multicast group and return a MembershipKey object.

4. Open the client.

In the solution to this recipe, the client application begins by obtaining a reference to the network interface that will be used for receiving the broadcast messages. Setting up a NetworkInterface is required for multicasting. Next, a port number is chosen, as well as a multicasting IP address. The group or registered listeners will use the IP address in order to listen for broadcasts. The port number must not be in use or an exception will be thrown. For IPv4 multicasting, the IP address must range from 224.0.0.0 to 239.255.255.255, inclusive. This port and IP address is the same one used by a server to broadcast the message. Next, a new DatagramChannel is opened using StandardProtocolFamily.INET. The choices for opening a DatagramChannel are StandardProtocolFamily.INET or StandardProtocolFamily.INET6, corresponding to IPv4 and IPv6, respectively. The first option that is set on the DatagramChannel is StandardSocketOptions.SO_REUSEADDR, and it is set to true. This indicates that multiple clients will be able to “reuse” the address or use it at the same time. This needs to be set for a multicast to occur. The client is then bound to the port using a new InetSocketAddress instance. Last, the StandardSocketOptions.IP_MULTICAST_IF option is set to the network interface that is used. This option represents the outgoing interface for multicast datagrams sent by the datagram-oriented socket.

client.setOption(StandardSocketOptions.SO_REUSEADDR, true);
client.bind(new InetSocketAddress(port));
client.setOption(StandardSocketOptions.IP_MULTICAST_IF, networkInterface);

Once these options have been set and the port has been bound to the DatagramChannel, it is ready to join the group of listeners. This can be done by calling the DatagramChanneljoin(InetAddress, NetworkInterface) method, passing the group address and network interface that will be used by the client. As a result, a java.nio.channels.MembershipKey object is produced, which is a token that represents the membership of an IP multicast group. Last, the DatagramChannelopen() method is called, which opens the channel to listen for broadcasts. At this point, the client is ready to receive multicast messages and it waits for a message to be received.

MembershipKey key = client.join(group, networkInterface);
client.open();

The next lines of code in the client take care of receiving messages from the server. In order to receive a broadcasted message, a ByteBuffer is created and then eventually passed to the DatagramChannel’s receive() method. Once the receive() method is called, the client will pause until a message is received. You can disable this feature by calling the DatagramChannel configureBlocking(boolean) method and passing a false value. Next, the ByteBuffer is converted to a string value and printed out by repositioning the buffer index at 0 using the flip() method, and then pulling the text starting at index 0 to the last index into a byte[]. Finally, be sure to disconnect the client when you’re finished. That wraps up the client code portion.

// Configure client to be passive and non.blocking
// client.configureBlocking(false);
client.receive(buffer);
// client pauses until a message is received... in this case
System.out.println("Client Received Message:");
buffer.flip();
byte[] arr = new byte[buffer.remaining()];
buffer.get(arr, 0, arr.length);

System.out.println(new String(arr));
System.out.println("Disconnecting...performing a single test pass only");
client.disconnect();

Image Note In the example to this recipe, a single pass is performed, and the client is then disconnected. For extended listening, you would need a loop with a timeout and tests for an ending state.

The server code is fairly basic. You can see that the MulticastServer class extends Thread. This means that this server application could run in a thread separate from other code within an application. If there were another class that initiated the MulticastServer class’ run()method, it would run in a thread separate from the class that initiated it. The run() method must exist in any class that extends Thread. For more information regarding threading and concurrency, refer to Chapter 10.

The bulk of the server code resides in the run() method. A new InetAddress object is created using the same IP address that the client registered with in order to join the multicast group. The same port number is also declared in the server code, and these two objects will be used later in the code block to send the message. A new DatagramChannel is opened and bound to null. The null value is important because by setting the SocketAddress equal to null, the socket will be bound to an address that is assigned automatically. Next, a ByteBuffer is created that contains a message that will be broadcast to any listeners. The message is then sent using the DatagramChannel’s send(ByteBuffer, InetSocketAddress) method. The send() method in the solution accepts the message as a ByteBuffer object, as well as a new InetSocketAddress that is created by using the address and port, which was declared at the beginning of the block. Told you we’d get back to those!

server.send(message, new InetSocketAddress(address, port));

At this point, the client would receive the message that was sent by the server. As for the client that is demonstrated in the solution to this recipe, it would then disconnect. Normally in a real-world scenario, a different class would most likely initiate the server, and its run() method would contain a loop that would continue to execute until all messages have been broadcast or the loop was told to stop. The client would probably not disconnect until after a user initiated a shutdown.

21-5. Generating and Reading from URLs

Problem

You want to generate URLs programmatically in your application. Once the URLs have been created, you’d like to read data from them for use in your application.

Solution

Make use of the java.net.URL class in order to create an URL. There are a few different ways to generate an URL depending on the address you are attempting to work with. This solution demonstrates a few different ways to create URL objects, along with comments indicating the differences. Once the URL objects have been created, one of the URLs is read into a BufferedReader and printed to the command line.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;

public class GenerateAndReadUrl {

public static void main(String[] args) {
try {
// Generate absolute URL
URL url1 = new URL("http://www.java.net");
System.out.println(url1.toString());
// Generate URL for pages with a common base
URL url2 = new URL(url1, "search/node/jdk8");

// Generate URL from different pieces of data
URL url3 = new URL("http", "java.net", "search/node/jdk8");

readFromUrl(url1);

} catch (MalformedURLException ex) {
ex.printStackTrace();
}
}

/**
* Open URL stream as an input stream and print contents to command line.
*
* @param url
*/
public static void readFromUrl(URL url) {
try {
BufferedReader in = new BufferedReader(
new InputStreamReader(
url.openStream()));

String inputLine;

while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
}

in.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

Running this program will result in the HTML from the URL resource identified as url1 being printed to the command line.

How It Works

Creating URLs in Java code is fairly straightforward thanks to the java.net.URL class, which does all of the heavy lifting. An URL is a character string that points to a resource on the Internet. Sometimes it is useful to create URLs in Java code so that you can read content from, or push content to, the Internet resource that the URL is pointing to. In the solution to this recipe, a few different URL objects are created, demonstrating the different constructors that are available for use.

The easiest route to use for creating an URL is to pass the standard readable URL string for a resource that is located on the Internet to the java.net.URL class to create a new instance of the URL. In the solution, an absolute URL is passed to the constructor to create the url1 object.

URL url1 = new URL("http://www.java.net");

Another useful way to create an URL is to pass two arguments to the URL constructor and create a relative URL. It is useful to base relative URLs on the location of another URL. For instance, if a particular site has a number of different pages, you could create an URL pointing to one of the subpages relative to the URL of the main site. Such is the case with the url2 object in the solution to this recipe.

URL url2 = new URL(url1, "search/node/jdk8");

As you can see, the path search/node/jdk8 is relative to the URL that is known as url1. In the end, the human-readable format of the url2 object is represented as http://www.java.net/search/node/jdk8. There are a couple more constructors for creating URL objects that take more than two arguments. Those constructors are as follows:

new URL (String protocol, String host, String port, String path);
new URL (String protocol, String host, String path);

In the solution, the second of the two constructors shown here is demonstrated. The protocol, hostname, and path of the resource are passed to the constructor to create the url3 object. These last two constructors are usually most useful when you’re dynamically generating an URL.

21-6. Parsing a URL

Problem

You want to programmatically gather information from an URL in your application.

Solution

Parse the URL using the built-in URL class methods. In the following example class named ParseUrl, an URL object is created and then parsed using the built-in URL class methods to gather information regarding the URL. After the information has been retrieved from the URL, it is printed to the command line and then used to create another URL.

import java.net.MalformedURLException;
import java.net.URL;

public static void main(String[] args) {
URL url1 = null;
URL url2 = null;
try {
// Generate absolute URL
url1 = new URL("http://www.apress.com/catalogsearch/result/?q=juneau");

String host = url1.getHost();
String path = url1.getPath();
String query = url1.getQuery();
String protocol = url1.getProtocol();
String authority = url1.getAuthority();
String ref = url1.getRef();

System.out.println("The URL " + url1.toString() + " parses to the following:\n");
System.out.println("Host: " + host + "\n");
System.out.println("Path: " + path + "\n");
System.out.println("Query: " + query + "\n");
System.out.println("Protocol: " + protocol + "\n");
System.out.println("Authority: " + authority + "\n");
System.out.println("Reference: " + ref + "\n");

url2 = new URL(protocol + "://" + host + path + "?q=java");

} catch (IOException ex) {
ex.printStackTrace();

}
}

When this code is executed, the following lines will be displayed:

The URL http://www.apress.com/catalogsearch/result/?q=juneau parses to the following:

Host: www.apress.com

Path: /catalogsearch/result/

Query: q=juneau

Protocol: http

Authority: www.apress.com

Reference: null

How It Works

When constructing and working with URLs in an application, it is sometimes beneficial to extract information pertaining to an URL. This can be easily done using the URL built-in class methods, which can call a given URL and return strings of information. Table 21-1 explains the accessor methods available in the URL class for obtaining information.

Table 21-1. Accessor Methods for Querying URLs

Method

URL Information Returned

getAuthority()

Authority component

getFile()

File name component

getHost()

Hostname component

getPath()

Path component

getProtocol()

Protocol identifier component

getRef()

Reference component

getQuery()

Query component

Each of these accessor methods returns a string value that can be used for informational purposes or for constructing other URLs dynamically, as was done in the example. If you take a look at the results from the solution to this recipe, you can see the information that was obtained regarding the URL via the accessor methods listed in Table 21-1. Most of the accessors are self-explanatory. However, a couple of them could use further explanation. The getFile() method returns the file name of the URL. The file name is the same as the result of concatenating the value returned from getPath() with the value returned from getQuery(). The getRef() method may not be very straightforward. The reference component that is returned by calling the getRef() method refers to the “fragment” that may be appended to the end of an URL. For instance, a fragment is indicated using the pound character (#), followed by a string that usually corresponds to a subsection on a particular web page. Given the URL such as the following, recipe21_6 would be returned using the getRef() method.

http://www.java8recipes.org/chapters/chapter21#recipe21_6

Although it’s not always needed, the ability to parse an URL to obtain information can come in very handy at times. Because the Java language has helper methods built into the java.net.URL class, it makes gathering information pertaining to URLs a piece of cake.

Summary

This chapter covered a few basic networking features of the Java language. In the recent releases, there have been some nice new features added, such as the Socket Direct Protocol. However, much of the java.net package has been unchanged for years, and it is robust and easy to use. This chapter delved into using socket connections and URLs and broadcasting messages via DatagramChannel. Chapter 22 contains a recipe that covers another new feature of the java.net package, which is the new URLPermission class.