• No results found

Getting Files Using Sockets

If, for some reason, you decide that you want to bypass the URL and URLConnection classes and speak HTTP directly over a socket, you are probably a glutton for punishment or just a genuine bit-head. Actually, the HTTP protocol is very simple, so it isn't that big a deal to open up a socket and fetch information. All you need to do is open the socket, send a GET message, and start reading.

When you read data from an HTTP server directly over a socket, you'll get all the header information first. Each line in the header is terminated by a carriage return and then a line feed (in Java, "\r\n"). The end of the header section is marked by a blank line. After that comes the data, in whatever form the server sends it. The "content-type" header tells you what type of data to expect. If you're just reading a text file, it should be "text/plain."

Listing 6.2 shows an applet that uses a socket connection to fetch a file from a Web server. Like the example in Listing 6.1, this applet fetches its own .class file.

import java.applet.*; import java.awt.*;

Chapter 6 -- Communicating with a Web Server

Listing 6.2 Source Code for FetchSockURL.java

import java.net.*; import java.io.*;

// This applet shows you how to open up a socket to an HTTP server // and read a file. The applet reads its own .class file, because // you can always be sure it exists.

public class FetchSockURL extends Applet {

byte[] appletCode; // Where to store the contents of the .class file public void init()

{

try {

// If the port number returned for the code base is -1, use the // default http port of 80.

int port = getCodeBase().getPort(); if (port < 0) port = 80;

// Open up a socket to the Web server where this applet came from

Socket sock = new Socket(getCodeBase().getHost(),port); // Get input and output streams for the socket connection

DataInputStream inStream = new DataInputStream( sock.getInputStream());

DataOutputStream outStream = new DataOutputStream( sock.getOutputStream());

// Send the GET request to the server

// The request is of the form: GET filename HTTP/1.0

// In this case, the filename will be the applet's filename as returned // by the getCodeBase method. Notice that you send two \r\n's

// The first one terminates the request line, the second indicates the // end of the request header.

outStream.writeBytes("GET "+

getCodeBase().getFile()+getClass().getName()+ ".class HTTP/1.0\r\n\r\n");

// Just to show you how it's done, look through the headers for // the content length. First, assume it's -1.

int length = -1; String currLine;

Chapter 6 -- Communicating with a Web Server

while ((currLine = inStream.readLine()) != null) {

// if the length of the line is 0, you just hit the end of the header if (currLine.length() == 0) break;

// See if it's the content-length header

if (currLine.toLowerCase().startsWith( "content-length:")) {

// "content-length:" is 15 characters long, so parse the length starting at // offset 15 (the 16th character). Catch any exceptions when parsing

// this number - it's not so important that you have to quit. try {

length = Integer.valueOf( currLine.substring(15)). intValue();

} catch (Exception ignoreMe) { }

} }

// Because you can't be sure of the size of the .class file, use a

// ByteArrayOutputStream as a temporary container. Once you are finished // reading, you can convert it to a byte array.

ByteArrayOutputStream tempBuffer;

// If you don't know the length of the .class file, use the default size if (length < 0) {

tempBuffer = new ByteArrayOutputStream(); } else {

tempBuffer = new ByteArrayOutputStream(length); }

// Read the contents of the URL and copy it to the temporary buffer int ch;

while ((ch = inStream.read()) >= 0) { tempBuffer.write(ch);

}

// Convert the temp buffer to a byte array (you don't do anything with // the array in this applet other than take its size.

appletCode = tempBuffer.toByteArray(); } catch (Exception e) {

e.printStackTrace(); }

}

public void paint(Graphics g) {

g.setColor(Color.black);

Chapter 6 -- Communicating with a Web Server

if (appletCode == null) {

g.drawString("I was unable to read my .class file", 10, 30);

} else {

g.drawString("This applet's .class file is "+ appletCode.length+" bytes long.", 10, 30); }

} }

Like the FetchURL applet, the FetchSockURL applet reads its own .class file from the Web server. FetchSockURL doesn't use the built-in URL class, however. Instead, it creates a socket connection to the Web server. Once this connection is made, the applet sends a GET request to the Web server to retrieve the .class file. The GET request usually looks something like this:

GET /classes/FetchSockURL.class HTTP/1.0

This line is followed by a blank line, indicating the end of the HTTP headers. You can send your own headers immediately after the GET request if you like. Just make sure they appear before the blank line. The FetchSockURL applet actually writes out the blank line in the same statement where it writes out the GET request, so you'll need to remove the \r\n from the end of the writeBytes statement if you add your own headers. If you do that, don't forget to write out a blank line after your headers.

Once the GET request has been sent to the server, the applet begins reading lines from the socket connection. The server will send a number of header lines, terminated by a blank line. This will be followed by the actual content of the page.

The FetchSockURL applet scans through the headers looking for the content length header field, which usually looks like this:

Content-length: 1234

Like the FetchURL applet, the FetchSockURL applet can handle situations where the content length is unknown. It uses the same technique of writing the data to a byte array output stream as it reads it. You can tell when you have reached the end of the content because you'll hit the end of file on the socket (the read method will return -1).