2013-03-01 95 views
0

我已經實現了一個簡單的HTTP/1.1兼容的多線程Web服務器,它處理GET和HEAD請求。當我通過Web服務器發出請求時,儘管它有效,但我在設置了12秒的超時後收到一個SocketTimeoutException。多線程Java Web服務器 - java.net.SocketTimeoutException

我通過在Eclipse中運行它並將瀏覽器指向localhost:portnumber然後嘗試在本地打開文件來測試我的Web服務器。我只有超時值,因爲如果我沒有它,讀取不存在的文件的任何請求都不會返回,而應該返回404 Not Found錯誤。

我收到的SocketTimeoutExceptions的數量等於爲處理請求而打開的套接字數量。我懷疑我應該以某種方式處理這個異常,但我不確定在哪裏或如何去做。如何處理這個問題的任何例子或解釋都會非常有用。

我的代碼被拆分成一個簡短的web服務器組件,後面跟着一個單獨的ThreadHandler類來處理請求。當我創建一個新的客戶端套接字時,我使用一個新的線程來處理請求。如果有必要,我可以提供ThreadHandler類,但是它要長得多。

這裏是web服務器組件:

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 I類讀取來自插座的請求,解析該請求,並經由所述插座適當響應來響應。我已經實現了持久連接,因此每個套接字只有在請求中包含請求中的「Connection:close」標記時纔會關閉。但是,我不確定這是否正常發生,尤其是在我嘗試打開不存在的文件並應返回404 Not Found Error的情況下。

有沒有人有任何想法如何處理這些例外。我應該做些什麼來關閉線程?

任何幫助將不勝感激。

編輯:這是程序的handleRequest()這是我從一個try catch語句內運行調用()

//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方法的內容嗎? – 2013-03-01 01:32:30

+0

我的run方法調用另一個名爲handleRequest()的方法,以便它可以處理異常。我將在原始框中發佈代碼。謝謝你的時間。 – JCutz 2013-03-01 01:42:03

+0

您應該在每次請求後關閉連接。這就是爲什麼你的404響應沒有返回到客戶端。否則,我相信你需要在你返回的內容之後使用CRLF字符串。你需要閱讀你的HTTPD協議。 – Gray 2013-03-01 02:40:52

回答

1

之所以設置讀取超時是把一個上限,你是時間準備等待同行向你發送數據。只有你知道這個限制應該是多少,以及你準備重試閱讀的頻率如何(如果有的話:最有可能一個超時就足夠了),以及多久太久了,但在某個時候你會決定並且只是關閉連接。大多數HTTP服務器都是可配置的,因此用戶可以決定。

+0

所以我應該在每個錯誤響應中放置單個超時?我真的不知道如何處理它們。再次感謝您的幫助EJP,我仍然在努力。 – JCutz 2013-03-01 01:47:38

+0

我不明白這個問題。我當然已經告訴過你如何處理它們,但是我不明白你爲什麼設置一個讀取超時,如果你不知道你想要做什麼的話。只有您可以知道您的閱讀超時時間應該是多長時間,只有您可以知道是否有適當的理由在應用程序中的不同位置設置它們。 – EJP 2013-03-01 05:15:48