0

I've attempted to create a basic HTTP/1.1 compliant web server which supports simple GET requests with persistent connections. I'm getting a SocketException: Connection Reset error occuring at line 61 (if (line==null || line.equals("")). I'm testing it by running it and then directing my chrome browser to localhost portnumber. When I test it with a page with multiple images it seems like only 1 request is being processed before the exception occurs, but I'm not sure what's wrong as this is my first attempt at any kind of socket programming.

Here's my updated Code after removing DataOutputStream:

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.StringTokenizer;

public class webserve
{
    public static void main(String[] args) throws Exception
    {
        String rootPath = "~/Documents/MockWebServerDocument/";
        int port = 10000;

        if(rootPath.startsWith("~" + File.separator))
        {
            rootPath = System.getProperty("user.home") + rootPath.substring(1);
        }

        String requestLine="";
        StringTokenizer tokens=null;
        String line, command;
        Date date = new Date();
        String connectionStatus="";


        //Create new server socket listening on specified port number
        ServerSocket serverSocket = new ServerSocket(port);

        while(true)
        {
            //Wait for a client to connect and make a request
            Socket connectionSocket = serverSocket.accept();
            System.out.println("Socket opened");

            //Input stream from client socket
            BufferedReader incomingFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
            //PrintWriter to send header to client socket
            PrintWriter outgoingHeader = new PrintWriter(connectionSocket.getOutputStream(),true);
            //OutputStream to send file data to client socket
            ObjectOutputStream outgoingFile = new ObjectOutputStream(connectionSocket.getOutputStream());
            //Date format for HTTP Header
            SimpleDateFormat HTTPDateFormat = new SimpleDateFormat("EEE MMM d hh:mm:ss zzz yyyy");


            //Create a HashMap to store the request header information
            HashMap<String,String> requestHeader = new HashMap<String,String>();

            while(connectionSocket.isConnected())
            {
                //requestHeader.clear();

                while((line = incomingFromClient.readLine()) != null)
                {
                    if(line.isEmpty())
                    {   
                        break;
                    }
                    //If this is the first line of the request, i.e doesnt contain a colon
                    if(!(line.contains(":")))
                    {
                        requestLine = line;
                        requestHeader.put("Request", requestLine);
                    }
                    else
                    {
                        //Otherwise, find the colon in the line and create a key/value pair for the HashMap
                        int index = line.indexOf(':')+2;
                        String header = line.substring(0,index-1);
                        line = line.substring(index).trim();

                        requestHeader.put(header, line);

                        System.out.println(header + " " + line);
                    }
                }   

                connectionStatus = (String)requestHeader.get("Connection:");
                requestLine = (String)requestHeader.get("Request");

                System.out.println("RequestLine: " + requestLine);

                if(!requestLine.equals("")||!(requestLine.equals(null)))
                {
                    tokens = new StringTokenizer(requestLine);

                    command = tokens.nextToken();
                    String filename = tokens.nextToken();
                    filename = cleanUpFilename(filename);
                    String fullFilepath = rootPath + filename;
                    System.out.println("Full FilePath: " + fullFilepath);

                    File file = new File(fullFilepath);

                    //Get the number of bytes in the file
                    int numOfBytes=(int)file.length();

                    //Open a file input stream using the full file pathname
                    FileInputStream inFile = new FileInputStream(fullFilepath);

                    //Create byte array to hold file contents
                    byte[] fileInBytes = new byte[numOfBytes];

                    inFile.read(fileInBytes,0,numOfBytes);

                    inFile.close();


                    //Write the header to the output stream 
                    outgoingHeader.print("HTTP/1.1 200 OK\r\n");
                    outgoingHeader.print("Date: " + HTTPDateFormat.format(date)+"\r\n");
                    outgoingHeader.print("Server: BC-Server\r\n");
                    outgoingHeader.print("Last-Modified: " + HTTPDateFormat.format(file.lastModified())+"\r\n");
                    outgoingHeader.print("Connection: keep-alive\r\n");
                    outgoingHeader.print("Content-Length: " + numOfBytes);
                    outgoingHeader.print("\r\n\r\n");               

                    //When the header has been printed, write the byte array containing the file
                    //to the output stream
                    outgoingFile.writeObject(fileInBytes);

                    if(!(connectionStatus.equals("keep-alive")))
                    {
                        System.out.println("Closing: " + connectionStatus);
                        outgoingHeader.close();
                        outgoingFile.close();
                        break;
                    }
                    else
                        continue;

                }       

            }

        }
    }

    public static String cleanUpFilename(String filename)
    {
        //If there is a "/" at the start of the filename, then remove it
        if(filename.charAt(0) == '/')
        {
            filename = filename.substring(1);
        }

        //If we are given an absolute URI request, strip all characters
        //before the third "/"
        if(filename.startsWith("http://"));
        {
            try
            {
                URI httpAddress = new URI(filename);

                //Get the path from the supplied absolute URI, that is remove
                //all character before the third "/"
                filename = httpAddress.getPath();

                //Again, we may have to trim this modified address if there is an
                //extra "/" at the start of the filename
                if(filename.charAt(0) == '/')
                {
                    filename = filename.substring(1);
                }
            }
            catch (URISyntaxException e)
            {                   
                e.printStackTrace();
            }
        }

        return filename;
    }

}

Here's my error trace:

Exception in thread "main" java.net.SocketException: Connection reset
    at java.net.SocketInputStream.read(SocketInputStream.java:185)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:282)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:324)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:176)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:153)
    at java.io.BufferedReader.readLine(BufferedReader.java:316)
    at java.io.BufferedReader.readLine(BufferedReader.java:379)
    at webserve.main(webserve.java:61)

