Я реализовал простой 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;
//}
}
}
Недопустимый код, скорее всего, в вашем классе HandleThreads, можете ли вы опубликовать содержимое метода run? –
Мой метод run вызывает другой метод, называемый handleRequest(), так что он может обрабатывать исключения. Я отправлю код в оригинальной коробке. Спасибо за ваше время. – JCutz
Возможно, вы должны закрыть соединение после каждого запроса. Вот почему ваши 404 ответы не возвращаются клиенту. В противном случае, я считаю, что вам нужны строки CRLF после того, как вы вернетесь. Вам нужно прочитать ваш протокол HTTPD. – Gray