Tuesday 11 March 2008

Simple POP3 client in Java (tutorial)

This article is intended for people who have no or very little experience with networking in Java.

Today, my aim is to show you how to make your own simple POP3 client in Java. Some people are scared of creating a network application in Java because they think it have to be difficult to handle such topic, but opposite is true. It’s easier than you could think as you will see later. I chose the POP3, which is used to access your mailbox on a server, because it’s quite simple protocol. It has just a few commands. We are not going to implement all of them of course, just a few.

Before creating a first class in Java, let’s see the RFC 1939 specification of the POP3 protocol that can be found at http://www.faqs.org/rfcs/rfc1939.html. It’s always important to go through a specification of a protocol you are going to implement and to understand it well. You should read paragraph “3. Basic operation” where you would find information about default port for POP3 protocol (110) and two possible responses for any POP3 command: -OK indicating success and –ERR indicating a problem on the server. By the way response from the server can be multi-line (typically when server sends an email content) or single-line (e.g. response for command “USER”).

Now, when you are familiar with basic protocol operation we can start and create a first Java class, let’s give it name ‘POP3Client’. What fields would the class need to have? Firstly an instance of Socket (java.net.Socket) class for network communication and secondly reader as well as writer for receiving and sending network data. That’s could be enough, but for debugging feature let’s add boolean debug field and POP3’s default port value field. You should have something similar to the following:

public class POP3Client {

private Socket socket;

private boolean debug = false;

private BufferedReader reader;
private BufferedWriter writer;

private static final int DEFAULT_PORT = 110;

public boolean isDebug() {
return debug;
}

public void setDebug(boolean debug) {
this.debug = debug;
}
}


For simplicity I won’t deal with exceptions and state (with exception of connection establishment) below. You should have on mind that POP3 is state protocol and you should always check for the state before trying to do anything (e.g. client can’t logout from the server while not connected). The first method would handle connecting to the server and initializing needed fields. Let’s name the method connect(…) and implement it:

public void connect(String host, int port) throws IOException {
socket = new Socket();
socket.connect(new InetSocketAddress(host, port));
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
if (debug)
System.out.println("Connected to the host");
}


The first line creates new socket, the second connects it to the specified host and address, the third and fourth sets reader and writer and the last prints debug message. Let’s create second method that would use the DEFAULT_PORT field

public void connect(String host) throws IOException {
connect(host, DEFAULT_PORT);
}


Once the socket is connected to the server, you would need to somehow check for establishment of connection before disconnecting. Let’s implement isConnected() method:

public boolean isConnected() {
return socket != null && socket.isConnected();
}


Now we can implement disconnect() method that would disconnect socket from the server:

public void disconnect() throws IOException {
if (!isConnected())
throw new IllegalStateException("Not connected to a host");
socket.close();
reader = null;
writer = null;
if (debug)
System.out.println("Disconnected from the host");
}


Implementation of the method is quite straightforward, so let’s move a bit. Now the most important part will take place. We need somehow to send commands to the server and receive response. For sending data will use “writer” and for receiving data “reader”. Sending data is simple as long as receiving. To send data to server, you just call writer’s write(…) method and to receive you just call reader’s readLine() method. Let’s create and implement method readResponseLine() that will read and return one line of data sent by the server:

protected String readResponseLine() throws IOException{
String response = reader.readLine();
if (debug) {
System.out.println("DEBUG [in] : " + response);
}
if (response.startsWith("-ERR"))
throw new RuntimeException("Server has returned an error: " + response.replaceFirst("-ERR ", ""));
return response;
}


The first line reads the line of text sent by the server, the second prints it while in debug mode and the third checks for an error according to RFC specification. When we have got the method for receiving data from server, we need the method for sending data – commands - to the server. Let’s name it sendCommand(…) and implement it:

protected String sendCommand(String command) throws IOException {
if (debug) {
System.out.println("DEBUG [out]: " + command);
}
writer.write(command + "\n");
writer.flush();
return readResponseLine();
}


The first line prints command being sent while in debug mode, the second sends it and the third flushes the socket’s buffer. It’s one of the most important things and you should never forget to do it. If you don’t flush the buffer then the data are held in it (until the buffer is full) and not sent to the server. The last line reads and returns server’s response to our command.

You should know from RFC specification that just after connecting to the server, the server sends welcome string (something like “+OK Hello, this is POP3 server v. 2.3.16.”), so you have to receive it. Let’s modify existing connect method:

public void connect(String host, int port) throws IOException {

readResponseLine();
}


The next thing we need to do is to add support for logging in and out from the server. Let’s create two methods - login(…) and logout() - and implement them:

