mirror of
https://github.com/JonasunderscoreJones/McWebserver.git
synced 2025-10-23 03:19:19 +02:00
Merge pull request #1 from apple502j/rewrite-server
Rewrite HTTPServer implementation
This commit is contained in:
commit
4225c9f139
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.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,7 +57,7 @@ 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);
|
||||||
|
|
||||||
|
@ -43,12 +65,13 @@ public class HTTPServer implements Runnable {
|
||||||
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) {
|
||||||
VerboseLogger.error("Server Connection error : " + e.getMessage());
|
VerboseLogger.error("Server Connection error : " + e.getMessage());
|
||||||
|
@ -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);
|
||||||
if (method.equals("GET")) { // GET method so we return content
|
}
|
||||||
byte[] fileData = readFileData(file, fileLength);
|
int fileLength = (int)Files.size(file);
|
||||||
|
String contentType = getContentType(fileRequested);
|
||||||
|
byte[] fileData = readFileData(file);
|
||||||
|
|
||||||
// send HTTP Headers
|
// send HTTP Headers
|
||||||
VerboseLogger.info("HTTP/1.1 200 OK");
|
dataOut.write(OK);
|
||||||
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 !
|
||||||
|
if (method.equals("GET")) { // GET method so we return content
|
||||||
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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue