title: Multithreaded Java Web Server - java.net.SocketTimeoutException
body: <p>I've implemented a simple HTTP/1.1 compliant multithreaded web server which handles GET and HEAD requests. When I make a request through the web server, although it works, I'm getting a SocketTimeoutException after the 12 second timeout I set.</p>
  
  <p>I'm testing my webserver by running it in Eclipse and directing the browser to localhost:portnumber and then trying to open files locally. I only have the timeout value because if I don't have it, any request to read a file that does not exist simply does not return, whereas it should return a 404 Not Found error.</p>
  
  <p>The number of SocketTimeoutExceptions I receive is equal to the number of sockets that were opened to handle the request. I suspect that I should be handling this exception somehow, but I'm not sure where or how to do it. Any example or explanation of how to handle this would be very useful.</p>
  
  <p>My code is split into a short webserver component followed with a separate ThreadHandler class to handle the requests. When I create a new client socket, I use a new thread to handle the request. I can provide the ThreadHandler class if necessary, but it is much, much longer.</p>
  
  <p>Here is the WebServer component:</p>
  
  <pre><code>public class MyWebServer
  {
      public static void main(String[] args) throws Exception
      {
  
          int port = 5000;
          String rootpath = "~/Documents/MockWebServerDocument/";
  
          if(rootpath.startsWith("~" + File.separator))
          {
              rootpath = System.getProperty("user.home") + rootpath.substring(1);
          }
  
          File testFile = new File(rootpath);
  
          //If the provided rootpath doesn't exist, or isn't a directory, exit
          if(!testFile.exists() || !testFile.isDirectory())
          {
              System.out.println("The provided rootpath either does not exist, or is not a directory. Exiting!");
              System.exit(1);
          }
  
          //Create the server socket
          ServerSocket serverSocket = new ServerSocket(port);
  
          //We want to process requests indefinitely, listen for connections inside an infinite loop
          while(true)
          {
              //The server socket waits for a client to connect and make a request. The timeout ensures that
              //a read request does not block for more than the allotted period of time. 
              Socket connectionSocket = serverSocket.accept();
              connectionSocket.setSoTimeout(12*1000);
  
              //When a connection is received, we want to create a new HandleRequest object
              //We pass the newly created socket as an argument to HandleRequest's constructor
              HandleThreads request = new HandleThreads(connectionSocket, rootpath);
  
              //Create thread for the request
              Thread requestThread = new Thread(request);
  
              System.out.println("Starting New Thread");
  
              //Start thread
              requestThread.start();
          }
      }
  
  }
  </code></pre>
  
  <p>Within the ThreadHandler class I read the request from the socket, parse the request and respond with appropriate response via the socket. I've implemented persistent connections, so that each socket is only closed if the request includes the "Connection: close" token within the request. However, I'm not sure if this is occurring properly, especially in the case where I try to open a file that doesn't exist and should return a 404 Not Found Error.</p>
  
  <p>Does anyone have any ideas how to handle these exceptions. Should I be doing something to close the threads?</p>
  
  <p>Any help would be much appreciated.</p>
  
  <p>EDIT: This is the handleRequest() which I call from within a try catch statement in run()</p>
  
  <pre><code>//This method handles any requests received through the client socket
      private void handleRequest() throws Exception
      {
          //Create outputStream to send data to client socket
          DataOutputStream outToClient = new DataOutputStream(clientsocket.getOutputStream());
          //Create BufferedReader to read data in from client socket
          BufferedReader inFromClient = new BufferedReader(new InputStreamReader(clientsocket.getInputStream()));
          //Create SimpleDateFormat object to match date format expected by HTTP
          SimpleDateFormat HTTPDateFormat = new SimpleDateFormat("EEE MMM d hh:mm:ss zzz yyyy");
  
          //Keep running while the socket is open
          while(clientsocket.isConnected())
          {
  
                  String line = null;
                  //HashMap to record relevant header lines as they are read
                  HashMap&lt;String,String&gt; requestLines = new HashMap&lt;String,String&gt;();
                  String ifModSince = null;
                  Date ifModifiedSince = null;
                  Date lastModifiedDate = null;
  
                  //Keep reading the request lines until the end of the request is signalled by a blank line
                  while ((line = inFromClient.readLine()).length() != 0) 
                  {
                      //To capture the request line
                      if(!line.contains(":"))
                      {
                          requestLines.put("Request", line);
                      }
  
                      //To capture the connection status
                      if(line.startsWith("Connection:"))
                      {
                          int index = line.indexOf(':');
                          String connectionStatus = line.substring(index + 2);
                          requestLines.put("Connection", connectionStatus);
                      }
  
                      //To capture the if-modified-since date, if present in the request
                      if(line.startsWith("If-Modified-Since"))
                      {
                          int index = line.indexOf(':');
                          ifModSince = line.substring(index + 2);
                          requestLines.put("If-Modified-Since", ifModSince);
                      }
  
                      System.out.println(line);
  
                  }
  
                  //Get the request line from the HashMap
                  String requestLine = (String)requestLines.get("Request");
                  //Create Stringtokenizer to help separate components of the request line
                  StringTokenizer tokens = new StringTokenizer(requestLine);
  
                  //If there are not 3 distinct components in the request line, then the request does
                  //not follow expected syntax and we should return a 400 Bad Request error
                  if(tokens.countTokens() != 3)
                  {
                      outToClient.writeBytes("HTTP/1.1 400 Bad Request"+CRLF);
                      outToClient.writeBytes("Content-Type: text/html"+CRLF);
                      outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                      outToClient.writeBytes("Connection: keep-alive"+CRLF);
                      outToClient.writeBytes(CRLF);
                      outToClient.writeBytes("&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;Error 400 - Bad Request&lt;/body&gt;&lt;/html&gt;"+CRLF);
  
                      outToClient.flush();
                  }
                  else
                  {
                      //Get the specific request, whether "GET", "HEAD" or unknown
                      String command = tokens.nextToken();
                      //Get the filename from the request
                      String filename = tokens.nextToken();
  
                      //Tidy up the recovered filename. This method can also tidy up absolute
                      //URI requests
                      filename = cleanUpFilename(filename);
  
                      //If the third token does not equal HTTP/1.1, then the request does 
                      //not follow expected syntax and we should return a 404 Bad Request Error
                      if(!(tokens.nextElement().equals("HTTP/1.1")))
                      {
                          outToClient.writeBytes("HTTP/1.1 400 Bad Request"+CRLF);
                          outToClient.writeBytes("Content-Type: text/html"+CRLF);
                          outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                          outToClient.writeBytes("Connection: keep-alive"+CRLF);
                          outToClient.writeBytes(CRLF);
                          outToClient.writeBytes("&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;Error 400 - Bad Request&lt;/body&gt;&lt;/html&gt;"+CRLF);
                          outToClient.flush();                    
                      }
                      else
                      {
                          //Add the supplied rootpath to the recovered filename
                          String fullFilepath = rootpath + filename;
  
                          //Create a new file using the full filepathname
                          File file = new File(fullFilepath);
  
                          //If the created file is a directory then we look to return index.html
                          if(file.isDirectory())
                          {
                              //Add index.html to the supplied rootpath
                              fullFilepath = rootpath + "index.html";
  
                              //Check to see if index.html exists. If not, then return Error 404: Not Found
                              if(!new File(fullFilepath).exists())
                              {
                                  outToClient.writeBytes("HTTP/1.1 404 Not Found"+CRLF);
                                  outToClient.writeBytes("Content-Type: text/html"+CRLF);
                                  outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                                  outToClient.writeBytes("Connection: keep-alive"+CRLF);
                                  outToClient.writeBytes(CRLF);
                                  outToClient.writeBytes("&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;Error 404 - index.html was not found&lt;/body&gt;&lt;/html&gt;"+CRLF);
                                  outToClient.flush();
                              }
                          }
                          //If the created file simply does not exist then we need to return Error 404: Not Found
                          else if(!file.exists())
                          {
                              System.out.println("File Doesn't Exist!");
                              outToClient.writeBytes("HTTP/1.1 404 Not Found"+CRLF);
                              outToClient.writeBytes("Content-Type: text/html"+CRLF);
                              outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                              outToClient.writeBytes("Connection: keep-alive"+CRLF);
                              outToClient.writeBytes(CRLF);
                              outToClient.writeBytes("&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;Error 404 - " + filename + " was not found&lt;/body&gt;&lt;/html&gt;"+CRLF);
                              outToClient.flush();
  
                          }
                          //Otherwise, we have a well formed request, and we should use the specific command to
                          //help us decide how to respond
                          else
                          {
                              //Get the number of bytes in the file
                              int numOfBytes=(int)file.length();
  
                              //If we are given a GET request
                              if(command.equals("GET"))
                              {
                                  //Open a file input stream using the full file pathname
                                  FileInputStream inFile = new FileInputStream(fullFilepath);
  
                                  //Create an array of bytes to hold the data from the file
                                  byte[] fileinBytes = new byte[numOfBytes];
  
                                  //We now check the If-Modified-Since date (if present) against the file's
                                  //last modified date. If the file has not been modified, then return 304: Not Modified
                                  if(ifModSince != null)
                                  {
                                      //Put the string version of If-Modified-Since data into the HTTPDate Format
                                      try
                                      {
                                          ifModifiedSince = HTTPDateFormat.parse(ifModSince);
                                      }
                                      catch(ParseException e)
                                      {
                                          e.printStackTrace();
                                      }
  
                                      //We now need to do a bit of rearranging to get the last modified date of the file
                                      //in the correct HTTP Date Format to allow us to directly compare two date object
  
                                      //1. Create a new Date using the epoch time from file.lastModified()
                                      lastModifiedDate = new Date(file.lastModified());
                                      //2. Create a string version, formatted into our correct format
                                      String lastMod = HTTPDateFormat.format(lastModifiedDate);
  
                                      lastModifiedDate = new Date();
                                      //3. Finally, parse this string version into a Date object we can use in a comparison
                                      try
                                      {
                                          lastModifiedDate = HTTPDateFormat.parse(lastMod);
                                      }
                                      catch (ParseException e)
                                      {
                                          e.printStackTrace();
                                      }
  
                                      //Comparing the last modified date to the "If-Modified Since" date, if the last modified
                                      //date is before modified since date, return Status Code 304: Not Modified
                                      if((ifModifiedSince != null) &amp;&amp; (lastModifiedDate.compareTo(ifModifiedSince) &lt;= 0))
                                      {
                                          System.out.println("Not Modified!");
                                          //Write the header to the output stream 
                                          outToClient.writeBytes("HTTP/1.1 304 Not Modified"+CRLF);
                                          outToClient.writeBytes("Date: " + HTTPDateFormat.format(new Date())+CRLF);
                                          outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                                          outToClient.writeBytes("Last-Modified: " + lastModifiedDate+CRLF);
                                          outToClient.writeBytes("Content-Length: " + (int)file.length()+CRLF);
                                          outToClient.writeBytes(CRLF);
                                      }                                   
  
                                  }
                                  else
                                  {
                                      //Read in the data from the file using the input stream and store in the byte array
                                      inFile.read(fileinBytes);
  
                                      //Write the header to the output stream 
                                      outToClient.writeBytes("HTTP/1.1 200 OK"+CRLF);
                                      outToClient.writeBytes("Date: " + HTTPDateFormat.format(new Date())+CRLF);
                                      outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                                      outToClient.writeBytes("Connection: keep-alive"+CRLF);
                                      outToClient.writeBytes("Last-Modified: " + HTTPDateFormat.format(file.lastModified())+CRLF);
                                      outToClient.writeBytes("Content-Length: " + numOfBytes +CRLF);
                                      outToClient.writeBytes(CRLF);
  
                                      //Write the file
                                      outToClient.write(fileinBytes,0,numOfBytes);
                                      outToClient.flush();                                    
                                  }
  
                              }   
                              //If we are given a HEAD request
                              else if(command.equals("HEAD"))
                              {
                                  //Write the header to the output stream 
                                  outToClient.writeBytes("HTTP/1.1 200 OK"+CRLF);
                                  outToClient.writeBytes("Date: " + HTTPDateFormat.format(new Date())+CRLF);
                                  outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                                  outToClient.writeBytes("Connection: keep-alive"+CRLF);
                                  outToClient.writeBytes("Last-Modified: " + HTTPDateFormat.format(file.lastModified())+CRLF);
                                  outToClient.writeBytes("Content-Length: " + numOfBytes +CRLF);
                                  outToClient.writeBytes(CRLF);
  
                                  outToClient.flush();
                              }
                              //If the command is neither GET or HEAD, then this type of request has
                              //not been implemented. In this case, we must return Error 501: Not Implemented
                              else
                              {
                                  //Print the header and error information            
                                  outToClient.writeBytes("HTTP/1.1 501 Not Implemented"+CRLF);
                                  outToClient.writeBytes("Date: " + HTTPDateFormat.format(new Date())+CRLF);
                                  outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                                  outToClient.writeBytes("Connection: keep-alive"+CRLF);
                                  outToClient.writeBytes("Content-Type: text/html"+CRLF);
                                  outToClient.writeBytes(CRLF);
                                  outToClient.writeBytes("&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt; Desired Action Not Implemented &lt;/body&gt;&lt;/html&gt;"+CRLF);
  
                                  outToClient.flush();
  
                              }
                          }                                               
                      }
                  }
  
                  //Get the connection status for this request from the HashMap
                  String connect = (String)requestLines.get("Connection");
                  //If the connection status is not "keep alive" then we must close the socket
                  if(!connect.equals("keep-alive"))
                  {
                      // Close streams and socket.
                      outToClient.close();
                      inFromClient.close();
                      clientsocket.close();                   
                  }
                  //Otherwise, we can continue using this socket
                  //else
                  //{                 
                      //continue;
                  //}
              }
      }   
  </code></pre>