2013-03-01 3 views
0

Я реализовал простой HTTP-протокол, совместимый с HTTP/1.1, который обрабатывает запросы GET и HEAD. Когда я делаю запрос через веб-сервер, хотя он работает, я получаю исключение SocketTimeout после 12-секундного тайм-аута, который я установил.Многопоточный Java Web-сервер - java.net.SocketTimeoutException

Я тестирую свой веб-сервер, запустив его в Eclipse и направляя браузер в localhost: portnumber, а затем пытаюсь открыть файлы локально. У меня только значение таймаута, потому что, если у меня его нет, любой запрос на чтение файла, который не существует, просто не возвращается, тогда как он должен вернуть ошибку 404 Not Found.

Число получаемых SocketTimeoutExceptions равно количеству сокетов, которые были открыты для обработки запроса. Я подозреваю, что я должен как-то справляться с этим исключением, но я не уверен, где и как это сделать. Любой пример или объяснение того, как справиться с этим, было бы очень полезно.

Мой код разбит на короткий компонент веб-сервера, за которым следует отдельный класс ThreadHandler для обработки запросов. Когда я создаю новый клиентский сокет, я использую новый поток для обработки запроса. Я могу предоставить класс ThreadHandler, если это необходимо, но это намного, намного дольше.

Вот компонент WebServer:

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(); 
     } 
    } 

} 

В классе ThreadHandler я прочитал запрос от сокета, разобрать запрос и ответить соответствующий ответ через разъем. Я реализовал постоянные соединения, так что каждый сокет закрывается только в том случае, если запрос содержит токен «Соединение: закрыть» в запросе. Однако я не уверен, что это происходит должным образом, особенно в том случае, когда я пытаюсь открыть файл, который не существует, и должен вернуть ошибку 404 Not Found.

Есть ли у кого-нибудь идеи, как справляться с этими исключениями. Должен ли я делать что-то, чтобы закрыть потоки?

Любая помощь будет высоко оценена.

EDIT: Это handleRequest(), который я звонить из в попытке поймать заявление в перспективе()

//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<String,String> requestLines = new HashMap<String,String>(); 
       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("<html><head></head><body>Error 400 - Bad Request</body></html>"+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("<html><head></head><body>Error 400 - Bad Request</body></html>"+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("<html><head></head><body>Error 404 - index.html was not found</body></html>"+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("<html><head></head><body>Error 404 - " + filename + " was not found</body></html>"+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) && (lastModifiedDate.compareTo(ifModifiedSince) <= 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("<html><head></head><body> Desired Action Not Implemented </body></html>"+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; 
       //} 
      } 
    } 
+0

Недопустимый код, скорее всего, в вашем классе HandleThreads, можете ли вы опубликовать содержимое метода run? –

+0

Мой метод run вызывает другой метод, называемый handleRequest(), так что он может обрабатывать исключения. Я отправлю код в оригинальной коробке. Спасибо за ваше время. – JCutz

+0

Возможно, вы должны закрыть соединение после каждого запроса. Вот почему ваши 404 ответы не возвращаются клиенту. В противном случае, я считаю, что вам нужны строки CRLF после того, как вы вернетесь. Вам нужно прочитать ваш протокол HTTPD. – Gray

ответ

1

Причина для установки тайм-аут чтения должен поместить верхнюю границу времени вы подготовленный к тому, чтобы тратить время ожидания того, что сверстник отправит вам данные. Только вы знаете, какой должен быть этот предел, и как часто вы готовы повторить чтение (если вообще: скорее всего, один тайм-аут достаточно), и как долго это слишком долго, но в какой-то момент вы решите это и просто закроете связь. Большинство HTTP-серверов делают эту конфигурацию так, что пользователь может решить.

+0

Должен ли я помещать индивидуальные тайм-ауты в каждый ответ на ошибку? Я действительно не уверен, как с ними справиться. Еще раз спасибо за помощь EJP, я все еще боюсь. – JCutz

+0

Я не понимаю вопроса. Я определенно рассказал вам, как с ними справиться, но я не понимаю, почему вы устанавливаете тайм-аут чтения вообще, если не знаете, что хотите делать, когда получите его. Только вы можете узнать, как долго будут длиться ваши тайм-ауты чтения, и только вы можете узнать, есть ли веская причина для их настройки по-разному в разных точках вашего приложения. – EJP

Смежные вопросы