Commit b5bed6ce authored by Benj's avatar Benj Committed by Melledy
Browse files

Update HttpServer & AuthenticationSystem to use Javalin

parent bf960622
......@@ -94,6 +94,7 @@ dependencies {
implementation group: 'com.github.davidmoten', name : 'rtree-multi', version: '0.1'
implementation group: 'io.javalin', name: 'javalin', version: '4.6.4'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.3'
protobuf files('proto/')
......
......@@ -2,8 +2,7 @@ package emu.grasscutter.auth;
import emu.grasscutter.game.Account;
import emu.grasscutter.server.http.objects.*;
import express.http.Request;
import express.http.Response;
import io.javalin.http.Context;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
......@@ -15,7 +14,7 @@ import javax.annotation.Nullable;
* Can be changed by plugins.
*/
public interface AuthenticationSystem {
/**
* Called when a user requests to make an account.
* @param username The provided username.
......@@ -71,9 +70,8 @@ public interface AuthenticationSystem {
*/
@Builder @AllArgsConstructor @Getter
class AuthenticationRequest {
private final Request request;
@Nullable private final Response response;
private final Context context;
@Nullable private final LoginAccountRequestJson passwordRequest;
@Nullable private final LoginTokenRequestJson tokenRequest;
@Nullable private final ComboTokenReqJson sessionKeyRequest;
......@@ -82,53 +80,51 @@ public interface AuthenticationSystem {
/**
* Generates an authentication request from a {@link LoginAccountRequestJson} object.
* @param request The Express request.
* @param ctx The Javalin context.
* @param jsonData The JSON data.
* @return An authentication request.
*/
static AuthenticationRequest fromPasswordRequest(Request request, LoginAccountRequestJson jsonData) {
static AuthenticationRequest fromPasswordRequest(Context ctx, LoginAccountRequestJson jsonData) {
return AuthenticationRequest.builder()
.request(request)
.context(ctx)
.passwordRequest(jsonData)
.build();
}
/**
* Generates an authentication request from a {@link LoginTokenRequestJson} object.
* @param request The Express request.
* @param ctx The Javalin context.
* @param jsonData The JSON data.
* @return An authentication request.
*/
static AuthenticationRequest fromTokenRequest(Request request, LoginTokenRequestJson jsonData) {
static AuthenticationRequest fromTokenRequest(Context ctx, LoginTokenRequestJson jsonData) {
return AuthenticationRequest.builder()
.request(request)
.context(ctx)
.tokenRequest(jsonData)
.build();
}
/**
* Generates an authentication request from a {@link ComboTokenReqJson} object.
* @param request The Express request.
* @param ctx The Javalin context.
* @param jsonData The JSON data.
* @return An authentication request.
*/
static AuthenticationRequest fromComboTokenRequest(Request request, ComboTokenReqJson jsonData,
static AuthenticationRequest fromComboTokenRequest(Context ctx, ComboTokenReqJson jsonData,
ComboTokenReqJson.LoginTokenData tokenData) {
return AuthenticationRequest.builder()
.request(request)
.context(ctx)
.sessionKeyRequest(jsonData)
.sessionKeyData(tokenData)
.build();
}
/**
* Generates an authentication request from a {@link Response} object.
* @param request The Express request.
* @param response the Express response.
* Generates an authentication request from a {@link Context} object.
* @param ctx The Javalin context.
* @return An authentication request.
*/
static AuthenticationRequest fromExternalRequest(Request request, Response response) {
return AuthenticationRequest.builder().request(request)
.response(response).build();
static AuthenticationRequest fromExternalRequest(Context ctx) {
return AuthenticationRequest.builder().context(ctx).build();
}
}
......@@ -36,7 +36,7 @@ public final class DefaultAuthenticators {
int playerCount = Grasscutter.getGameServer().getPlayers().size();
boolean successfulLogin = false;
String address = request.getRequest().ip();
String address = request.getContext().ip();
String responseMessage = translate("messages.dispatch.account.username_error");
String loggerMessage = "";
......@@ -99,7 +99,7 @@ public final class DefaultAuthenticators {
int playerCount = Grasscutter.getGameServer().getPlayers().size();
boolean successfulLogin = false;
String address = request.getRequest().ip();
String address = request.getContext().ip();
String responseMessage = translate("messages.dispatch.account.username_error");
String loggerMessage = "";
String decryptedPassword = "";
......@@ -205,7 +205,7 @@ public final class DefaultAuthenticators {
assert requestData != null;
boolean successfulLogin;
String address = request.getRequest().ip();
String address = request.getContext().ip();
String loggerMessage;
int playerCount = Grasscutter.getGameServer().getPlayers().size();
......@@ -263,7 +263,7 @@ public final class DefaultAuthenticators {
assert loginData != null;
boolean successfulLogin;
String address = request.getRequest().ip();
String address = request.getContext().ip();
String loggerMessage;
int playerCount = Grasscutter.getGameServer().getPlayers().size();
......@@ -309,43 +309,37 @@ public final class DefaultAuthenticators {
public static class ExternalAuthentication implements ExternalAuthenticator {
@Override
public void handleLogin(AuthenticationRequest request) {
assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method.");
request.getContext().result("Authentication is not available with the default authentication method.");
}
@Override
public void handleAccountCreation(AuthenticationRequest request) {
assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method.");
request.getContext().result("Authentication is not available with the default authentication method.");
}
@Override
public void handlePasswordReset(AuthenticationRequest request) {
assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method.");
request.getContext().result("Authentication is not available with the default authentication method.");
}
}
/**
* Handles authentication requests from OAuth sources.
* Handles authentication requests from OAuth sources.Zenlith
*/
public static class OAuthAuthentication implements OAuthAuthenticator {
@Override
public void handleLogin(AuthenticationRequest request) {
assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method.");
request.getContext().result("Authentication is not available with the default authentication method.");
}
@Override
public void handleRedirection(AuthenticationRequest request, ClientType type) {
assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method.");
request.getContext().result("Authentication is not available with the default authentication method.");
}
@Override
public void handleTokenProcess(AuthenticationRequest request) {
assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method.");
request.getContext().result("Authentication is not available with the default authentication method.");
}
}
}
......@@ -3,9 +3,9 @@ package emu.grasscutter.server.http;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerDebugMode;
import emu.grasscutter.utils.FileUtils;
import express.Express;
import express.http.MediaType;
import emu.grasscutter.utils.HttpUtils;
import io.javalin.Javalin;
import io.javalin.core.util.JavalinLogger;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
......@@ -21,14 +21,14 @@ import static emu.grasscutter.utils.Language.translate;
* (including dispatch, announcements, gacha, etc.)
*/
public final class HttpServer {
private final Express express;
private final Javalin javalin;
/**
* Configures the Express application.
* Configures the Javalin application.
*/
public HttpServer() {
this.express = new Express(config -> {
// Set the Express HTTP server.
this.javalin = Javalin.create(config -> {
// Set the Javalin HTTP server.
config.server(HttpServer::createServer);
// Configure encryption/HTTPS/SSL.
......@@ -46,8 +46,7 @@ public final class HttpServer {
if (DISPATCH_INFO.logRequests == ServerDebugMode.ALL)
config.enableDevLogging();
// Disable compression on static files.
config.precompressStaticFiles = false;
// Static files should be added like this https://javalin.io/documentation#static-files
});
}
......@@ -100,7 +99,7 @@ public final class HttpServer {
* @return A Javalin instance.
*/
public Javalin getHandle() {
return this.express.raw();
return this.javalin;
}
/**
......@@ -118,7 +117,7 @@ public final class HttpServer {
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.express, this.getHandle()); // Apply routes.
routerInstance.applyRoutes(this.javalin); // Apply routes.
} catch (Exception exception) {
Grasscutter.getLogger().warn(translate("messages.dispatch.router_error"), exception);
} return this;
......@@ -131,24 +130,24 @@ public final class HttpServer {
public void start() throws UnsupportedEncodingException {
// Attempt to start the HTTP server.
if (HTTP_INFO.bindAddress.equals("")) {
this.express.listen(HTTP_INFO.bindPort);
this.javalin.start(HTTP_INFO.bindPort);
}else {
this.express.listen(HTTP_INFO.bindAddress, HTTP_INFO.bindPort);
this.javalin.start(HTTP_INFO.bindAddress, HTTP_INFO.bindPort);
}
// Log bind information.
Grasscutter.getLogger().info(translate("messages.dispatch.address_bind", HTTP_INFO.accessAddress, this.express.raw().port()));
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(Express express, Javalin handle) {
express.get("/", (request, response) -> {
@Override public void applyRoutes(Javalin javalin) {
javalin.get("/", ctx -> {
File file = new File(HTTP_STATIC_FILES.indexFile);
if (!file.exists())
response.send("""
ctx.result("""
<!DOCTYPE html>
<html>
<head>
......@@ -159,9 +158,8 @@ public final class HttpServer {
""".formatted(translate("messages.status.welcome")));
else {
final var filePath = file.getPath();
final MediaType fromExtension = MediaType.getByExtension(filePath.substring(filePath.lastIndexOf(".") + 1));
response.type((fromExtension != null) ? fromExtension.getMIME() : "text/plain")
.send(FileUtils.read(filePath));
final HttpUtils.MediaType fromExtension = HttpUtils.MediaType.getByExtension(filePath.substring(filePath.lastIndexOf(".") + 1));
ctx.contentType((fromExtension != null) ? fromExtension.getMIME() : "text/plain").result(FileUtils.read(filePath));
}
});
}
......@@ -171,8 +169,8 @@ public final class HttpServer {
* Handles unhandled endpoints on the Express application.
*/
public static class UnhandledRequestRouter implements Router {
@Override public void applyRoutes(Express express, Javalin handle) {
handle.error(404, context -> {
@Override public void applyRoutes(Javalin javalin) {
javalin.error(404, context -> {
if (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING)
Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", context.method(), context.url()));
context.contentType("text/html");
......@@ -193,7 +191,7 @@ public final class HttpServer {
""");
else {
final var filePath = file.getPath();
final MediaType fromExtension = MediaType.getByExtension(filePath.substring(filePath.lastIndexOf(".") + 1));
final HttpUtils.MediaType fromExtension = HttpUtils.MediaType.getByExtension(filePath.substring(filePath.lastIndexOf(".") + 1));
context.contentType((fromExtension != null) ? fromExtension.getMIME() : "text/plain")
.result(FileUtils.read(filePath));
}
......
package emu.grasscutter.server.http;
import express.Express;
import io.javalin.Javalin;
/**
* Defines routes for an {@link Express} instance.
* Defines routes for an {@link Javalin} instance.
*/
public interface Router {
/**
* Called when the router is initialized by Express.
* @param express An Express instance.
* @param javalin A Javalin instance.
*/
void applyRoutes(Express express, Javalin handle);
void applyRoutes(Javalin javalin);
}
......@@ -2,16 +2,13 @@ package emu.grasscutter.server.http.dispatch;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.auth.OAuthAuthenticator;
import emu.grasscutter.auth.OAuthAuthenticator.ClientType;
import emu.grasscutter.server.http.Router;
import emu.grasscutter.server.http.objects.*;
import emu.grasscutter.server.http.objects.ComboTokenReqJson.LoginTokenData;
import emu.grasscutter.utils.JsonUtils;
import express.Express;
import express.http.Request;
import express.http.Response;
import io.javalin.Javalin;
import io.javalin.http.Context;
import static emu.grasscutter.utils.Language.translate;
......@@ -19,40 +16,40 @@ import static emu.grasscutter.utils.Language.translate;
* Handles requests related to authentication. (aka dispatch)
*/
public final class DispatchHandler implements Router {
@Override public void applyRoutes(Express express, Javalin handle) {
@Override public void applyRoutes(Javalin javalin) {
// Username & Password login (from client).
express.post("/hk4e_global/mdk/shield/api/login", DispatchHandler::clientLogin);
javalin.post("/hk4e_global/mdk/shield/api/login", DispatchHandler::clientLogin);
// Cached token login (from registry).
express.post("/hk4e_global/mdk/shield/api/verify", DispatchHandler::tokenLogin);
javalin.post("/hk4e_global/mdk/shield/api/verify", DispatchHandler::tokenLogin);
// Combo token login (from session key).
express.post("/hk4e_global/combo/granter/login/v2/login", DispatchHandler::sessionKeyLogin);
javalin.post("/hk4e_global/combo/granter/login/v2/login", DispatchHandler::sessionKeyLogin);
// External login (from other clients).
express.get("/authentication/type", (request, response) -> response.send(Grasscutter.getAuthenticationSystem().getClass().getSimpleName()));
express.post("/authentication/login", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator()
.handleLogin(AuthenticationSystem.fromExternalRequest(request, response)));
express.post("/authentication/register", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator()
.handleAccountCreation(AuthenticationSystem.fromExternalRequest(request, response)));
express.post("/authentication/change_password", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator()
.handlePasswordReset(AuthenticationSystem.fromExternalRequest(request, response)));
javalin.get("/authentication/type", ctx -> ctx.result(Grasscutter.getAuthenticationSystem().getClass().getSimpleName()));
javalin.post("/authentication/login", ctx -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator()
.handleLogin(AuthenticationSystem.fromExternalRequest(ctx)));
javalin.post("/authentication/register", ctx -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator()
.handleAccountCreation(AuthenticationSystem.fromExternalRequest(ctx)));
javalin.post("/authentication/change_password", ctx -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator()
.handlePasswordReset(AuthenticationSystem.fromExternalRequest(ctx)));
// External login (from OAuth2).
express.post("/hk4e_global/mdk/shield/api/loginByThirdparty", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleLogin(AuthenticationSystem.fromExternalRequest(request, response)));
express.get("/authentication/openid/redirect", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleTokenProcess(AuthenticationSystem.fromExternalRequest(request, response)));
express.get("/Api/twitter_login", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleRedirection(AuthenticationSystem.fromExternalRequest(request, response), ClientType.DESKTOP));
express.get("/sdkTwitterLogin.html", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleRedirection(AuthenticationSystem.fromExternalRequest(request, response), ClientType.MOBILE));
javalin.post("/hk4e_global/mdk/shield/api/loginByThirdparty", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleLogin(AuthenticationSystem.fromExternalRequest(ctx)));
javalin.get("/authentication/openid/redirect", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleTokenProcess(AuthenticationSystem.fromExternalRequest(ctx)));
javalin.get("/Api/twitter_login", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleRedirection(AuthenticationSystem.fromExternalRequest(ctx), ClientType.DESKTOP));
javalin.get("/sdkTwitterLogin.html", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleRedirection(AuthenticationSystem.fromExternalRequest(ctx), ClientType.MOBILE));
}
/**
* @route /hk4e_global/mdk/shield/api/login
*/
private static void clientLogin(Request request, Response response) {
private static void clientLogin(Context ctx) {
// Parse body data.
String rawBodyData = request.ctx().body();
String rawBodyData = ctx.body();
var bodyData = JsonUtils.decode(rawBodyData, LoginAccountRequestJson.class);
// Validate body data.
......@@ -62,20 +59,20 @@ public final class DispatchHandler implements Router {
// Pass data to authentication handler.
var responseData = Grasscutter.getAuthenticationSystem()
.getPasswordAuthenticator()
.authenticate(AuthenticationSystem.fromPasswordRequest(request, bodyData));
.authenticate(AuthenticationSystem.fromPasswordRequest(ctx, bodyData));
// Send response.
response.send(responseData);
ctx.json(responseData);
// Log to console.
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", request.ip()));
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", ctx.ip()));
}
/**
* @route /hk4e_global/mdk/shield/api/verify
*/
private static void tokenLogin(Request request, Response response) {
private static void tokenLogin(Context ctx) {
// Parse body data.
String rawBodyData = request.ctx().body();
String rawBodyData = ctx.body();
var bodyData = JsonUtils.decode(rawBodyData, LoginTokenRequestJson.class);
// Validate body data.
......@@ -85,20 +82,20 @@ public final class DispatchHandler implements Router {
// Pass data to authentication handler.
var responseData = Grasscutter.getAuthenticationSystem()
.getTokenAuthenticator()
.authenticate(AuthenticationSystem.fromTokenRequest(request, bodyData));
.authenticate(AuthenticationSystem.fromTokenRequest(ctx, bodyData));
// Send response.
response.send(responseData);
ctx.json(responseData);
// Log to console.
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", request.ip()));
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", ctx.ip()));
}
/**
* @route /hk4e_global/combo/granter/login/v2/login
*/
private static void sessionKeyLogin(Request request, Response response) {
private static void sessionKeyLogin(Context ctx) {
// Parse body data.
String rawBodyData = request.ctx().body();
String rawBodyData = ctx.body();
var bodyData = JsonUtils.decode(rawBodyData, ComboTokenReqJson.class);
// Validate body data.
......@@ -111,11 +108,11 @@ public final class DispatchHandler implements Router {
// Pass data to authentication handler.
var responseData = Grasscutter.getAuthenticationSystem()
.getSessionKeyAuthenticator()
.authenticate(AuthenticationSystem.fromComboTokenRequest(request, bodyData, tokenData));
.authenticate(AuthenticationSystem.fromComboTokenRequest(ctx, bodyData, tokenData));
// Send response.
response.send(responseData);
ctx.json(responseData);
// Log to console.
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", request.ip()));
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", ctx.ip()));
}
}
......@@ -11,20 +11,12 @@ import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent;
import emu.grasscutter.server.http.Router;
import emu.grasscutter.server.http.objects.QueryCurRegionRspJson;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import express.Express;
import express.http.Request;
import express.http.Response;
import io.javalin.Javalin;
import io.javalin.http.Context;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.security.Signature;
......@@ -107,36 +99,36 @@ public final class RegionHandler implements Router {
regionListResponse = Utils.base64Encode(updatedRegionList.toByteString().toByteArray());
}
@Override public void applyRoutes(Express express, Javalin handle) {
express.get("/query_region_list", RegionHandler::queryRegionList);
express.get("/query_cur_region/:region", RegionHandler::queryCurrentRegion );
@Override public void applyRoutes(Javalin javalin) {
javalin.get("/query_region_list", RegionHandler::queryRegionList);
javalin.get("/query_cur_region/{region}", RegionHandler::queryCurrentRegion );
}
/**
* @route /query_region_list
*/
private static void queryRegionList(Request request, Response response) {
private static void queryRegionList(Context ctx) {
// Invoke event.
QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse); event.call();
// Respond with event result.
response.send(event.getRegionList());
ctx.result(event.getRegionList());
// Log to console.
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", request.ip()));
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", ctx.ip()));
}
/**
* @route /query_cur_region/:region
* @route /query_cur_region/{region}
*/
private static void queryCurrentRegion(Request request, Response response) {
private static void queryCurrentRegion(Context ctx) {
// Get region to query.
String regionName = request.params("region");
String versionName = request.query("version");
String regionName = ctx.pathParam("region");
String versionName = ctx.queryParam("version");
var region = regions.get(regionName);
// Get region data.
String regionData = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw==";
if (request.query().values().size() > 0) {
if (ctx.queryParamMap().values().size() > 0) {
if (region != null)
regionData = region.getBase64();
}
......@@ -150,18 +142,18 @@ public final class RegionHandler implements Router {
try {
QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call();
if (request.query("dispatchSeed") == null) {
if (ctx.queryParam("dispatchSeed") == null) {
// More love for UA Patch players
var rsp = new QueryCurRegionRspJson();
rsp.content = event.getRegionInfo();
rsp.sign = "TW9yZSBsb3ZlIGZvciBVQSBQYXRjaCBwbGF5ZXJz";
response.send(rsp);
ctx.json(rsp);
return;
}
String key_id = request.query("key_id");
String key_id = ctx.queryParam("key_id");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, key_id.equals("3") ? Crypto.CUR_OS_ENCRYPT_KEY : Crypto.CUR_CN_ENCRYPT_KEY);
var regionInfo = Utils.base64Decode(event.getRegionInfo());
......@@ -189,7 +181,7 @@ public final class RegionHandler implements Router {
rsp.content = Utils.base64Encode(encryptedRegionInfoStream.toByteArray());
rsp.sign = Utils.base64Encode(privateSignature.sign());
response.send(rsp);
ctx.json(rsp);
}
catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while handling query_cur_region.", e);
......@@ -199,10 +191,10 @@ public final class RegionHandler implements Router {
// Invoke event.
QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call();
// Respond with event result.
response.send(event.getRegionInfo());
ctx.result(event.getRegionInfo());
}
// Log to console.
Grasscutter.getLogger().info(String.format("Client %s request: query_cur_region/%s", request.ip(), regionName));
Grasscutter.getLogger().info(String.format("Client %s request: query_cur_region/%s", ctx.ip(), regionName));
}
/**
......
package emu.grasscutter.server.http.documentation;
import express.http.Request;
import express.http.Response;
import io.javalin.http.Context;
interface DocumentationHandler {
void handle(Request request, Response response);
void handle(Context ctx);
}
package emu.grasscutter.server.http.documentation;
import emu.grasscutter.server.http.Router;
import express.Express;
import io.javalin.Javalin;
import io.javalin.http.Context;
public final class DocumentationServerHandler implements Router {
@Override
public void applyRoutes(Express express, Javalin handle) {
public void applyRoutes(Javalin javalin) {
final RootRequestHandler root = new RootRequestHandler();
final HandbookRequestHandler handbook = new HandbookRequestHandler();
final GachaMappingRequestHandler gachaMapping = new GachaMappingRequestHandler();
express.get("/documentation/handbook", handbook::handle);
express.get("/documentation/gachamapping", gachaMapping::handle);
express.get("/documentation", root::handle);
// TODO: Removal
// TODO: Forward /documentation requests to https://grasscutter.io/wiki
javalin.get("/documentation/handbook", handbook::handle);
javalin.get("/documentation/gachamapping", gachaMapping::handle);
javalin.get("/documentation", root::handle);
}
}
package emu.grasscutter.server.http.documentation;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.HttpUtils;
import emu.grasscutter.utils.Language;
import express.http.Request;
import express.http.Response;
import io.javalin.http.Context;
import static emu.grasscutter.config.Configuration.DOCUMENT_LANGUAGE;
......@@ -17,10 +17,8 @@ final class GachaMappingRequestHandler implements DocumentationHandler {
}
@Override
public void handle(Request request, Response response) {
public void handle(Context ctx) {
final int langIdx = Language.TextStrings.MAP_LANGUAGES.getOrDefault(DOCUMENT_LANGUAGE, 0); // TODO: This should really be based off the client language somehow
response.set("Content-Type", "application/json")
.ctx()
.result(gachaJsons.get(langIdx));
ctx.contentType(HttpUtils.MediaType._json.getMIME()).result(gachaJsons.get(langIdx));
}
}
......@@ -10,10 +10,10 @@ import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.data.excels.SceneData;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.HttpUtils;
import emu.grasscutter.utils.Language;
import emu.grasscutter.utils.Utils;
import express.http.Request;
import express.http.Response;
import io.javalin.http.Context;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.io.File;
import java.nio.charset.StandardCharsets;
......@@ -36,12 +36,13 @@ final class HandbookRequestHandler implements DocumentationHandler {
}
@Override
public void handle(Request request, Response response) {
public void handle(Context ctx) {
final int langIdx = Language.TextStrings.MAP_LANGUAGES.getOrDefault(DOCUMENT_LANGUAGE, 0); // TODO: This should really be based off the client language somehow
if (template == null) {
response.status(500);
ctx.status(500);
} else {
response.send(handbookHtmls.get(langIdx));
ctx.contentType(HttpUtils.MediaType._html.getMIME());
ctx.result(handbookHtmls.get(langIdx));
}
}
......
......@@ -4,11 +4,11 @@ import static emu.grasscutter.config.Configuration.DATA;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.HttpUtils;
import emu.grasscutter.utils.Utils;
import express.http.Request;
import express.http.Response;
import io.javalin.http.Context;
import java.io.File;
import java.nio.charset.StandardCharsets;
......@@ -27,15 +27,16 @@ final class RootRequestHandler implements DocumentationHandler {
}
@Override
public void handle(Request request, Response response) {
public void handle(Context ctx) {
if (template == null) {
response.status(500);
ctx.status(500);
return;
}
String content = template.replace("{{TITLE}}", translate("documentation.index.title"))
.replace("{{ITEM_HANDBOOK}}", translate("documentation.index.handbook"))
.replace("{{ITEM_GACHA_MAPPING}}", translate("documentation.index.gacha_mapping"));
response.send(content);
ctx.contentType(HttpUtils.MediaType._html.getMIME());
ctx.result(content);
}
}
......@@ -5,44 +5,39 @@ import emu.grasscutter.data.DataLoader;
import emu.grasscutter.server.http.objects.HttpJsonResponse;
import emu.grasscutter.server.http.Router;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.HttpUtils;
import emu.grasscutter.utils.Utils;
import express.Express;
import express.http.MediaType;
import express.http.Request;
import express.http.Response;
import io.javalin.Javalin;
import io.javalin.http.Context;
import static emu.grasscutter.config.Configuration.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* Handles requests related to the announcements page.
*/
public final class AnnouncementsHandler implements Router {
@Override public void applyRoutes(Express express, Javalin handle) {
@Override public void applyRoutes(Javalin javalin) {
// hk4e-api-os.hoyoverse.com
express.all("/common/hk4e_global/announcement/api/getAlertPic", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}"));
HttpUtils.allRoutes(javalin, "/common/hk4e_global/announcement/api/getAlertPic", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}"));
// hk4e-api-os.hoyoverse.com
express.all("/common/hk4e_global/announcement/api/getAlertAnn", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}"));
HttpUtils.allRoutes(javalin,"/common/hk4e_global/announcement/api/getAlertAnn", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}"));
// hk4e-api-os.hoyoverse.com
express.all("/common/hk4e_global/announcement/api/getAnnList", AnnouncementsHandler::getAnnouncement);
HttpUtils.allRoutes(javalin,"/common/hk4e_global/announcement/api/getAnnList", AnnouncementsHandler::getAnnouncement);
// hk4e-api-os-static.hoyoverse.com
express.all("/common/hk4e_global/announcement/api/getAnnContent", AnnouncementsHandler::getAnnouncement);
HttpUtils.allRoutes(javalin,"/common/hk4e_global/announcement/api/getAnnContent", AnnouncementsHandler::getAnnouncement);
// hk4e-sdk-os.hoyoverse.com
express.all("/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}"));
HttpUtils.allRoutes(javalin,"/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}"));
express.get("/hk4e/announcement/*", AnnouncementsHandler::getPageResources);
javalin.get("/hk4e/announcement/*", AnnouncementsHandler::getPageResources);
}
private static void getAnnouncement(Request request, Response response) {
private static void getAnnouncement(Context ctx) {
String data = "";
if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnContent")) {
if (Objects.equals(ctx.endpointHandlerPath(), "/common/hk4e_global/announcement/api/getAnnContent")) {
try {
data = FileUtils.readToString(DataLoader.load("GameAnnouncement.json"));
} catch (Exception e) {
......@@ -50,7 +45,7 @@ public final class AnnouncementsHandler implements Router {
Grasscutter.getLogger().info("Unable to read file 'GameAnnouncementList.json'. \n" + e);
}
}
} else if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnList")) {
} else if (Objects.equals(ctx.endpointHandlerPath(), "/common/hk4e_global/announcement/api/getAnnList")) {
try {
data = FileUtils.readToString(DataLoader.load("GameAnnouncementList.json"));
} catch (Exception e) {
......@@ -59,11 +54,11 @@ public final class AnnouncementsHandler implements Router {
}
}
} else {
response.send("{\"retcode\":404,\"message\":\"Unknown request path\"}");
ctx.result("{\"retcode\":404,\"message\":\"Unknown request path\"}");
}
if (data.isEmpty()) {
response.send("{\"retcode\":500,\"message\":\"Unable to fetch requsted content\"}");
ctx.result("{\"retcode\":500,\"message\":\"Unable to fetch requsted content\"}");
return;
}
......@@ -74,19 +69,19 @@ public final class AnnouncementsHandler implements Router {
data = data
.replace("{{DISPATCH_PUBLIC}}", dispatchDomain)
.replace("{{SYSTEM_TIME}}", String.valueOf(System.currentTimeMillis()));
response.send("{\"retcode\":0,\"message\":\"OK\",\"data\": " + data + "}");
ctx.result("{\"retcode\":0,\"message\":\"OK\",\"data\": " + data + "}");
}
private static void getPageResources(Request request, Response response) {
try (InputStream filestream = DataLoader.load(request.path())) {
String possibleFilename = Utils.toFilePath(DATA(request.path()));
private static void getPageResources(Context ctx) {
try (InputStream filestream = DataLoader.load(ctx.path())) {
String possibleFilename = Utils.toFilePath(DATA(ctx.path()));
MediaType fromExtension = MediaType.getByExtension(possibleFilename.substring(possibleFilename.lastIndexOf(".") + 1));
response.type((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream");
response.send(filestream.readAllBytes());
HttpUtils.MediaType fromExtension = HttpUtils.MediaType.getByExtension(possibleFilename.substring(possibleFilename.lastIndexOf(".") + 1));
ctx.contentType((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream");
ctx.result(filestream.readAllBytes());
} catch (Exception e) {
Grasscutter.getLogger().warn("File does not exist: " + request.path());
response.status(404);
Grasscutter.getLogger().warn("File does not exist: " + ctx.path());
ctx.status(404);
}
}
}
......@@ -7,13 +7,11 @@ import emu.grasscutter.game.gacha.GachaBanner;
import emu.grasscutter.game.gacha.GachaSystem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.http.Router;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.HttpUtils;
import emu.grasscutter.utils.Utils;
import express.Express;
import express.http.Request;
import express.http.Response;
import io.javalin.Javalin;
import io.javalin.http.Context;
import io.javalin.http.staticfiles.Location;
import java.io.File;
......@@ -31,38 +29,38 @@ import static emu.grasscutter.utils.Language.translate;
public final class GachaHandler implements Router {
public static final String gachaMappings = DATA(Utils.toFilePath("gacha/mappings.js"));
@Override public void applyRoutes(Express express, Javalin handle) {
express.get("/gacha", GachaHandler::gachaRecords);
express.get("/gacha/details", GachaHandler::gachaDetails);
@Override public void applyRoutes(Javalin javalin) {
javalin.get("/gacha", GachaHandler::gachaRecords);
javalin.get("/gacha/details", GachaHandler::gachaDetails);
express.useStaticFallback("/gacha/mappings", gachaMappings, Location.EXTERNAL);
javalin._conf.addSinglePageRoot("/gacha/mappings", gachaMappings, Location.EXTERNAL);
}
private static void gachaRecords(Request request, Response response) {
private static void gachaRecords(Context ctx) {
File recordsTemplate = new File(Utils.toFilePath(DATA("gacha/records.html")));
if (!recordsTemplate.exists()) {
Grasscutter.getLogger().warn("File does not exist: " + recordsTemplate);
response.status(500);
ctx.status(500);
return;
}
String sessionKey = request.query("s");
String sessionKey = ctx.queryParam("s");
Account account = DatabaseHelper.getAccountBySessionKey(sessionKey);
if (account == null) {
response.status(403).send("Requested account was not found");
ctx.status(403).result("Requested account was not found");
return;
}
Player player = Grasscutter.getGameServer().getPlayerByAccountId(account.getId());
if (player == null) {
response.status(403).send("No player associated with requested account");
ctx.status(403).result("No player associated with requested account");
return;
}
int page = 0, gachaType = 0;
if (request.query("p") != null)
page = Integer.parseInt(request.query("p"));
if (request.query("gachaType") != null)
gachaType = Integer.parseInt(request.query("gachaType"));
if (ctx.queryParam("p") != null)
page = Integer.parseInt(ctx.queryParam("p"));
if (ctx.queryParam("gachaType") != null)
gachaType = Integer.parseInt(ctx.queryParam("gachaType"));
String records = DatabaseHelper.getGachaRecords(player.getUid(), page, gachaType).toString();
long maxPage = DatabaseHelper.getGachaRecordsMaxPage(player.getUid(), page, gachaType);
......@@ -74,26 +72,27 @@ public final class GachaHandler implements Router {
.replace("{{DATE}}", translate(player, "gacha.records.date"))
.replace("{{ITEM}}", translate(player, "gacha.records.item"))
.replace("{{LANGUAGE}}", Utils.getLanguageCode(account.getLocale()));
response.send(template);
ctx.contentType(HttpUtils.MediaType._html.getMIME());
ctx.result(template);
}
private static void gachaDetails(Request request, Response response) {
private static void gachaDetails(Context ctx) {
File detailsTemplate = new File(Utils.toFilePath(DATA("gacha/details.html")));
if (!detailsTemplate.exists()) {
Grasscutter.getLogger().warn("File does not exist: " + detailsTemplate);
response.status(500);
ctx.status(500);
return;
}
String sessionKey = request.query("s");
String sessionKey = ctx.queryParam("s");
Account account = DatabaseHelper.getAccountBySessionKey(sessionKey);
if (account == null) {
response.status(403).send("Requested account was not found");
ctx.status(403).result("Requested account was not found");
return;
}
Player player = Grasscutter.getGameServer().getPlayerByAccountId(account.getId());
if (player == null) {
response.status(403).send("No player associated with requested account");
ctx.status(403).result("No player associated with requested account");
return;
}
......@@ -107,7 +106,7 @@ public final class GachaHandler implements Router {
.replace("{{LANGUAGE}}", Utils.getLanguageCode(account.getLocale()));
// Get the banner info for the banner we want.
int scheduleId = Integer.parseInt(request.query("scheduleId"));
int scheduleId = Integer.parseInt(ctx.queryParam("scheduleId"));
GachaSystem manager = Grasscutter.getGameServer().getGachaSystem();
GachaBanner banner = manager.getGachaBanners().get(scheduleId);
......@@ -135,6 +134,7 @@ public final class GachaHandler implements Router {
template = template.replace("{{THREE_STARS}}", "[" + String.join(",", threeStarItems) + "]");
// Done.
response.send(template);
ctx.contentType(HttpUtils.MediaType._html.getMIME());
ctx.result(template);
}
}
......@@ -7,53 +7,52 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.http.objects.HttpJsonResponse;
import emu.grasscutter.server.http.Router;
import emu.grasscutter.server.http.objects.WebStaticVersionResponse;
import express.Express;
import express.http.Request;
import express.http.Response;
import emu.grasscutter.utils.HttpUtils;
import io.javalin.Javalin;
import io.javalin.http.Context;
/**
* Handles all generic, hard-coded responses.
*/
public final class GenericHandler implements Router {
@Override public void applyRoutes(Express express, Javalin handle) {
@Override public void applyRoutes(Javalin javalin) {
// hk4e-sdk-os.hoyoverse.com
express.get("/hk4e_global/mdk/agreement/api/getAgreementInfos", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}"));
javalin.get("/hk4e_global/mdk/agreement/api/getAgreementInfos", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}"));
// hk4e-sdk-os.hoyoverse.com
// this could be either GET or POST based on the observation of different clients
express.all("/hk4e_global/combo/granter/api/compareProtocolVersion", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}"));
HttpUtils.allRoutes(javalin, "/hk4e_global/combo/granter/api/compareProtocolVersion", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}"));
// api-account-os.hoyoverse.com
express.post("/account/risky/api/check", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}"));
javalin.post("/account/risky/api/check", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}"));
// sdk-os-static.hoyoverse.com
express.get("/combo/box/api/config/sdk/combo", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}"));
javalin.get("/combo/box/api/config/sdk/combo", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}"));
// hk4e-sdk-os-static.hoyoverse.com
express.get("/hk4e_global/combo/granter/api/getConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}"));
javalin.get("/hk4e_global/combo/granter/api/getConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}"));
// hk4e-sdk-os-static.hoyoverse.com
express.get("/hk4e_global/mdk/shield/api/loadConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}"));
javalin.get("/hk4e_global/mdk/shield/api/loadConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}"));
// Test api?
// abtest-api-data-sg.hoyoverse.com
express.post("/data_abtest_api/config/experiment/list", new HttpJsonResponse("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}"));
javalin.post("/data_abtest_api/config/experiment/list", new HttpJsonResponse("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}"));
// log-upload-os.mihoyo.com
express.all("/log/sdk/upload", new HttpJsonResponse("{\"code\":0}"));
express.all("/sdk/upload", new HttpJsonResponse("{\"code\":0}"));
express.post("/sdk/dataUpload", new HttpJsonResponse("{\"code\":0}"));
HttpUtils.allRoutes(javalin, "/log/sdk/upload", new HttpJsonResponse("{\"code\":0}"));
HttpUtils.allRoutes(javalin, "/sdk/upload", new HttpJsonResponse("{\"code\":0}"));
javalin.post("/sdk/dataUpload", new HttpJsonResponse("{\"code\":0}"));
// /perf/config/verify?device_id=xxx&platform=x&name=xxx
express.all("/perf/config/verify", new HttpJsonResponse("{\"code\":0}"));
HttpUtils.allRoutes(javalin, "/perf/config/verify", new HttpJsonResponse("{\"code\":0}"));
// webstatic-sea.hoyoverse.com
express.get("/admin/mi18n/plat_oversea/*", new WebStaticVersionResponse());
javalin.get("/admin/mi18n/plat_oversea/*", new WebStaticVersionResponse());
express.get("/status/server", GenericHandler::serverStatus);
javalin.get("/status/server", GenericHandler::serverStatus);
}
private static void serverStatus(Request request, Response response) {
private static void serverStatus(Context ctx) {
int playerCount = Grasscutter.getGameServer().getPlayers().size();
int maxPlayer = ACCOUNT.maxPlayer;
String version = GameConstants.VERSION;
response.send("{\"retcode\":0,\"status\":{\"playerCount\":" + playerCount + ",\"maxPlayer\":" + maxPlayer + ",\"version\":\"" + version + "\"}}");
ctx.result("{\"retcode\":0,\"status\":{\"playerCount\":" + playerCount + ",\"maxPlayer\":" + maxPlayer + ",\"version\":\"" + version + "\"}}");
}
}
package emu.grasscutter.server.http.handlers;
import emu.grasscutter.server.http.Router;
import express.Express;
import express.http.Request;
import express.http.Response;
import io.javalin.Javalin;
import io.javalin.http.Context;
/**
* Handles logging requests made to the server.
*/
public final class LogHandler implements Router {
@Override public void applyRoutes(Express express, Javalin handle) {
@Override public void applyRoutes(Javalin javalin) {
// overseauspider.yuanshen.com
express.post("/log", LogHandler::log);
javalin.post("/log", LogHandler::log);
// log-upload-os.mihoyo.com
express.post("/crash/dataUpload", LogHandler::log);
javalin.post("/crash/dataUpload", LogHandler::log);
}
private static void log(Request request, Response response) {
private static void log(Context ctx) {
// TODO: Figure out how to dump request body and log to file.
response.send("{\"code\":0}");
ctx.result("{\"code\":0}");
}
}
......@@ -6,14 +6,14 @@ import java.util.Objects;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerDebugMode;
import express.http.HttpContextHandler;
import express.http.Request;
import express.http.Response;
import io.javalin.http.Context;
import io.javalin.http.Handler;
import org.jetbrains.annotations.NotNull;
import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.Language.translate;
public final class HttpJsonResponse implements HttpContextHandler {
public final class HttpJsonResponse implements Handler {
private final String response;
private final String[] missingRoutes = { // TODO: When http requests for theses routes are found please remove it from this list and update the route request type in the DispatchServer
"/common/hk4e_global/announcement/api/getAlertPic",
......@@ -33,11 +33,11 @@ public final class HttpJsonResponse implements HttpContextHandler {
}
@Override
public void handle(Request req, Response res) throws IOException {
public void handle(@NotNull Context ctx) throws Exception {
// Checking for ALL here isn't required as when ALL is enabled enableDevLogging() gets enabled
if (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING && Arrays.stream(missingRoutes).anyMatch(x -> Objects.equals(x, req.baseUrl()))) {
Grasscutter.getLogger().info(translate("messages.dispatch.request", req.ip(), req.method(), req.baseUrl()) + (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING ? "(MISSING)" : ""));
if (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING && Arrays.stream(missingRoutes).anyMatch(x -> Objects.equals(x, ctx.endpointHandlerPath()))) {
Grasscutter.getLogger().info(translate("messages.dispatch.request", ctx.ip(), ctx.method(), ctx.endpointHandlerPath()) + (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING ? "(MISSING)" : ""));
}
res.send(response);
ctx.result(response);
}
}
package emu.grasscutter.server.http.objects;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import express.http.HttpContextHandler;
import express.http.MediaType;
import express.http.Request;
import express.http.Response;
import io.javalin.core.util.FileUtil;
import static emu.grasscutter.config.Configuration.DATA;
import emu.grasscutter.utils.HttpUtils;
import io.javalin.http.Context;
import io.javalin.http.Handler;
import static emu.grasscutter.config.Configuration.DISPATCH_INFO;
import java.io.IOException;
import java.io.InputStream;
public class WebStaticVersionResponse implements HttpContextHandler {
public class WebStaticVersionResponse implements Handler {
@Override
public void handle(Request request, Response response) throws IOException {
String requestFor = request.path().substring(request.path().lastIndexOf("-") + 1);
public void handle(Context ctx) throws IOException {
String requestFor = ctx.path().substring(ctx.path().lastIndexOf("-") + 1);
getPageResources("/webstatic/" + requestFor, response);
getPageResources("/webstatic/" + requestFor, ctx);
return;
}
private static void getPageResources(String path, Response response) {
private static void getPageResources(String path, Context ctx) {
try (InputStream filestream = FileUtils.readResourceAsStream(path)) {
MediaType fromExtension = MediaType.getByExtension(path.substring(path.lastIndexOf(".") + 1));
response.type((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream");
response.send(filestream.readAllBytes());
HttpUtils.MediaType fromExtension = HttpUtils.MediaType.getByExtension(path.substring(path.lastIndexOf(".") + 1));
ctx.contentType((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream");
ctx.result(filestream.readAllBytes());
} catch (Exception e) {
if (DISPATCH_INFO.logRequests == Grasscutter.ServerDebugMode.MISSING) {
Grasscutter.getLogger().warn("Webstatic File Missing: " + path);
}
response.status(404);
ctx.status(404);
}
}
}
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment