KingRainbow44 15b1718052
Separate the dispatch and game servers (pt. 2)
this commit fixes the gacha page
2023-05-15 02:37:35 -04:00

229 lines
8.5 KiB
Java

package emu.grasscutter.server.http;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerDebugMode;
import emu.grasscutter.utils.FileUtils;
import io.javalin.Javalin;
import io.javalin.http.ContentType;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.Language.translate;
/**
* Manages all HTTP-related classes.
* (including dispatch, announcements, gacha, etc.)
*/
public final class HttpServer {
private final Javalin javalin;
/**
* Configures the Javalin application.
*/
public HttpServer() {
// Check if we are in game only mode.
if (Grasscutter.getRunMode() == Grasscutter.ServerRunMode.GAME_ONLY) {
this.javalin = null;
return;
}
this.javalin = Javalin.create(config -> {
// Set the Javalin HTTP server.
config.jetty.server(HttpServer::createServer);
// Configure encryption/HTTPS/SSL.
if (HTTP_ENCRYPTION.useEncryption)
config.plugins.enableSslRedirects();
// Configure HTTP policies.
if (HTTP_POLICIES.cors.enabled) {
var allowedOrigins = HTTP_POLICIES.cors.allowedOrigins;
config.plugins.enableCors(cors -> cors.add(corsConfig -> {
if (allowedOrigins.length > 0)
corsConfig.allowHost(Arrays.toString(allowedOrigins));
else corsConfig.anyHost();
}));
}
// Configure debug logging.
if (DISPATCH_INFO.logRequests == ServerDebugMode.ALL)
config.plugins.enableDevLogging();
// Static files should be added like this https://javalin.io/documentation#static-files
});
this.javalin.exception(Exception.class, (exception, ctx) -> {
ctx.status(500).result("Internal server error. %s"
.formatted(exception.getMessage()));
Grasscutter.getLogger().debug("Exception thrown: " +
exception.getMessage(), exception);
});
}
/**
* Creates an HTTP(S) server.
*
* @return A server instance.
*/
@SuppressWarnings("resource")
private static Server createServer() {
Server server = new Server();
ServerConnector serverConnector
= new ServerConnector(server);
if (HTTP_ENCRYPTION.useEncryption) {
var sslContextFactory = new SslContextFactory.Server();
var keystoreFile = new File(HTTP_ENCRYPTION.keystore);
if (!keystoreFile.exists()) {
HTTP_ENCRYPTION.useEncryption = false;
HTTP_ENCRYPTION.useInRouting = false;
Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.no_keystore_error"));
} else try {
sslContextFactory.setKeyStorePath(keystoreFile.getPath());
sslContextFactory.setKeyStorePassword(HTTP_ENCRYPTION.keystorePassword);
} catch (Exception ignored) {
Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.password_error"));
try {
sslContextFactory.setKeyStorePath(keystoreFile.getPath());
sslContextFactory.setKeyStorePassword("123456");
Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.default_password"));
} catch (Exception exception) {
Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.general_error"), exception);
}
} finally {
serverConnector = new ServerConnector(server, sslContextFactory);
}
}
serverConnector.setPort(HTTP_INFO.bindPort);
server.setConnectors(new ServerConnector[]{serverConnector});
return server;
}
/**
* Returns the handle for the Express application.
*
* @return A Javalin instance.
*/
public Javalin getHandle() {
return this.javalin;
}
/**
* Initializes the provided class.
*
* @param router The router class.
* @return Method chaining.
*/
@SuppressWarnings("UnusedReturnValue")
public HttpServer addRouter(Class<? extends Router> router, Object... args) {
// Get all constructor parameters.
var types = new Class<?>[args.length];
for (var argument : args)
types[args.length - 1] = argument.getClass();
try { // Create a router instance & apply routes.
var constructor = router.getDeclaredConstructor(types); // Get the constructor.
var routerInstance = constructor.newInstance(args); // Create instance.
routerInstance.applyRoutes(this.javalin); // Apply routes.
} catch (Exception exception) {
Grasscutter.getLogger().warn(translate("messages.dispatch.router_error"), exception);
}
return this;
}
/**
* Starts listening on the HTTP server.
*
* @throws UnsupportedEncodingException
*/
public void start() throws UnsupportedEncodingException {
// Attempt to start the HTTP server.
if (HTTP_INFO.bindAddress.equals("")) {
this.javalin.start(HTTP_INFO.bindPort);
} else {
this.javalin.start(HTTP_INFO.bindAddress, HTTP_INFO.bindPort);
}
// Log bind information.
Grasscutter.getLogger().info(translate("messages.dispatch.address_bind", HTTP_INFO.accessAddress, this.javalin.port()));
}
/**
* Handles the '/' (index) endpoint on the Express application.
*/
public static class DefaultRequestRouter implements Router {
@Override
public void applyRoutes(Javalin javalin) {
javalin.get("/", ctx -> {
// Send file
File file = new File(HTTP_STATIC_FILES.indexFile);
if (!file.exists()) {
ctx.contentType(ContentType.TEXT_HTML);
ctx.result("""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
</head>
<body>%s</body>
</html>
""".formatted(translate("messages.status.welcome")));
} else {
var filePath = file.getPath();
ContentType fromExtension = ContentType.getContentTypeByExtension(filePath.substring(filePath.lastIndexOf(".") + 1));
ctx.contentType(fromExtension != null ? fromExtension : ContentType.TEXT_HTML);
ctx.result(FileUtils.read(filePath));
}
});
}
}
/**
* Handles unhandled endpoints on the Express application.
*/
public static class UnhandledRequestRouter implements Router {
@Override
public void applyRoutes(Javalin javalin) {
javalin.error(404, ctx -> {
// Error log
if (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING)
Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", ctx.method(), ctx.url()));
// Send file
File file = new File(HTTP_STATIC_FILES.errorFile);
if (!file.exists()) {
ctx.contentType(ContentType.TEXT_HTML);
ctx.result("""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
</head>
<body>
<img src="https://http.cat/404" />
</body>
</html>
""");
} else {
var filePath = file.getPath();
ContentType fromExtension = ContentType.getContentTypeByExtension(filePath.substring(filePath.lastIndexOf(".") + 1));
ctx.contentType(fromExtension != null ? fromExtension : ContentType.TEXT_HTML);
ctx.result(FileUtils.read(filePath));
}
});
}
}
}