public void login(String username, String password) throws IOException {
sendCommand("USER " + username);
sendCommand("PASS " + password);
}

public void logout() throws IOException {
sendCommand("QUIT");
}


You can find in the RFC specification that to login to the server you need to send the combination of commands USER and PASS. The USER command takes as an argument username to an email account (e.g. john.dow@myserver.com) and PASS command takes as an argument the password. To logout from the server you need to send QUIT command. Once a user is logged to the server he can start manipulating his email messages. Firstly let’s create a method that will check for number of messages on the server:

public int getNumberOfNewMessages() throws IOException {
String response = sendCommand("STAT");
String[] values = response.split(" ");
return Integer.parseInt(values[1]);
}


I chose the STAT command (there’s also LIST command) that returns number of messages and theirs overall size in bytes (response has format: “+OK nn mm”, where nn is the number of messages and mm the size of messages in bytes). Now it’s the right time to prepare class for holding a message so let’s create it:

public class Message {

private final Map<String, List<String>> headers;

private final String body;

protected Message(Map<String, List<String>> headers, String body) {
this.headers = Collections.unmodifiableMap(headers);
this.body = body;
}

public Map<String, List<String>> getHeaders() {
return headers;
}

public String getBody() {
return body;
}

}


As you can suspect, every message has a list of headers (each header has a name and one or more values) and a body. The next method we are going to implement is method that gets a message from the server. Let’s name it getMessage(..):

protected Message getMessage(int i) throws IOException {
String response = sendCommand("RETR " + i);
Map<String, List
<String>> headers = new HashMap<String, List<String>>();
String headerName = null;
// process headers
while ((response = readResponseLine()).length() != 0) {
if (response.startsWith("\t")) {
continue; //no process of multiline headers
}
int colonPosition = response.indexOf(":");
headerName = response.substring(0, colonPosition);
String headerValue;
if (headerName.length()
> colonPosition) {
headerValue = response.substring(colonPosition + 2);
} else {
headerValue = "";
}
List
<String> headerValues = headers.get(headerName);
if (headerValues == null) {
headerValues = new ArrayList
<String>();
headers.put(headerName, headerValues);
}
headerValues.add(headerValue);
}
// process body
StringBuilder bodyBuilder = new StringBuilder();
while (!(response = readResponseLine()).equals(".")) {
bodyBuilder.append(response + "\n");
}
return new Message(headers, bodyBuilder.toString());
}


The method uses RETR command to get a message from the server. After sending the command, the server starts sending a message. Firstly headers terminated by empty line (its length is 0) followed by body terminated by line containing “.” character. The first loop goes through the all headers and saves it to the map “headers”. Each header is in the following format:

headerName: headerValue

The second loop goes through the all body response lines until the termination character is found. Finally the last method we are going to implement is method that will get complete list of messages from the server. Let’s name it getMessages():

public List
<Message> getMessages() throws IOException {
int numOfMessages = getNumberOfNewMessages();
List
<Message> messageList = new ArrayList<Message>();
for (int i = 1; i
<= numOfMessages; i++) {
messageList.add(getMessage(i));
}
return messageList;
}


I hope that it’s quite straightforward after all we did.

Now it’s the right time to test our implementation, let’s create some class and use the client in it:

public static void main(String[] args) throws IOException {
POP3Client client = new POP3Client();
client.setDebug(true);
client.connect("pop3.myserver.com");
client.login("name@myserver.com", "password");
System.out.println("Number of new emails: " + client.getNumberOfNewMessages());
List
<Message> messages = client.getMessages();
for (int index = 0; index
< messages.size(); index++) {
System.out.println("--- Message num. " + index + " ---");
System.out.println(messages.get(index).getBody());
}
client.logout();
client.disconnect();
}



Because the debug mode was turned on you should see a list of POP3 commands and its responses.

I hope that this tutorial was hopeful for you and that you enjoyed it as well as I did.

You can download source codes at http://rapidshare.com/files/100278017/pop3client.zip

3 comments:

Lukáš Zapletal said...

Yeah, POP3 is nice and easy protocol. Several years ago I wrote a e-mail agent in PHP with POP3 support. In conrast IMAP is very complicated and hard to implement. There are many problems with it.

Anonymous said...

Hey, you've made a great job there! I was just wondering... In method "protected Message getMessage(int i) throws IOException { ...
".
The line "if (headerName.length() > colonPosition) {".
I guess you mean "if (response.length() > colonPosition) {".
Am I right?
Thx .

Praveen Kumar said...

Hey u did a wonderful job man. Thank u so much. It really supports me well and also for the person who are beginners for pop3.