Rewrite HTTPServer implementation

This commit is contained in:
apple502j 2022-09-16 20:46:31 +09:00
parent c5033ccfc0
commit 3f9d91b731
No known key found for this signature in database
GPG key ID: AFDA4829AC6D8993

View file

@ -6,28 +6,50 @@ import me.jonasjones.mcwebserver.util.VerboseLogger;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.Date; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.time.Instant;
import java.util.StringTokenizer; import java.util.StringTokenizer;
public class HTTPServer implements Runnable { public class HTTPServer implements Runnable {
static final File WEB_ROOT = new File(ModConfigs.WEB_ROOT); static Path WEB_ROOT;
static final String DEFAULT_FILE = ModConfigs.WEB_FILE_ROOT; static final String DEFAULT_FILE = ModConfigs.WEB_FILE_ROOT;
static final String FILE_NOT_FOUND = ModConfigs.WEB_FILE_404; static final String FILE_NOT_FOUND = ModConfigs.WEB_FILE_404;
static final String METHOD_NOT_SUPPORTED = ModConfigs.WEB_FILE_NOSUPPORT; static final String METHOD_NOT_SUPPORTED = ModConfigs.WEB_FILE_NOSUPPORT;
// port to listen connection // port to listen connection
static final int PORT = ModConfigs.WEB_PORT; static final int PORT = ModConfigs.WEB_PORT;
private static final byte[] NOT_IMPLEMENTED = "HTTP/1.1 405 Method Not Allowed\r\n".getBytes(StandardCharsets.UTF_8);
private static final byte[] OK = "HTTP/1.1 200 OK\r\n".getBytes(StandardCharsets.UTF_8);
private static final byte[] NOT_FOUND = "HTTP/1.1 404 Not Found\r\n".getBytes(StandardCharsets.UTF_8);
private static final byte[] HEADERS = String.join(
"\r\n",
"Server: Java HTTP Server from SSaurel : 1.0",
"X-Frame-Options: DENY",
"X-Content-Type-Options: nosniff",
"" // trailing CRLF
).getBytes(StandardCharsets.UTF_8);
private static final byte[] CRLF = new byte[]{0x0D, 0x0A};
// Client Connection via Socket Class // Client Connection via Socket Class
private Socket connect; private final Socket connect;
static {
try {
WEB_ROOT = Path.of(ModConfigs.WEB_ROOT).toRealPath(LinkOption.NOFOLLOW_LINKS);
} catch (IOException e) {
WEB_ROOT = Path.of(ModConfigs.WEB_ROOT);
}
}
public HTTPServer(Socket c) { public HTTPServer(Socket c) {
connect = c; connect = c;
@ -35,19 +57,20 @@ public class HTTPServer implements Runnable {
public static void main() { public static void main() {
try { try {
ServerSocket serverConnect = new ServerSocket(PORT); try (ServerSocket serverConnect = new ServerSocket(PORT)) {
McWebserver.LOGGER.info("Server started."); McWebserver.LOGGER.info("Server started.");
McWebserver.LOGGER.info("Listening for connections on port : " + PORT); McWebserver.LOGGER.info("Listening for connections on port : " + PORT);
// we listen until user halts server execution // we listen until user halts server execution
while (true) { while (true) {
HTTPServer myServer = new HTTPServer(serverConnect.accept()); HTTPServer myServer = new HTTPServer(serverConnect.accept());
VerboseLogger.info("Connection opened. (" + new Date() + ")"); VerboseLogger.info("Connection opened. (" + Instant.now() + ")");
// create dedicated thread to manage the client connection // create dedicated thread to manage the client connection
Thread thread = new Thread(myServer); Thread thread = new Thread(myServer);
thread.start(); thread.start();
}
} }
} catch (IOException e) { } catch (IOException e) {
@ -82,21 +105,21 @@ public class HTTPServer implements Runnable {
VerboseLogger.info("501 Not Implemented : " + method + " method."); VerboseLogger.info("501 Not Implemented : " + method + " method.");
// we return the not supported file to the client // we return the not supported file to the client
File file = new File(WEB_ROOT, METHOD_NOT_SUPPORTED); Path file = WEB_ROOT.resolve(METHOD_NOT_SUPPORTED);
int fileLength = (int) file.length(); long fileLength = Files.size(file);
String contentMimeType = "text/html"; String contentMimeType = "text/html";
//read content to return to client //read content to return to client
byte[] fileData = readFileData(file, fileLength); byte[] fileData = readFileData(file);
// we send HTTP Headers with data to client // we send HTTP Headers with data to client
VerboseLogger.info("HTTP/1.1 501 Not Implemented"); dataOut.write(NOT_IMPLEMENTED);
VerboseLogger.info("Server: Java HTTP Server from SSaurel : 1.0"); //hopefully enough credits dataOut.write(HEADERS); //hopefully enough credits
VerboseLogger.info("Date: " + new Date()); dataOut.write("Date: %s\r\n".formatted(Instant.now()).getBytes(StandardCharsets.UTF_8));
VerboseLogger.info("Content-type: " + contentMimeType); dataOut.write("Content-Type: %s\r\n".formatted(contentMimeType).getBytes(StandardCharsets.UTF_8));
VerboseLogger.info("Content-length: " + fileLength); dataOut.write("Content-Length: %s\r\n".formatted(fileLength).getBytes(StandardCharsets.UTF_8));
VerboseLogger.info(""); // blank line between headers and content, very important ! dataOut.write(CRLF); // blank line between headers and content, very important !
// file // file
dataOut.write(fileData, 0, fileLength); dataOut.write(fileData, 0, fileData.length);
dataOut.flush(); dataOut.flush();
} else { } else {
@ -104,31 +127,36 @@ public class HTTPServer implements Runnable {
if (fileRequested.endsWith("/")) { if (fileRequested.endsWith("/")) {
fileRequested += DEFAULT_FILE; fileRequested += DEFAULT_FILE;
} }
if (fileRequested.startsWith("/")) {
fileRequested = fileRequested.substring(1);
}
File file = new File(WEB_ROOT, fileRequested); Path file = WEB_ROOT.resolve(fileRequested).toRealPath(LinkOption.NOFOLLOW_LINKS);
int fileLength = (int) file.length(); if (!file.startsWith(WEB_ROOT)) {
String content = getContentType(fileRequested); VerboseLogger.warn("Access to file outside root: " + file);
throw new NoSuchFileException(fileRequested);
}
int fileLength = (int)Files.size(file);
String contentType = getContentType(fileRequested);
byte[] fileData = readFileData(file);
// send HTTP Headers
dataOut.write(OK);
dataOut.write(HEADERS);
dataOut.write("Date: %s\r\n".formatted(Instant.now()).getBytes(StandardCharsets.UTF_8));
dataOut.write("Content-Type: %s\r\n".formatted(contentType).getBytes(StandardCharsets.UTF_8));
dataOut.write("Content-Length: %s\r\n".formatted(fileLength).getBytes(StandardCharsets.UTF_8));
dataOut.write(CRLF); // blank line between headers and content, very important !
if (method.equals("GET")) { // GET method so we return content if (method.equals("GET")) { // GET method so we return content
byte[] fileData = readFileData(file, fileLength);
// send HTTP Headers
VerboseLogger.info("HTTP/1.1 200 OK");
VerboseLogger.info("Server: Java HTTP Server from SSaurel : 1.0");
VerboseLogger.info("Date: " + new Date());
VerboseLogger.info("Content-type: " + content);
VerboseLogger.info("Content-length: " + fileLength);
VerboseLogger.info(""); // blank line between headers and content, very important !
dataOut.write(fileData, 0, fileLength); dataOut.write(fileData, 0, fileLength);
dataOut.flush(); dataOut.flush();
} }
VerboseLogger.info("File " + fileRequested + " of type " + content + " returned"); VerboseLogger.info("File " + fileRequested + " of type " + contentType + " returned");
} }
} catch (FileNotFoundException fnfe) { } catch (NoSuchFileException e) {
try { try {
fileNotFound(out, dataOut, fileRequested); fileNotFound(out, dataOut, fileRequested);
} catch (IOException ioe) { } catch (IOException ioe) {
@ -153,19 +181,8 @@ public class HTTPServer implements Runnable {
} }
private byte[] readFileData(File file, int fileLength) throws IOException { private byte[] readFileData(Path file) throws IOException {
FileInputStream fileIn = null; return Files.readAllBytes(file);
byte[] fileData = new byte[fileLength];
try {
fileIn = new FileInputStream(file);
fileIn.read(fileData);
} finally {
if (fileIn != null)
fileIn.close();
}
return fileData;
} }
// return supported MIME Types // return supported MIME Types
@ -177,17 +194,17 @@ public class HTTPServer implements Runnable {
} }
private void fileNotFound(PrintWriter out, OutputStream dataOut, String fileRequested) throws IOException { private void fileNotFound(PrintWriter out, OutputStream dataOut, String fileRequested) throws IOException {
File file = new File(WEB_ROOT, FILE_NOT_FOUND); Path file = WEB_ROOT.resolve(FILE_NOT_FOUND);
int fileLength = (int) file.length(); int fileLength = (int) Files.size(file);
String content = "text/html"; String contentType = "text/html";
byte[] fileData = readFileData(file, fileLength); byte[] fileData = readFileData(file);
VerboseLogger.error("HTTP/1.1 404 File Not Found"); dataOut.write(NOT_FOUND);
VerboseLogger.info("Server: Java HTTP Server from SSaurel : 1.0"); dataOut.write(HEADERS);
VerboseLogger.info("Date: " + new Date()); dataOut.write("Date: %s\r\n".formatted(Instant.now()).getBytes(StandardCharsets.UTF_8));
VerboseLogger.info("Content-type: " + content); dataOut.write("Content-Type: %s\r\n".formatted(contentType).getBytes(StandardCharsets.UTF_8));
VerboseLogger.info("Content-length: " + fileLength); dataOut.write("Content-Length: %s\r\n".formatted(fileLength).getBytes(StandardCharsets.UTF_8));
VerboseLogger.info(""); // blank line between headers and content, very important ! dataOut.write(CRLF); // blank line between headers and content, very important !
out.flush(); // flush character output stream buffer out.flush(); // flush character output stream buffer
dataOut.write(fileData, 0, fileLength); dataOut.write(fileData, 0, fileLength);