简体   繁体   English

多线程Java Web Server-java.net.SocketTimeoutException

[英]Multithreaded Java Web Server - java.net.SocketTimeoutException

I've implemented a simple HTTP/1.1 compliant multithreaded web server which handles GET and HEAD requests. 我已经实现了一个简单的HTTP / 1.1兼容多线程Web服务器,该服务器可以处理GET和HEAD请求。 When I make a request through the web server, although it works, I'm getting a SocketTimeoutException after the 12 second timeout I set. 当我通过Web服务器发出请求时,尽管它可以工作,但在设置的12秒超时之后,我收到了SocketTimeoutException。

I'm testing my webserver by running it in Eclipse and directing the browser to localhost:portnumber and then trying to open files locally. 我正在测试Web服务器,方法是在Eclipse中运行它,并将浏览器定向到localhost:portnumber,然后尝试在本地打开文件。 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. 我只有超时值,因为如果没有超时值,则读取不存在的文件的任何请求都不会返回,而应该返回404 Not Found错误。

The number of SocketTimeoutExceptions I receive is equal to the number of sockets that were opened to handle the request. 我收到的SocketTimeoutExceptions的数量等于为处理请求而打开的套接字的数量。 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. 关于如何处理此问题的任何示例或解释将非常有用。

My code is split into a short webserver component followed with a separate ThreadHandler class to handle the requests. 我的代码分为一个简短的Web服务器组件,后跟一个单独的ThreadHandler类来处理请求。 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. 如果需要,我可以提供ThreadHandler类,但是它的时间长得多。

Here is the WebServer component: 这是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();
        }
    }

}

Within the ThreadHandler class I read the request from the socket, parse the request and respond with appropriate response via the socket. 在ThreadHandler类中,我从套接字读取请求,解析请求并通过套接字以适当的响应进行响应。 I've implemented persistent connections, so that each socket is only closed if the request includes the "Connection: close" token within the request. 我已经实现了持久连接,因此仅当请求在请求中包含“ Connection:close”令牌时,才关闭每个套接字。 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. 但是,我不确定这种情况是否正确发生,特别是在我尝试打开不存在的文件并应返回404 Not Found Error的情况下。

Does anyone have any ideas how to handle these exceptions. 有谁知道如何处理这些异常。 Should I be doing something to close the threads? 我应该做些什么来关闭线程吗?

Any help would be much appreciated. 任何帮助将非常感激。

EDIT: This is the handleRequest() which I call from within a try catch statement in run() 编辑:这是handleRequest(),我从run()的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;
                //}
            }
    }   

The reason for setting a read timeout is to place an upper bound on the time you are prepared to spend waiting for the peer to send you data. 设置读取超时的原因是在准备等待对等方向您发送数据的时间上设置上限。 Only you know what that limit should be, and how often you are prepared to retry the read (if at all: most probably one timeout is enough), and how long is too long, but at some point you will decide that and just close the connection. 只有您知道该限制应该是多少,以及您准备重试读取的频率(如果有的话:很可能:一次超时就足够了)以及多长时间太长,但是在某个时候,您将决定并立即关闭连接。 Most HTTP servers make this configurable so the user can decide. 大多数HTTP服务器使此配置成为可配置,以便用户决定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM