diff --git a/src/xdvrx1_serverProject/ClientRequest.java b/src/xdvrx1_serverProject/ClientRequest.java index cb2ac60..e7764d9 100644 --- a/src/xdvrx1_serverProject/ClientRequest.java +++ b/src/xdvrx1_serverProject/ClientRequest.java @@ -1,6 +1,6 @@ package xdvrx1_serverProject; -/* +/** * This is where the request of a client * is being processed. When we say `client`, * it can be Google Chrome or any other browser. @@ -8,249 +8,170 @@ * `FileWebServer` class. */ -import java.nio.file.Files; import java.io.*; import java.net.*; import java.util.logging.*; public class ClientRequest implements Runnable { - - FileNotFoundMessage message1 = new FileNotFoundMessage(); - NotSupportedMessage message2 = new NotSupportedMessage(); - - private String defaultPage = "index.html"; - private File rootDirectory; - private Socket connection; - - private final static Logger requestLogger = - Logger.getLogger(ClientRequest.class.getCanonicalName()); - - //the constructor - public ClientRequest(File rootDirectory, - String defaultPage, - Socket connection) { - - //check if the given rootDirectory is a file - //and will explicitly throw an exception - if (rootDirectory.isFile()) { - throw new - IllegalArgumentException("rootDirectory must be" - + "a directory, not a file"); - } - - //will try to get the canonical pathname - //from the supplied `rootDirectory` argument - try { - rootDirectory = rootDirectory.getCanonicalFile(); - } catch (IOException ex) { - requestLogger.log(Level.WARNING, "IOException", ex); - } - - //constructors `rootDirectory` is assigned to - //the successful `rootDirectory` - this.rootDirectory = rootDirectory; - - if (defaultPage != null) { - this.defaultPage = defaultPage; - } - - this.connection = connection; - - } - - @Override - public void run() { - - try { - //remember, once the connection is established - //the server will use the Socket to pass - //data back and forth - - //raw output stream, - //in case it is not a text document - //this will be used purely to pass - //the data as bytes - OutputStream raw = - new BufferedOutputStream(connection.getOutputStream()); - - //for text files that uses the - //underlying output stream - Writer out = - new OutputStreamWriter(raw); - - //needed to add new line correctly - //for different platforms - BufferedWriter bufferedOut = - new BufferedWriter(out); - - BufferedInputStream bis = - new BufferedInputStream(connection.getInputStream()); - - //for reading the GET header from a browser - Reader in = - new - InputStreamReader(bis, "US-ASCII"); - - //the request send by a client - //take note, this can be loaded into a data structure - //instead of using StringBuffer to generate string - //but, it's up to you, for demonstration - //I will just use the StringBuffer to build - //the string object - StringBuffer userRequest = new StringBuffer(); - - //this will be the basis to get - //all the bytes from the stream - int bufferSize = bis.available(); - - while (true) { - if (userRequest.length() > bufferSize-1) { - //for performance, always shutdown - //after breaking from this loop - connection.shutdownInput(); - break; - } + + private String defaultPage = "index.html"; + private File rootDirectory; + private Socket connection; + + private final static Logger requestLogger = + Logger.getLogger(ClientRequest.class.getCanonicalName()); + + //the constructor + public ClientRequest(File rootDirectory, + String defaultPage, + Socket connection) { + + //check if the given rootDirectory is a file + //and will explicitly throw an exception + if (rootDirectory.isFile()) { + throw new + IllegalArgumentException("rootDirectory must be" + + "a directory, not a file"); + } + + //will try to get the canonical pathname + //from the supplied `rootDirectory` argument + try { + rootDirectory = rootDirectory.getCanonicalFile(); + } catch (IOException ex) { + requestLogger.log(Level.WARNING, "IOException", ex); + } + + //constructors `rootDirectory` is assigned to + //the successful `rootDirectory` + this.rootDirectory = rootDirectory; + + if (defaultPage != null) { + this.defaultPage = defaultPage; + } + + this.connection = connection; + + } + + @Override + public void run() { + + try { + //remember, once the connection is established + //the server will use the Socket to pass + //data back and forth - //read() of Reader is actually a blocking - //method, and without proper algorithm - //this will hang the entire program - int c = in.read(); - userRequest.append((char) c); + //raw output stream, + //in case it is not a text document + //this will be used purely to pass + //the data as bytes + OutputStream raw = + new BufferedOutputStream(connection.getOutputStream()); - //ignore the line endings, - //the Reader will interpret this as end of buffer - //we need to read the entire content of the buffer - if (c == '\n' || c == '\r' || c == 1) continue; - } - - //build a string object from StringBuffer - String userRequestToString = userRequest.toString(); - - //get the first line through this index - int indexOfFirst = userRequestToString.indexOf("\r\n"); - String firstLine = userRequestToString - .substring(0,indexOfFirst); - - //express it in the logger, mostly for debugging purposes - requestLogger - .info(connection - .getRemoteSocketAddress() + " " + firstLine); - - //`token` are the words separated from the request line, - //for example, `GET /data/ HTTP/1.1` - String[] token = firstLine.split("\\s+"); - //0 index tells the method - String method = token[0]; - //null at first - String http_version = ""; - - if (method.equals("GET")) { - String fileName = token[1]; + //for text files that uses the + //underlying output stream + Writer out = + new OutputStreamWriter(raw); - //add manually the default page - if (fileName.endsWith("/")) { - fileName = fileName + defaultPage; - } + //needed to add new line correctly + //for different platforms + BufferedWriter bufferedOut = + new BufferedWriter(out); - //get the content type for proper encoding of data - String contentType = - URLConnection.getFileNameMap().getContentTypeFor(fileName); + BufferedInputStream bis = + new BufferedInputStream(connection.getInputStream()); - if (token.length > 2) { - http_version = token[2]; - } + //for reading the GET header from a browser + Reader in = + new + InputStreamReader(bis, "US-ASCII"); - File actualFile = - new File(rootDirectory, - fileName.substring(1, fileName.length())); + //the request send by a client + //take note, this can be loaded into a data structure + //instead of using StringBuffer to generate string + //but, it's up to you, for demonstration + //I will just use the StringBuffer to build + //the string object + ReadInputStream readInputStream = new ReadInputStream(); + StringBuffer userRequest = + readInputStream.readUserRequest(bis, in, connection); - String root = rootDirectory.getPath(); - - // restrict clients inside the document root - if (actualFile.canRead() - && actualFile.getCanonicalPath().startsWith(root)) { - - byte[] _data = Files.readAllBytes(actualFile.toPath()); - - if (http_version.startsWith("HTTP/")) { - // send a MIME header - ServerHeader - .serverHeader(out, - "HTTP/1.0 200 OK", - contentType, - _data.length); - } - - // still send the file; - //and use the underlying stream - //instead of the writer - raw.write(_data); - raw.flush(); - - } else { - - // file not found - if (http_version.startsWith("HTTP/")) { - // send a MIME header - ServerHeader - .serverHeader(out, - "HTTP/1.0 404 File Not Found", - "text/html; charset=utf-8", - message1.body.length()); - } - - out.write(message1.body); - out.flush(); - } + //build a string object from StringBuffer + String userRequestToString = userRequest.toString(); - } else if(method.equals("POST")) { + //get the first line through this index + int indexOfFirst = userRequestToString.indexOf("\r\n"); + String firstLine = userRequestToString + .substring(0,indexOfFirst); - //get the request body for POST method - //add 4, because the index that - //will be returned is relative to the - //very first occurence of the string - String requestBody = - userRequestToString - .substring(userRequestToString.lastIndexOf("\r\n\r\n") + 4); + //express it in the logger, mostly for debugging purposes + requestLogger + .info(connection + .getRemoteSocketAddress() + " " + firstLine); - //just showing the input data back to the client - //a lot of things can be done for the request body - //it can go directly to a file or a database, - //or be loaded into an XML file for further processing + //`token` are the words separated from the request line, + //for example, `GET /data/ HTTP/1.1` + String[] token = firstLine.split("\\s+"); + //0 index tells the method + String method = token[0]; + //null at first + String http_version = ""; - //we use also the buffered out writer - //to make sure that the new line will be correct - //for all platforms - bufferedOut.write("data recorded:"); - bufferedOut.newLine(); - bufferedOut.write(requestBody); - bufferedOut.flush(); - - } else { - - //not yet implemented methods - if (http_version.startsWith("HTTP/")) { - // send a MIME header - ServerHeader.serverHeader(out, - "HTTP/1.0 501 Not Implemented", - "text/html; charset=utf-8", - message2.body.length()); + if (method.equals("GET")) { + + GETMethod getMethod = new GETMethod(); + byte[] _data = getMethod.processGET(rootDirectory, + token, + defaultPage, + http_version, + out); + // still send the file; + //and use the underlying stream + //instead of the writer + raw.write(_data); + raw.flush(); + + } else if(method.equals("POST")) { + + POSTMethod postMethod = new POSTMethod(); + + String requestBody = + postMethod.returnPOSTData(userRequestToString); + + //we use also the buffered out writer + //to make sure that the new line will be correct + //for all platforms + bufferedOut.write("data recorded:"); + bufferedOut.newLine(); + bufferedOut.write(requestBody); + bufferedOut.flush(); + + } else { + + //not yet implemented methods + if (http_version.startsWith("HTTP/")) { + // send a MIME header + ServerHeader.serverHeader(out, + "HTTP/1.0 501 Not Implemented", + "text/html; charset=utf-8", + NotSupportedMessage.content.length()); + } + + out.write(NotSupportedMessage.content); + out.flush(); } - - out.write(message2.body); - out.flush(); - } - } catch (IOException ex) { - requestLogger - .log(Level.WARNING, "Can't talk to: " - + connection.getRemoteSocketAddress(), ex); - } finally { - try { - connection.close(); - } catch (IOException ex) { - requestLogger.log(Level.WARNING, "IO exception", ex); - } - } - } - + } catch (IOException ex) { + requestLogger + .log(Level.WARNING, "Can't talk to: " + + connection.getRemoteSocketAddress(), ex); + } finally { + try { + connection.close(); + } catch (IOException ex) { + requestLogger.log(Level.WARNING, "IO exception", ex); + } + } + } + } diff --git a/src/xdvrx1_serverProject/FileNotFoundMessage.java b/src/xdvrx1_serverProject/FileNotFoundMessage.java index d325488..ee7599c 100644 --- a/src/xdvrx1_serverProject/FileNotFoundMessage.java +++ b/src/xdvrx1_serverProject/FileNotFoundMessage.java @@ -1,14 +1,14 @@ package xdvrx1_serverProject; class FileNotFoundMessage { - - String body = - new StringBuilder("\r\n") - .append("File Not Found\r\n") - .append("\r\n") - .append("") - .append("

HTTP Error 404: File Not Found [Try again later]

\r\n") - .append("\r\n") - .toString(); - -} \ No newline at end of file + + static final String content = + new StringBuilder("\r\n") + .append("File Not Found\r\n") + .append("\r\n") + .append("") + .append("

HTTP Error 404: File Not Found [Try again later]

\r\n") + .append("\r\n") + .toString(); + +} diff --git a/src/xdvrx1_serverProject/FileWebServer.java b/src/xdvrx1_serverProject/FileWebServer.java index 79fd231..caddc92 100644 --- a/src/xdvrx1_serverProject/FileWebServer.java +++ b/src/xdvrx1_serverProject/FileWebServer.java @@ -1,64 +1,64 @@ package xdvrx1_serverProject; -/* +/** * This is the actual server class. * This will call the overridden * method `run()` of `Runnable`. */ import java.util.concurrent.*; -import java.util.logging.*; - import java.io.*; import java.net.*; +import java.util.logging.*; + public class FileWebServer { - - private final File rootDirectory; - private final int port; - private static final int pool_count = 10000; - private static final String defaultPage = "index.html"; - - private static final Logger - serverLogger = Logger.getLogger(FileWebServer - .class.getCanonicalName()); - - //the constructor - public FileWebServer(File rootDirectory, int port) - throws IOException { - - if (!rootDirectory.isDirectory()) { - throw new IOException(rootDirectory - + " is not a directory"); - } - - this.rootDirectory = rootDirectory; - this.port = port; - - } - - //void start - public void start() - throws IOException { - - ExecutorService pool = - Executors.newFixedThreadPool(pool_count); - - try (ServerSocket server = new ServerSocket(port)) { - - serverLogger.info("Listening on port " + server.getLocalPort()); - serverLogger.info("@DocumentRoot"); - - while (true) { - try { - Socket request = server.accept(); - Runnable r = - new ClientRequest(rootDirectory, defaultPage, request); - pool.submit(r); - } catch (IOException ex) { - serverLogger.log(Level.WARNING, "Error accepting connection", ex); + + private final File rootDirectory; + private final int port; + private static final int pool_count = 1000; + private static final String defaultPage = "index.html"; + + private static final Logger + serverLogger = Logger.getLogger(FileWebServer + .class.getCanonicalName()); + + //the constructor + public FileWebServer(File rootDirectory, int port) + throws IOException { + + if (!rootDirectory.isDirectory()) { + throw new IOException(rootDirectory + + " is not a directory"); + } + + this.rootDirectory = rootDirectory; + this.port = port; + + } + + //void start + public void start() + throws IOException { + + ExecutorService pool = + Executors.newFixedThreadPool(pool_count); + + try (ServerSocket server = new ServerSocket(port)) { + + serverLogger.info("Listening on port " + server.getLocalPort()); + serverLogger.info("@DocumentRoot"); + + while (true) { + try { + Socket request = server.accept(); + Runnable r = + new ClientRequest(rootDirectory, defaultPage, request); + pool.submit(r); + } catch (IOException ex) { + serverLogger.log(Level.WARNING, "Error accepting connection", ex); + } } - } - } - } + } + } } \ No newline at end of file diff --git a/src/xdvrx1_serverProject/GETMethod.java b/src/xdvrx1_serverProject/GETMethod.java new file mode 100644 index 0000000..b6b1e6d --- /dev/null +++ b/src/xdvrx1_serverProject/GETMethod.java @@ -0,0 +1,79 @@ +package xdvrx1_serverProject; + +import java.nio.file.Files; +import java.io.*; +import java.net.*; + +class GETMethod { + + byte[] processGET(File rootDirectory, + String[] token, + String defaultPage, + String http_version, + Writer out) { + + try { + String fileName = token[1]; + + //add manually the default page + if (fileName.endsWith("/")) { + fileName = fileName + defaultPage; + } + + //get the content type for proper encoding of data + String contentType = + URLConnection.getFileNameMap().getContentTypeFor(fileName); + + if (token.length > 2) { + http_version = token[2]; + } + + File actualFile = + new File(rootDirectory, + fileName.substring(1, fileName.length())); + + String root = rootDirectory.getPath(); + + // restrict clients inside the document root + if (actualFile.canRead() + && actualFile.getCanonicalPath().startsWith(root)) { + + byte[] _data = Files.readAllBytes(actualFile.toPath()); + + if (http_version.startsWith("HTTP/")) { + // send a MIME header + ServerHeader + .serverHeader(out, + "HTTP/1.0 200 OK", + contentType, + _data.length); + } + + return _data; + + } else { + + // file not found + if (http_version.startsWith("HTTP/")) { + + // send a MIME header + ServerHeader + .serverHeader(out, + "HTTP/1.0 404 File Not Found", + "text/html; charset=utf-8", + FileNotFoundMessage.content.length()); + } + + out.write(FileNotFoundMessage.content); + out.flush(); + return null; + } + + } catch (IOException ioe) { + System.out.println(ioe.getMessage()); + return null; + } + + } + +} \ No newline at end of file diff --git a/src/xdvrx1_serverProject/MainMethod.java b/src/xdvrx1_serverProject/MainMethod.java index 4a2a5cb..6d00f51 100644 --- a/src/xdvrx1_serverProject/MainMethod.java +++ b/src/xdvrx1_serverProject/MainMethod.java @@ -3,30 +3,8 @@ import java.io.*; class MainMethod { - public static void main(String[] args) { - - try { - - //the relative root directory - //it's up to you if you want to change this - File currentDir = new File("."); - - //create an instance of `FileWebServer` - //at the current directory and using port 80 - //again, it is up to you when you want to change - //the port - FileWebServer filewebserver = new FileWebServer(currentDir, 80); - - //call `start` method that - //contains the call for the Runnable `run` - //of `ClientRequest` class - filewebserver.start(); - - } catch (IOException ex) { - //throws an exception if `currentDir` - //is not recognized as such - System.out.println(ex); - } - } - + public static void main(String[] args) { + ServerApp serverApp = new ServerApp(); + serverApp.build(); + } } \ No newline at end of file diff --git a/src/xdvrx1_serverProject/NotSupportedMessage.java b/src/xdvrx1_serverProject/NotSupportedMessage.java index facb783..247813d 100644 --- a/src/xdvrx1_serverProject/NotSupportedMessage.java +++ b/src/xdvrx1_serverProject/NotSupportedMessage.java @@ -1,12 +1,12 @@ package xdvrx1_serverProject; class NotSupportedMessage { - - String body = new StringBuilder("\r\n") - .append("Not Implemented\r\n") - .append("\r\n") - .append("") - .append("

HTTP Error 501: Not Yet Supported Method

\r\n") - .append("\r\n") - .toString(); + + static final String content = new StringBuilder("\r\n") + .append("Not Implemented\r\n") + .append("\r\n") + .append("") + .append("

HTTP Error 501: Not Yet Supported Method

\r\n") + .append("\r\n") + .toString(); } \ No newline at end of file diff --git a/src/xdvrx1_serverProject/POSTMethod.java b/src/xdvrx1_serverProject/POSTMethod.java new file mode 100644 index 0000000..d56f2f7 --- /dev/null +++ b/src/xdvrx1_serverProject/POSTMethod.java @@ -0,0 +1,21 @@ +package xdvrx1_serverProject; + +class POSTMethod { + + String returnPOSTData(String userRequestToString) { + + //get the request body for POST method + //add 4, because the index that + //will be returned is relative to the + //very first occurence of the string + String requestBody = + userRequestToString + .substring(userRequestToString.lastIndexOf("\r\n\r\n") + 4); + + //just showing the input data back to the client + //a lot of things can be done for the request body + //it can go directly to a file or a database, + //or be loaded into an XML file for further processing + return requestBody; + } +} \ No newline at end of file diff --git a/src/xdvrx1_serverProject/ReadInputStream.java b/src/xdvrx1_serverProject/ReadInputStream.java new file mode 100644 index 0000000..aef2d38 --- /dev/null +++ b/src/xdvrx1_serverProject/ReadInputStream.java @@ -0,0 +1,44 @@ +package xdvrx1_serverProject; + +import java.io.*; +import java.net.*; + +class ReadInputStream { + + StringBuffer readUserRequest(BufferedInputStream bis, + Reader in, + Socket connection) { + + StringBuffer userRequest = new StringBuffer(); + + try { + //this will be the basis to get + //all the bytes from the stream + int bufferSize = bis.available(); + + while (true) { + if (userRequest.length() > bufferSize-1) { + //for performance, always shutdown + //after breaking from this loop + connection.shutdownInput(); + break; + } + + //read() of Reader is actually a blocking + //method, and without proper algorithm + //this will hang the entire program + int c = in.read(); + userRequest.append((char) c); + + //ignore the line endings, + //the Reader will interpret this as end of buffer + //we need to read the entire content of the buffer + if (c == '\n' || c == '\r' || c == 1) continue; + } + return userRequest; + } catch (IOException ioe) { + System.out.println(ioe.getMessage()); + return null; + } + } +} \ No newline at end of file diff --git a/src/xdvrx1_serverProject/ServerApp.java b/src/xdvrx1_serverProject/ServerApp.java new file mode 100644 index 0000000..1383f58 --- /dev/null +++ b/src/xdvrx1_serverProject/ServerApp.java @@ -0,0 +1,31 @@ +package xdvrx1_serverProject; + +import java.io.*; + +class ServerApp { + public void build() { + + try { + //the relative root directory + //it's up to you if you want to change this + File currentDir = new File("."); + + //create an instance of `FileWebServer` + //at the current directory and using port 80 + //again, it is up to you when you want to change + //the port + FileWebServer filewebserver = new FileWebServer(currentDir, 80); + + //call `start` method that + //contains the call for the Runnable `run` + //of `ClientRequest` class + filewebserver.start(); + + } catch (IOException ex) { + //throws an exception if `currentDir` + //is not recognized as such + System.out.println(ex.getMessage()); + } + } + +} \ No newline at end of file diff --git a/src/xdvrx1_serverProject/ServerHeader.java b/src/xdvrx1_serverProject/ServerHeader.java index 0d1ffbc..96285b2 100644 --- a/src/xdvrx1_serverProject/ServerHeader.java +++ b/src/xdvrx1_serverProject/ServerHeader.java @@ -3,22 +3,22 @@ import java.util.*; import java.io.*; -class ServerHeader { - - private static Date current = new Date(); - - static void serverHeader(Writer out, - String responseCode, - String contentType, - int length) - throws IOException { - - out.write(responseCode + "\r\n"); - out.write("Date: " + current + "\r\n"); - out.write("Server: `xdvrx1_Server` 3.0\r\n"); - out.write("Content-length: " + length + "\r\n"); - out.write("Content-type: " + contentType + "\r\n\r\n"); - out.flush(); - } - +class ServerHeader { + + static void serverHeader(Writer out, + String responseCode, + String contentType, + int length) + throws IOException { + + Date current = new Date(); + + out.write(responseCode + "\r\n"); + out.write("Date: " + current + "\r\n"); + out.write("Server: `xdvrx1_Server` 3.0\r\n"); + out.write("Content-length: " + length + "\r\n"); + out.write("Content-type: " + contentType + "\r\n\r\n"); + out.flush(); + } + }