mirror of
https://github.com/JonasunderscoreJones/McWebserver.git
synced 2025-10-23 03:19:19 +02:00
Rewrite HTTPServer implementation
This commit is contained in:
parent
c5033ccfc0
commit
3f9d91b731
1 changed files with 81 additions and 64 deletions
|
@ -6,28 +6,50 @@ import me.jonasjones.mcwebserver.util.VerboseLogger;
|
|||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.ServerSocket;
|
||||
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;
|
||||
|
||||
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 FILE_NOT_FOUND = ModConfigs.WEB_FILE_404;
|
||||
static final String METHOD_NOT_SUPPORTED = ModConfigs.WEB_FILE_NOSUPPORT;
|
||||
// port to listen connection
|
||||
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
|
||||
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) {
|
||||
connect = c;
|
||||
|
@ -35,19 +57,20 @@ public class HTTPServer implements Runnable {
|
|||
|
||||
public static void main() {
|
||||
try {
|
||||
ServerSocket serverConnect = new ServerSocket(PORT);
|
||||
McWebserver.LOGGER.info("Server started.");
|
||||
McWebserver.LOGGER.info("Listening for connections on port : " + PORT);
|
||||
try (ServerSocket serverConnect = new ServerSocket(PORT)) {
|
||||
McWebserver.LOGGER.info("Server started.");
|
||||
McWebserver.LOGGER.info("Listening for connections on port : " + PORT);
|
||||
|
||||
// we listen until user halts server execution
|
||||
while (true) {
|
||||
HTTPServer myServer = new HTTPServer(serverConnect.accept());
|
||||
// we listen until user halts server execution
|
||||
while (true) {
|
||||
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
|
||||
Thread thread = new Thread(myServer);
|
||||
thread.start();
|
||||
// create dedicated thread to manage the client connection
|
||||
Thread thread = new Thread(myServer);
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
|
@ -82,21 +105,21 @@ public class HTTPServer implements Runnable {
|
|||
VerboseLogger.info("501 Not Implemented : " + method + " method.");
|
||||
|
||||
// we return the not supported file to the client
|
||||
File file = new File(WEB_ROOT, METHOD_NOT_SUPPORTED);
|
||||
int fileLength = (int) file.length();
|
||||
Path file = WEB_ROOT.resolve(METHOD_NOT_SUPPORTED);
|
||||
long fileLength = Files.size(file);
|
||||
String contentMimeType = "text/html";
|
||||
//read content to return to client
|
||||
byte[] fileData = readFileData(file, fileLength);
|
||||
byte[] fileData = readFileData(file);
|
||||
|
||||
// we send HTTP Headers with data to client
|
||||
VerboseLogger.info("HTTP/1.1 501 Not Implemented");
|
||||
VerboseLogger.info("Server: Java HTTP Server from SSaurel : 1.0"); //hopefully enough credits
|
||||
VerboseLogger.info("Date: " + new Date());
|
||||
VerboseLogger.info("Content-type: " + contentMimeType);
|
||||
VerboseLogger.info("Content-length: " + fileLength);
|
||||
VerboseLogger.info(""); // blank line between headers and content, very important !
|
||||
dataOut.write(NOT_IMPLEMENTED);
|
||||
dataOut.write(HEADERS); //hopefully enough credits
|
||||
dataOut.write("Date: %s\r\n".formatted(Instant.now()).getBytes(StandardCharsets.UTF_8));
|
||||
dataOut.write("Content-Type: %s\r\n".formatted(contentMimeType).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 !
|
||||
// file
|
||||
dataOut.write(fileData, 0, fileLength);
|
||||
dataOut.write(fileData, 0, fileData.length);
|
||||
dataOut.flush();
|
||||
|
||||
} else {
|
||||
|
@ -104,31 +127,36 @@ public class HTTPServer implements Runnable {
|
|||
if (fileRequested.endsWith("/")) {
|
||||
fileRequested += DEFAULT_FILE;
|
||||
}
|
||||
if (fileRequested.startsWith("/")) {
|
||||
fileRequested = fileRequested.substring(1);
|
||||
}
|
||||
|
||||
File file = new File(WEB_ROOT, fileRequested);
|
||||
int fileLength = (int) file.length();
|
||||
String content = getContentType(fileRequested);
|
||||
Path file = WEB_ROOT.resolve(fileRequested).toRealPath(LinkOption.NOFOLLOW_LINKS);
|
||||
if (!file.startsWith(WEB_ROOT)) {
|
||||
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
|
||||
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.flush();
|
||||
}
|
||||
|
||||
VerboseLogger.info("File " + fileRequested + " of type " + content + " returned");
|
||||
VerboseLogger.info("File " + fileRequested + " of type " + contentType + " returned");
|
||||
|
||||
}
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
} catch (NoSuchFileException e) {
|
||||
try {
|
||||
fileNotFound(out, dataOut, fileRequested);
|
||||
} catch (IOException ioe) {
|
||||
|
@ -153,19 +181,8 @@ public class HTTPServer implements Runnable {
|
|||
|
||||
}
|
||||
|
||||
private byte[] readFileData(File file, int fileLength) throws IOException {
|
||||
FileInputStream fileIn = null;
|
||||
byte[] fileData = new byte[fileLength];
|
||||
|
||||
try {
|
||||
fileIn = new FileInputStream(file);
|
||||
fileIn.read(fileData);
|
||||
} finally {
|
||||
if (fileIn != null)
|
||||
fileIn.close();
|
||||
}
|
||||
|
||||
return fileData;
|
||||
private byte[] readFileData(Path file) throws IOException {
|
||||
return Files.readAllBytes(file);
|
||||
}
|
||||
|
||||
// return supported MIME Types
|
||||
|
@ -177,17 +194,17 @@ public class HTTPServer implements Runnable {
|
|||
}
|
||||
|
||||
private void fileNotFound(PrintWriter out, OutputStream dataOut, String fileRequested) throws IOException {
|
||||
File file = new File(WEB_ROOT, FILE_NOT_FOUND);
|
||||
int fileLength = (int) file.length();
|
||||
String content = "text/html";
|
||||
byte[] fileData = readFileData(file, fileLength);
|
||||
Path file = WEB_ROOT.resolve(FILE_NOT_FOUND);
|
||||
int fileLength = (int) Files.size(file);
|
||||
String contentType = "text/html";
|
||||
byte[] fileData = readFileData(file);
|
||||
|
||||
VerboseLogger.error("HTTP/1.1 404 File Not Found");
|
||||
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(NOT_FOUND);
|
||||
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 !
|
||||
out.flush(); // flush character output stream buffer
|
||||
|
||||
dataOut.write(fileData, 0, fileLength);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue