2013-03-01 2 views
0

GET 및 HEAD 요청을 처리하는 간단한 HTTP/1.1 호환 다중 스레드 웹 서버를 구현했습니다. 웹 서버를 통해 요청을하면 작동하지만 12 초의 시간 초과 후 SocketTimeoutException이 발생합니다.다중 스레드 Java 웹 서버 - java.net.SocketTimeoutException

Eclipse에서 실행하고 브라우저에서 localhost : portnumber로 이동 한 다음 로컬로 파일을 열어 웹 서버를 테스트하고 있습니다. 나는 그것이 없다면, 존재하지 않는 파일을 읽으려는 요청은 단순히 반환되지 않고, 404 찾을 수 없음 오류를 반환해야하기 때문에 시간 제한 값만 있습니다.

받은 SocketTimeoutExceptions 수는 요청을 처리하기 위해 열린 소켓 수와 같습니다. 나는이 예외를 어떻게 든 처리해야한다고 생각하지만, 어디서 어떻게해야하는지 잘 모르겠습니다. 이것을 처리하는 방법에 대한 예제 나 설명은 매우 유용합니다.

내 코드는 짧은 웹 서버 구성 요소와 분리 된 ThreadHandler 클래스로 분할되어 요청을 처리합니다. 새 클라이언트 소켓을 만들 때 새 스레드를 사용하여 요청을 처리합니다. 필요한 경우 ThreadHandler 클래스를 제공 할 수 있지만 훨씬 더 길다. 여기

은 웹 서버의 구성 요소입니다

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 클래스 내에서 요청을 분석하고 소켓을 통해 적절한 응답으로 응답. 영구적 인 연결을 구현했습니다. 요청에 "Connection : close"토큰이 포함되어 있으면 각 소켓 만 닫힙니다. 그러나, 내가 제대로 존재하지 않는 파일을 열려고 시도하고 404 Not Found Error를 반환해야하는 경우에 이것이 제대로 발생하는지 확신 할 수 없습니다.

누구나 이러한 예외를 처리하는 방법에 대한 아이디어가 있습니까? 스레드를 닫으려는 작업을해야합니까?

도움을 주시면 감사하겠습니다.

편집 :()

//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 클래스에있을 가능성이 큽니다. 실행 방법의 내용을 게시 할 수 있습니까? –

+0

내 run 메서드는 예외를 처리 할 수 ​​있도록 handleRequest()라는 다른 메서드를 호출합니다. 원래 상자에 코드를 게시합니다. 시간 내 줘서 고마워. – JCutz

+0

요청할 때마다 연결을 닫아야합니다. 그래서 404 응답이 클라이언트에게 반환되지 않습니다. 그렇지 않으면 반환 한 내용 다음에 CRLF 문자열이 필요하다고 생각합니다. HTTPD 프로토콜을 읽어야합니다. – Gray

답변

1

읽기 시간 제한을 설정하는 이유는 당신이 시간에 상한을 배치하는 나는 실행에 시도의 catch 문 내에서 호출 handleRequest()는이됩니다 피어가 당신에게 데이터를 보내기를 기다리는 데 쓸 준비가되었습니다. 그 한계가 무엇인지, 그리고 얼마나 자주 독서를 재 시도 할 준비가되었는지 (대부분의 경우 하나의 타임 아웃만으로도 충분하다) 얼마나 오랜 시간이 걸릴지는 알고 있지만, 어느 시점에서 결정할 것인가? 연결. 대부분의 HTTP 서버는이를 사용자가 결정할 수 있도록 구성합니다.

+0

각 오류 응답 내에 개별 타임 아웃을 넣어야합니까? 나는 그들을 어떻게 다룰 수 있을지 정말로 모른다. 도와 주셔서 다시 한번 감사드립니다. EJP, 저는 여전히 고심하고 있습니다. – JCutz

+0

나는 그 질문을 이해하지 못한다. 나는 그들을 정확하게 다루는 방법을 확실히 말했지만, 당신이 무엇을 원할 때 당신이 무엇을하고 싶은지 모를 경우 왜 읽기 타임 아웃을 설정하는지 이해하지 못합니다. 오직 당신 만이 당신의 읽기 타임 아웃이 얼마나 오래되어야 하는지를 알 수 있으며, 응용 프로그램의 다른 지점에서 다르게 설정하는 유효한 이유가 있는지 알 수 있습니다. – EJP