Any help would be much appreciated, as I'm at a total loss.

JCutz
  • 3
  • 1
  • 4

4 Answers4

0

Try testing the connection using telnet, wget or curl instead of chrome, because you can then be in control of both sided of the TCP/IP connection.

I think that your web-client is closing the connection from it's side, and you try to read from that socket again (yes, even isConnected() will throw this error when the remote party closed the connection). I am also sorry to say that there is no easy way to combat this other than to catch the exception and handle it gracefully.

This is a problem that often happens with synchronous sockets. Try using java.nio channels and selectors instead.

RudolphEst
  • 1,240
  • 13
  • 21
  • I've tried running in telnet, and it works smoothly as long as I pass the "Connection: keep-alive" header with the GET request, and closes correctly if I pass "Connection: close". – JCutz Feb 26 '13 at 23:12
  • When using telnet, what happens when you pass the keep-alive and close the telnet session after the result? In other words, how does your server handle open connections that are closed by the client after the first initial response? – RudolphEst Feb 26 '13 at 23:43
  • I just tried this, and it produced a SocketException:Broken Pipe exception at the point where I attempt to write the file to the client. Any idea why it is doing this? – JCutz Feb 27 '13 at 00:45
  • This is a problem that happens with *all* sockets. Using NIO won't change it in the slightest. – user207421 Feb 27 '13 at 00:57
  • @JClutz as I said before, your server is waiting for a second HTTP request (because the connection is still open) using a blocking read() from the socket, but the client disconnects instead of making that request. You will have to catch the exception in a try-catch-finally to allow for graceful handling of the disconnect. – RudolphEst Feb 27 '13 at 01:01
  • This [link](http://stackoverflow.com/questions/1181255/java-nio-what-does-ioexception-broken-pipe-mean) is a well worded, related answer. – RudolphEst Feb 27 '13 at 01:03
  • [This link](http://stackoverflow.com/a/4232540/207421) is the well-worded answer :-| Some of the others are pretty vague. – user207421 Feb 27 '13 at 01:08
0

Using multiple output streams at the same time is highly problematic. In this case you shouldn't create the ObjectOutputStream until you are certain you are going to write an object and you have already written and flushed the headers, because ObjectOutputStream writes a header to the output, which in your present code will appear before any headers, probably causing the client to barf.

In general, SocketException: Connection Reset usually means that you have written to a connection that has already been closed by the peer. As in this case the peer is the client and the client is a Web browser, it can mean anything at all, e.g. the user stopped loading the page, he browsed away, exited the browser, closed the tab. It's not your problem. Just close the socket and forget about it.

For the same reason, your server should also set a reasonable read timeout, like say 10-30 seconds, and bail out if it fires.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Ok, so would is there any way which you would recommend to allow me to write the headers and send the file bytes. Should I use separate output streams, but only open the ObjectOutputStream when I am about to write the object? – JCutz Feb 27 '13 at 01:15
  • That's what I said, but if your client is a browser you won't be sending objects to it so you won't need an `ObjectOutputStream` at all. – user207421 Feb 27 '13 at 02:09
0

The most obvious problem of your server is that it's not multi-threaded. After re-reading your description of the problem, that seems to the be root cause. You need one thread per connection. After serverSocket.accept(), create a new thread to handle the connectionSocket.

    while(true)
    {
        //Wait for a client to connect and make a request
        Socket connectionSocket = serverSocket.accept();

        new Thread()
        {
            public void run()
            {
                //Input stream from client socket
                BufferedReader incomingFromClient = ...

                etc

            }
        }.start();
irreputable
  • 44,725
  • 9
  • 65
  • 93
-1

You cannot use DataOutputStream, it's for Java-Java communication. Try Writer for writing headers, and original OutputStream for writing file content.

What's happening is that the browser sees invalid response, and closes the connection. The serve is still writing to the client, which responds RST because the connection is gone.

irreputable
  • 44,725
  • 9
  • 65
  • 93
  • Thanks for the information. I've edited my original code above. This seems to have removed my original exception, but is causing the browser to simply hang. When I stop the server's execution, the html shell of the webpage I'm testing appears as a result. – JCutz Feb 27 '13 at 00:50
  • you cannot use `ObjectOutputStream` either. write file content directly to `connectionSocket.getOutputStream()` – irreputable Feb 27 '13 at 00:54
  • @JCutz This answer is not correct. You *can* use `DataOutputStream,` as long as either you don't use anything but the `write()` and `writeBytes()` methods *or* the peer understands the same binary protocol in network byte order. The only method that is Java-specific is `writeUTF()`. There was nothing in your original code that would cause the peer to barf. – user207421 Feb 27 '13 at 01:03
  • EJP is probably correct. nevertheless, do not use DataOutputStream for this program. – irreputable Feb 27 '13 at 01:24