From b5bed6ceefe596e2537c2cde0c61f7a27186a855 Mon Sep 17 00:00:00 2001
From: Benj <benjamin7006@gmail.com>
Date: Thu, 1 Sep 2022 21:33:03 +0800
Subject: [PATCH] Update HttpServer & AuthenticationSystem to use Javalin

---
 build.gradle                                  |   1 +
 .../auth/AuthenticationSystem.java            |  38 +-
 .../auth/DefaultAuthenticators.java           |  28 +-
 .../grasscutter/server/http/HttpServer.java   |  42 +-
 .../emu/grasscutter/server/http/Router.java   |   9 +-
 .../server/http/dispatch/DispatchHandler.java |  73 +-
 .../server/http/dispatch/RegionHandler.java   |  44 +-
 .../documentation/DocumentationHandler.java   |   5 +-
 .../DocumentationServerHandler.java           |  12 +-
 .../GachaMappingRequestHandler.java           |  10 +-
 .../documentation/HandbookRequestHandler.java |  11 +-
 .../documentation/RootRequestHandler.java     |  13 +-
 .../http/handlers/AnnouncementsHandler.java   |  51 +-
 .../server/http/handlers/GachaHandler.java    |  50 +-
 .../server/http/handlers/GenericHandler.java  |  37 +-
 .../server/http/handlers/LogHandler.java      |  16 +-
 .../server/http/objects/HttpJsonResponse.java |  16 +-
 .../objects/WebStaticVersionResponse.java     |  31 +-
 .../java/emu/grasscutter/utils/HttpUtils.java | 738 ++++++++++++++++++
 19 files changed, 964 insertions(+), 261 deletions(-)
 create mode 100644 src/main/java/emu/grasscutter/utils/HttpUtils.java

diff --git a/build.gradle b/build.gradle
index 1b800180..fd79b73d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -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/')
 
diff --git a/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java
index 90f2648c..828c4eca 100644
--- a/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java
+++ b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java
@@ -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();
     }
 }
diff --git a/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java
index cc908004..85b9e8e2 100644
--- a/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java
+++ b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java
@@ -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.");
         }
     }
 }
diff --git a/src/main/java/emu/grasscutter/server/http/HttpServer.java b/src/main/java/emu/grasscutter/server/http/HttpServer.java
index 1ad62fb2..b34a6e35 100644
--- a/src/main/java/emu/grasscutter/server/http/HttpServer.java
+++ b/src/main/java/emu/grasscutter/server/http/HttpServer.java
@@ -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));
                 }
diff --git a/src/main/java/emu/grasscutter/server/http/Router.java b/src/main/java/emu/grasscutter/server/http/Router.java
index 1720d7ca..3e3f2877 100644
--- a/src/main/java/emu/grasscutter/server/http/Router.java
+++ b/src/main/java/emu/grasscutter/server/http/Router.java
@@ -1,16 +1,15 @@
 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);
 }
diff --git a/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java
index 6a93b544..0678abd4 100644
--- a/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java
@@ -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()));
     }
 }
diff --git a/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java b/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java
index 40a2c203..8c4d9874 100644
--- a/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java
@@ -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));
     }
 
     /**
diff --git a/src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java
index de7f543a..05468e95 100644
--- a/src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java
@@ -1,9 +1,8 @@
 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);
 }
diff --git a/src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java
index 24c8236d..6e64286c 100644
--- a/src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java
@@ -1,19 +1,21 @@
 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);
     }
 }
diff --git a/src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java
index 6b821a3f..51d52dc7 100644
--- a/src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java
@@ -1,9 +1,9 @@
 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));
     }
 }
diff --git a/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java
index cb260e3a..c207461e 100644
--- a/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java
@@ -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));
         }
     }
 
diff --git a/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java
index 7cf37697..2ff9dd2c 100644
--- a/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java
@@ -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);
     }
 }
diff --git a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java
index 69ebc6b9..5191c886 100644
--- a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java
@@ -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);
         }
     }
 }
diff --git a/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java
index 38548b1d..9c883e6b 100644
--- a/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java
@@ -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);
     }
 }
diff --git a/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java
index 4c32d3d2..222d3ec8 100644
--- a/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java
@@ -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 + "\"}}");
     }
 }
diff --git a/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java
index 08025d36..37ac7ad4 100644
--- a/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java
@@ -1,24 +1,22 @@
 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}");
     }
 }
diff --git a/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java b/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java
index 327445f2..410e69ac 100644
--- a/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java
+++ b/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java
@@ -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);
     }
 }
diff --git a/src/main/java/emu/grasscutter/server/http/objects/WebStaticVersionResponse.java b/src/main/java/emu/grasscutter/server/http/objects/WebStaticVersionResponse.java
index ec0f973f..7d348072 100644
--- a/src/main/java/emu/grasscutter/server/http/objects/WebStaticVersionResponse.java
+++ b/src/main/java/emu/grasscutter/server/http/objects/WebStaticVersionResponse.java
@@ -1,42 +1,37 @@
 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);
         }
     }
 }
diff --git a/src/main/java/emu/grasscutter/utils/HttpUtils.java b/src/main/java/emu/grasscutter/utils/HttpUtils.java
new file mode 100644
index 00000000..b1fc02b2
--- /dev/null
+++ b/src/main/java/emu/grasscutter/utils/HttpUtils.java
@@ -0,0 +1,738 @@
+package emu.grasscutter.utils;
+
+import io.javalin.Javalin;
+import io.javalin.http.Handler;
+
+public final class HttpUtils {
+
+    public static Javalin allRoutes(Javalin javalin, String path, Handler ctx) {
+        javalin.get(path, ctx);
+        javalin.post(path, ctx);
+        javalin.put(path, ctx);
+        javalin.patch(path, ctx);
+        javalin.delete(path, ctx);
+
+        return javalin;
+    }
+
+    /**
+     * @author Simon Reinisch
+     * Enum with all MediaTypes
+     * Original code: https://github.com/Aarkan1/java-express/blob/main/src/main/java/express/http/MediaType.java
+     */
+    public enum MediaType {
+
+        _aw("aw", "application/applixware"),
+        _atom("atom", "application/atom+xml "),
+        _atomcat("atomcat", "application/atomcat+xml"),
+        _atomsvc("atomsvc", "application/atomsvc+xml"),
+        _ccxml("ccxml", "application/ccxml+xml"),
+        _cdmia("cdmia", "application/cdmi-capability"),
+        _cdmic("cdmic", "application/cdmi-container"),
+        _cdmid("cdmid", "application/cdmi-domain"),
+        _cdmio("cdmio", "application/cdmi-object"),
+        _cdmiq("cdmiq", "application/cdmi-queue"),
+        _cu("cu", "application/cu-seeme"),
+        _davmount("davmount", "application/davmount+xml"),
+        _dssc("dssc", "application/dssc+der"),
+        _xdssc("xdssc", "application/dssc+xml"),
+        _es("es", "application/ecmascript"),
+        _emma("emma", "application/emma+xml"),
+        _epub("epub", "application/epub+zip"),
+        _exi("exi", "application/exi"),
+        _pfr("pfr", "application/font-tdpfr"),
+        _stk("stk", "application/hyperstudio"),
+        _ipfix("ipfix", "application/ipfix"),
+        _jar("jar", "application/java-archive"),
+        _ser("ser", "application/java-serialized-object"),
+        _class("class", "application/java-vm"),
+        _js("js", "application/javascript"),
+        _json("json", "application/json"),
+        _hqx("hqx", "application/mac-binhex40"),
+        _cpt("cpt", "application/mac-compactpro"),
+        _mads("mads", "application/mads+xml"),
+        _mrc("mrc", "application/marc"),
+        _mrcx("mrcx", "application/marcxml+xml"),
+        _ma("ma", "application/mathematica"),
+        _mathml("mathml", "application/mathml+xml"),
+        _mbox("mbox", "application/mbox"),
+        _mscml("mscml", "application/mediaservercontrol+xml"),
+        _meta4("meta4", "application/metalink4+xml"),
+        _mets("mets", "application/mets+xml"),
+        _mods("mods", "application/mods+xml"),
+        _m21("m21", "application/mp21"),
+        _doc("doc", "application/msword"),
+        _mxf("mxf", "application/mxf"),
+        _bin("bin", "application/octet-stream"),
+        _oda("oda", "application/oda"),
+        _opf("opf", "application/oebps-package+xml"),
+        _ogx("ogx", "application/ogg"),
+        _onetoc("onetoc", "application/onenote"),
+        _xer("xer", "application/patch-ops-error+xml"),
+        _pdf("pdf", "application/pdf"),
+        _prf("prf", "application/pics-rules"),
+        _p10("p10", "application/pkcs10"),
+        _p7m("p7m", "application/pkcs7-mime"),
+        _p7s("p7s", "application/pkcs7-signature"),
+        _p8("p8", "application/pkcs8"),
+        _ac("ac", "application/pkix-attr-cert"),
+        _cer("cer", "application/pkix-cert"),
+        _crl("crl", "application/pkix-crl"),
+        _pkipath("pkipath", "application/pkix-pkipath"),
+        _pki("pki", "application/pkixcmp"),
+        _pls("pls", "application/pls+xml"),
+        _ai("ai", "application/postscript"),
+        _cww("cww", "application/prs.cww"),
+        _pskcxml("pskcxml", "application/pskc+xml"),
+        _rdf("rdf", "application/rdf+xml"),
+        _rif("rif", "application/reginfo+xml"),
+        _rnc("rnc", "application/relax-ng-compact-syntax"),
+        _rl("rl", "application/resource-lists+xml"),
+        _rld("rld", "application/resource-lists-diff+xml"),
+        _rs("rs", "application/rls-services+xml"),
+        _rsd("rsd", "application/rsd+xml"),
+        _rss("rss", "application/rss+xml"),
+        _rtf("rtf", "application/rtf"),
+        _sbml("sbml", "application/sbml+xml"),
+        _scq("scq", "application/scvp-cv-request"),
+        _scs("scs", "application/scvp-cv-response"),
+        _spq("spq", "application/scvp-vp-request"),
+        _spp("spp", "application/scvp-vp-response"),
+        _sdp("sdp", "application/sdp"),
+        _setpay("setpay", "application/set-payment-initiation"),
+        _setreg("setreg", "application/set-registration-initiation"),
+        _shf("shf", "application/shf+xml"),
+        _smi("smi", "application/smil+xml"),
+        _rq("rq", "application/sparql-query"),
+        _srx("srx", "application/sparql-results+xml"),
+        _gram("gram", "application/srgs"),
+        _grxml("grxml", "application/srgs+xml"),
+        _sru("sru", "application/sru+xml"),
+        _ssml("ssml", "application/ssml+xml"),
+        _tei("tei", "application/tei+xml"),
+        _tfi("tfi", "application/thraud+xml"),
+        _tsd("tsd", "application/timestamped-data"),
+        _plb("plb", "application/vnd.3gpp.pic-bw-large"),
+        _psb("psb", "application/vnd.3gpp.pic-bw-small"),
+        _pvb("pvb", "application/vnd.3gpp.pic-bw-var"),
+        _tcap("tcap", "application/vnd.3gpp2.tcap"),
+        _pwn("pwn", "application/vnd.3m.post-it-notes"),
+        _aso("aso", "application/vnd.accpac.simply.aso"),
+        _imp("imp", "application/vnd.accpac.simply.imp"),
+        _acu("acu", "application/vnd.acucobol"),
+        _atc("atc", "application/vnd.acucorp"),
+        _air("air", "application/vnd.adobe.air-application-installer-package+zip"),
+        _fxp("fxp", "application/vnd.adobe.fxp"),
+        _xdp("xdp", "application/vnd.adobe.xdp+xml"),
+        _xfdf("xfdf", "application/vnd.adobe.xfdf"),
+        _ahead("ahead", "application/vnd.ahead.space"),
+        _azf("azf", "application/vnd.airzip.filesecure.azf"),
+        _azs("azs", "application/vnd.airzip.filesecure.azs"),
+        _azw("azw", "application/vnd.amazon.ebook"),
+        _acc("acc", "application/vnd.americandynamics.acc"),
+        _ami("ami", "application/vnd.amiga.ami"),
+        _apk("apk", "application/vnd.android.package-archive"),
+        _cii("cii", "application/vnd.anser-web-certificate-issue-initiation"),
+        _fti("fti", "application/vnd.anser-web-funds-transfer-initiation"),
+        _atx("atx", "application/vnd.antix.game-component"),
+        _mpkg("mpkg", "application/vnd.apple.installer+xml"),
+        _m3u8("m3u8", "application/vnd.apple.mpegurl"),
+        _swi("swi", "application/vnd.aristanetworks.swi"),
+        _aep("aep", "application/vnd.audiograph"),
+        _mpm("mpm", "application/vnd.blueice.multipass"),
+        _bmi("bmi", "application/vnd.bmi"),
+        _rep("rep", "application/vnd.businessobjects"),
+        _cdxml("cdxml", "application/vnd.chemdraw+xml"),
+        _mmd("mmd", "application/vnd.chipnuts.karaoke-mmd"),
+        _cdy("cdy", "application/vnd.cinderella"),
+        _cla("cla", "application/vnd.claymore"),
+        _rp9("rp9", "application/vnd.cloanto.rp9"),
+        _c4g("c4g", "application/vnd.clonk.c4group"),
+        _c11amc("c11amc", "application/vnd.cluetrust.cartomobile-config"),
+        _c11amz("c11amz", "application/vnd.cluetrust.cartomobile-config-pkg"),
+        _csp("csp", "application/vnd.commonspace"),
+        _cdbcmsg("cdbcmsg", "application/vnd.contact.cmsg"),
+        _cmc("cmc", "application/vnd.cosmocaller"),
+        _clkx("clkx", "application/vnd.crick.clicker"),
+        _clkk("clkk", "application/vnd.crick.clicker.keyboard"),
+        _clkp("clkp", "application/vnd.crick.clicker.palette"),
+        _clkt("clkt", "application/vnd.crick.clicker.template"),
+        _clkw("clkw", "application/vnd.crick.clicker.wordbank"),
+        _wbs("wbs", "application/vnd.criticaltools.wbs+xml"),
+        _pml("pml", "application/vnd.ctc-posml"),
+        _ppd("ppd", "application/vnd.cups-ppd"),
+        _car("car", "application/vnd.curl.car"),
+        _pcurl("pcurl", "application/vnd.curl.pcurl"),
+        _rdz("rdz", "application/vnd.data-vision.rdz"),
+        _fe_launch("fe_launch", "application/vnd.denovo.fcselayout-link"),
+        _dna("dna", "application/vnd.dna"),
+        _mlp("mlp", "application/vnd.dolby.mlp"),
+        _dpg("dpg", "application/vnd.dpgraph"),
+        _dfac("dfac", "application/vnd.dreamfactory"),
+        _ait("ait", "application/vnd.dvb.ait"),
+        _svc("svc", "application/vnd.dvb.service"),
+        _geo("geo", "application/vnd.dynageo"),
+        _mag("mag", "application/vnd.ecowin.chart"),
+        _nml("nml", "application/vnd.enliven"),
+        _esf("esf", "application/vnd.epson.esf"),
+        _msf("msf", "application/vnd.epson.msf"),
+        _qam("qam", "application/vnd.epson.quickanime"),
+        _slt("slt", "application/vnd.epson.salt"),
+        _ssf("ssf", "application/vnd.epson.ssf"),
+        _es3("es3", "application/vnd.eszigno3+xml"),
+        _ez2("ez2", "application/vnd.ezpix-album"),
+        _ez3("ez3", "application/vnd.ezpix-package"),
+        _fdf("fdf", "application/vnd.fdf"),
+        _seed("seed", "application/vnd.fdsn.seed"),
+        _gph("gph", "application/vnd.flographit"),
+        _ftc("ftc", "application/vnd.fluxtime.clip"),
+        _fm("fm", "application/vnd.framemaker"),
+        _fnc("fnc", "application/vnd.frogans.fnc"),
+        _ltf("ltf", "application/vnd.frogans.ltf"),
+        _fsc("fsc", "application/vnd.fsc.weblaunch"),
+        _oas("oas", "application/vnd.fujitsu.oasys"),
+        _oa2("oa2", "application/vnd.fujitsu.oasys2"),
+        _oa3("oa3", "application/vnd.fujitsu.oasys3"),
+        _fg5("fg5", "application/vnd.fujitsu.oasysgp"),
+        _bh2("bh2", "application/vnd.fujitsu.oasysprs"),
+        _ddd("ddd", "application/vnd.fujixerox.ddd"),
+        _xdw("xdw", "application/vnd.fujixerox.docuworks"),
+        _xbd("xbd", "application/vnd.fujixerox.docuworks.binder"),
+        _fzs("fzs", "application/vnd.fuzzysheet"),
+        _txd("txd", "application/vnd.genomatix.tuxedo"),
+        _ggb("ggb", "application/vnd.geogebra.file"),
+        _ggt("ggt", "application/vnd.geogebra.tool"),
+        _gex("gex", "application/vnd.geometry-explorer"),
+        _gxt("gxt", "application/vnd.geonext"),
+        _g2w("g2w", "application/vnd.geoplan"),
+        _g3w("g3w", "application/vnd.geospace"),
+        _gmx("gmx", "application/vnd.gmx"),
+        _kml("kml", "application/vnd.google-earth.kml+xml"),
+        _kmz("kmz", "application/vnd.google-earth.kmz"),
+        _gqf("gqf", "application/vnd.grafeq"),
+        _gac("gac", "application/vnd.groove-account"),
+        _ghf("ghf", "application/vnd.groove-help"),
+        _gim("gim", "application/vnd.groove-identity-message"),
+        _grv("grv", "application/vnd.groove-injector"),
+        _gtm("gtm", "application/vnd.groove-tool-message"),
+        _tpl("tpl", "application/vnd.groove-tool-template"),
+        _vcg("vcg", "application/vnd.groove-vcard"),
+        _hal("hal", "application/vnd.hal+xml"),
+        _zmm("zmm", "application/vnd.handheld-entertainment+xml"),
+        _hbci("hbci", "application/vnd.hbci"),
+        _les("les", "application/vnd.hhe.lesson-player"),
+        _hpgl("hpgl", "application/vnd.hp-hpgl"),
+        _hpid("hpid", "application/vnd.hp-hpid"),
+        _hps("hps", "application/vnd.hp-hps"),
+        _jlt("jlt", "application/vnd.hp-jlyt"),
+        _pcl("pcl", "application/vnd.hp-pcl"),
+        _pclxl("pclxl", "application/vnd.hp-pclxl"),
+        _sfd_hdstx("sfd-hdstx", "application/vnd.hydrostatix.sof-data"),
+        _x3d("x3d", "application/vnd.hzn-3d-crossword"),
+        _mpy("mpy", "application/vnd.ibm.minipay"),
+        _afp("afp", "application/vnd.ibm.modcap"),
+        _irm("irm", "application/vnd.ibm.rights-management"),
+        _sc("sc", "application/vnd.ibm.secure-container"),
+        _icc("icc", "application/vnd.iccprofile"),
+        _igl("igl", "application/vnd.igloader"),
+        _ivp("ivp", "application/vnd.immervision-ivp"),
+        _ivu("ivu", "application/vnd.immervision-ivu"),
+        _igm("igm", "application/vnd.insors.igm"),
+        _xpw("xpw", "application/vnd.intercon.formnet"),
+        _i2g("i2g", "application/vnd.intergeo"),
+        _qbo("qbo", "application/vnd.intu.qbo"),
+        _qfx("qfx", "application/vnd.intu.qfx"),
+        _rcprofile("rcprofile", "application/vnd.ipunplugged.rcprofile"),
+        _irp("irp", "application/vnd.irepository.package+xml"),
+        _xpr("xpr", "application/vnd.is-xpr"),
+        _fcs("fcs", "application/vnd.isac.fcs"),
+        _jam("jam", "application/vnd.jam"),
+        _rms("rms", "application/vnd.jcp.javame.midlet-rms"),
+        _jisp("jisp", "application/vnd.jisp"),
+        _joda("joda", "application/vnd.joost.joda-archive"),
+        _ktz("ktz", "application/vnd.kahootz"),
+        _karbon("karbon", "application/vnd.kde.karbon"),
+        _chrt("chrt", "application/vnd.kde.kchart"),
+        _kfo("kfo", "application/vnd.kde.kformula"),
+        _flw("flw", "application/vnd.kde.kivio"),
+        _kon("kon", "application/vnd.kde.kontour"),
+        _kpr("kpr", "application/vnd.kde.kpresenter"),
+        _ksp("ksp", "application/vnd.kde.kspread"),
+        _kwd("kwd", "application/vnd.kde.kword"),
+        _htke("htke", "application/vnd.kenameaapp"),
+        _kia("kia", "application/vnd.kidspiration"),
+        _kne("kne", "application/vnd.kinar"),
+        _skp("skp", "application/vnd.koan"),
+        _sse("sse", "application/vnd.kodak-descriptor"),
+        _lasxml("lasxml", "application/vnd.las.las+xml"),
+        _lbd("lbd", "application/vnd.llamagraphics.life-balance.desktop"),
+        _lbe("lbe", "application/vnd.llamagraphics.life-balance.exchange+xml"),
+        _123("123", "application/vnd.lotus-1-2-3"),
+        _apr("apr", "application/vnd.lotus-approach"),
+        _pre("pre", "application/vnd.lotus-freelance"),
+        _nsf("nsf", "application/vnd.lotus-notes"),
+        _org("org", "application/vnd.lotus-organizer"),
+        _scm("scm", "application/vnd.lotus-screencam"),
+        _lwp("lwp", "application/vnd.lotus-wordpro"),
+        _portpkg("portpkg", "application/vnd.macports.portpkg"),
+        _mcd("mcd", "application/vnd.mcd"),
+        _mc1("mc1", "application/vnd.medcalcdata"),
+        _cdkey("cdkey", "application/vnd.mediastation.cdkey"),
+        _mwf("mwf", "application/vnd.mfer"),
+        _mfm("mfm", "application/vnd.mfmp"),
+        _flo("flo", "application/vnd.micrografx.flo"),
+        _igx("igx", "application/vnd.micrografx.igx"),
+        _mif("mif", "application/vnd.mif"),
+        _daf("daf", "application/vnd.mobius.daf"),
+        _dis("dis", "application/vnd.mobius.dis"),
+        _mbk("mbk", "application/vnd.mobius.mbk"),
+        _mqy("mqy", "application/vnd.mobius.mqy"),
+        _msl("msl", "application/vnd.mobius.msl"),
+        _plc("plc", "application/vnd.mobius.plc"),
+        _txf("txf", "application/vnd.mobius.txf"),
+        _mpn("mpn", "application/vnd.mophun.application"),
+        _mpc("mpc", "application/vnd.mophun.certificate"),
+        _xul("xul", "application/vnd.mozilla.xul+xml"),
+        _cil("cil", "application/vnd.ms-artgalry"),
+        _cab("cab", "application/vnd.ms-cab-compressed"),
+        _xls("xls", "application/vnd.ms-excel"),
+        _xlam("xlam", "application/vnd.ms-excel.addin.macroenabled.12"),
+        _xlsb("xlsb", "application/vnd.ms-excel.sheet.binary.macroenabled.12"),
+        _xlsm("xlsm", "application/vnd.ms-excel.sheet.macroenabled.12"),
+        _xltm("xltm", "application/vnd.ms-excel.template.macroenabled.12"),
+        _eot("eot", "application/vnd.ms-fontobject"),
+        _chm("chm", "application/vnd.ms-htmlhelp"),
+        _ims("ims", "application/vnd.ms-ims"),
+        _lrm("lrm", "application/vnd.ms-lrm"),
+        _thmx("thmx", "application/vnd.ms-officetheme"),
+        _cat("cat", "application/vnd.ms-pki.seccat"),
+        _stl("stl", "application/vnd.ms-pki.stl"),
+        _ppt("ppt", "application/vnd.ms-powerpoint"),
+        _ppam("ppam", "application/vnd.ms-powerpoint.addin.macroenabled.12"),
+        _pptm("pptm", "application/vnd.ms-powerpoint.presentation.macroenabled.12"),
+        _sldm("sldm", "application/vnd.ms-powerpoint.slide.macroenabled.12"),
+        _ppsm("ppsm", "application/vnd.ms-powerpoint.slideshow.macroenabled.12"),
+        _potm("potm", "application/vnd.ms-powerpoint.template.macroenabled.12"),
+        _mpp("mpp", "application/vnd.ms-project"),
+        _docm("docm", "application/vnd.ms-word.document.macroenabled.12"),
+        _dotm("dotm", "application/vnd.ms-word.template.macroenabled.12"),
+        _wps("wps", "application/vnd.ms-works"),
+        _wpl("wpl", "application/vnd.ms-wpl"),
+        _xps("xps", "application/vnd.ms-xpsdocument"),
+        _mseq("mseq", "application/vnd.mseq"),
+        _mus("mus", "application/vnd.musician"),
+        _msty("msty", "application/vnd.muvee.style"),
+        _nlu("nlu", "application/vnd.neurolanguage.nlu"),
+        _nnd("nnd", "application/vnd.noblenet-directory"),
+        _nns("nns", "application/vnd.noblenet-sealer"),
+        _nnw("nnw", "application/vnd.noblenet-web"),
+        _ngdat("ngdat", "application/vnd.nokia.n-gage.data"),
+        _n_gage("n-gage", "application/vnd.nokia.n-gage.symbian.install"),
+        _rpst("rpst", "application/vnd.nokia.radio-preset"),
+        _rpss("rpss", "application/vnd.nokia.radio-presets"),
+        _edm("edm", "application/vnd.novadigm.edm"),
+        _edx("edx", "application/vnd.novadigm.edx"),
+        _ext("ext", "application/vnd.novadigm.ext"),
+        _odc("odc", "application/vnd.oasis.opendocument.chart"),
+        _otc("otc", "application/vnd.oasis.opendocument.chart-template"),
+        _odb("odb", "application/vnd.oasis.opendocument.database"),
+        _odf("odf", "application/vnd.oasis.opendocument.formula"),
+        _odft("odft", "application/vnd.oasis.opendocument.formula-template"),
+        _odg("odg", "application/vnd.oasis.opendocument.graphics"),
+        _otg("otg", "application/vnd.oasis.opendocument.graphics-template"),
+        _odi("odi", "application/vnd.oasis.opendocument.image"),
+        _oti("oti", "application/vnd.oasis.opendocument.image-template"),
+        _odp("odp", "application/vnd.oasis.opendocument.presentation"),
+        _otp("otp", "application/vnd.oasis.opendocument.presentation-template"),
+        _ods("ods", "application/vnd.oasis.opendocument.spreadsheet"),
+        _ots("ots", "application/vnd.oasis.opendocument.spreadsheet-template"),
+        _odt("odt", "application/vnd.oasis.opendocument.text"),
+        _odm("odm", "application/vnd.oasis.opendocument.text-master"),
+        _ott("ott", "application/vnd.oasis.opendocument.text-template"),
+        _oth("oth", "application/vnd.oasis.opendocument.text-web"),
+        _xo("xo", "application/vnd.olpc-sugar"),
+        _dd2("dd2", "application/vnd.oma.dd2+xml"),
+        _oxt("oxt", "application/vnd.openofficeorg.extension"),
+        _pptx("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"),
+        _sldx("sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"),
+        _ppsx("ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"),
+        _potx("potx", "application/vnd.openxmlformats-officedocument.presentationml.template"),
+        _xlsx("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
+        _xltx("xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"),
+        _docx("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
+        _dotx("dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"),
+        _mgp("mgp", "application/vnd.osgeo.mapguide.package"),
+        _dp("dp", "application/vnd.osgi.dp"),
+        _pdb("pdb", "application/vnd.palm"),
+        _paw("paw", "application/vnd.pawaafile"),
+        _str("str", "application/vnd.pg.format"),
+        _ei6("ei6", "application/vnd.pg.osasli"),
+        _efif("efif", "application/vnd.picsel"),
+        _wg("wg", "application/vnd.pmi.widget"),
+        _plf("plf", "application/vnd.pocketlearn"),
+        _pbd("pbd", "application/vnd.powerbuilder6"),
+        _box("box", "application/vnd.previewsystems.box"),
+        _mgz("mgz", "application/vnd.proteus.magazine"),
+        _qps("qps", "application/vnd.publishare-delta-tree"),
+        _ptid("ptid", "application/vnd.pvi.ptid1"),
+        _qxd("qxd", "application/vnd.quark.quarkxpress"),
+        _bed("bed", "application/vnd.realvnc.bed"),
+        _mxl("mxl", "application/vnd.recordare.musicxml"),
+        _musicxml("musicxml", "application/vnd.recordare.musicxml+xml"),
+        _cryptonote("cryptonote", "application/vnd.rig.cryptonote"),
+        _cod("cod", "application/vnd.rim.cod"),
+        _rm("rm", "application/vnd.rn-realmedia"),
+        _link66("link66", "application/vnd.route66.link66+xml"),
+        _st("st", "application/vnd.sailingtracker.track"),
+        _see("see", "application/vnd.seemail"),
+        _sema("sema", "application/vnd.sema"),
+        _semd("semd", "application/vnd.semd"),
+        _semf("semf", "application/vnd.semf"),
+        _ifm("ifm", "application/vnd.shana.informed.formdata"),
+        _itp("itp", "application/vnd.shana.informed.formtemplate"),
+        _iif("iif", "application/vnd.shana.informed.interchange"),
+        _ipk("ipk", "application/vnd.shana.informed.package"),
+        _twd("twd", "application/vnd.simtech-mindmapper"),
+        _mmf("mmf", "application/vnd.smaf"),
+        _teacher("teacher", "application/vnd.smart.teacher"),
+        _sdkm("sdkm", "application/vnd.solent.sdkm+xml"),
+        _dxp("dxp", "application/vnd.spotfire.dxp"),
+        _sfs("sfs", "application/vnd.spotfire.sfs"),
+        _sdc("sdc", "application/vnd.stardivision.calc"),
+        _sda("sda", "application/vnd.stardivision.draw"),
+        _sdd("sdd", "application/vnd.stardivision.impress"),
+        _smf("smf", "application/vnd.stardivision.math"),
+        _sdw("sdw", "application/vnd.stardivision.writer"),
+        _sgl("sgl", "application/vnd.stardivision.writer-global"),
+        _sm("sm", "application/vnd.stepmania.stepchart"),
+        _sxc("sxc", "application/vnd.sun.xml.calc"),
+        _stc("stc", "application/vnd.sun.xml.calc.template"),
+        _sxd("sxd", "application/vnd.sun.xml.draw"),
+        _std("std", "application/vnd.sun.xml.draw.template"),
+        _sxi("sxi", "application/vnd.sun.xml.impress"),
+        _sti("sti", "application/vnd.sun.xml.impress.template"),
+        _sxm("sxm", "application/vnd.sun.xml.math"),
+        _sxw("sxw", "application/vnd.sun.xml.writer"),
+        _sxg("sxg", "application/vnd.sun.xml.writer.global"),
+        _stw("stw", "application/vnd.sun.xml.writer.template"),
+        _sus("sus", "application/vnd.sus-calendar"),
+        _svd("svd", "application/vnd.svd"),
+        _sis("sis", "application/vnd.symbian.install"),
+        _xsm("xsm", "application/vnd.syncml+xml"),
+        _bdm("bdm", "application/vnd.syncml.dm+wbxml"),
+        _xdm("xdm", "application/vnd.syncml.dm+xml"),
+        _tao("tao", "application/vnd.tao.intent-module-archive"),
+        _tmo("tmo", "application/vnd.tmobile-livetv"),
+        _tpt("tpt", "application/vnd.trid.tpt"),
+        _mxs("mxs", "application/vnd.triscape.mxs"),
+        _tra("tra", "application/vnd.trueapp"),
+        _ufd("ufd", "application/vnd.ufdl"),
+        _utz("utz", "application/vnd.uiq.theme"),
+        _umj("umj", "application/vnd.umajin"),
+        _unityweb("unityweb", "application/vnd.unity"),
+        _uoml("uoml", "application/vnd.uoml+xml"),
+        _vcx("vcx", "application/vnd.vcx"),
+        _vsd("vsd", "application/vnd.visio"),
+        _vsdx("vsdx", "application/vnd.visio2013"),
+        _vis("vis", "application/vnd.visionary"),
+        _vsf("vsf", "application/vnd.vsf"),
+        _wbxml("wbxml", "application/vnd.wap.wbxml"),
+        _wmlc("wmlc", "application/vnd.wap.wmlc"),
+        _wmlsc("wmlsc", "application/vnd.wap.wmlscriptc"),
+        _wtb("wtb", "application/vnd.webturbo"),
+        _nbp("nbp", "application/vnd.wolfram.player"),
+        _wpd("wpd", "application/vnd.wordperfect"),
+        _wqd("wqd", "application/vnd.wqd"),
+        _stf("stf", "application/vnd.wt.stf"),
+        _xar("xar", "application/vnd.xara"),
+        _xfdl("xfdl", "application/vnd.xfdl"),
+        _hvd("hvd", "application/vnd.yamaha.hv-dic"),
+        _hvs("hvs", "application/vnd.yamaha.hv-script"),
+        _hvp("hvp", "application/vnd.yamaha.hv-voice"),
+        _osf("osf", "application/vnd.yamaha.openscoreformat"),
+        _osfpvg("osfpvg", "application/vnd.yamaha.openscoreformat.osfpvg+xml"),
+        _saf("saf", "application/vnd.yamaha.smaf-audio"),
+        _spf("spf", "application/vnd.yamaha.smaf-phrase"),
+        _cmp("cmp", "application/vnd.yellowriver-custom-menu"),
+        _zir("zir", "application/vnd.zul"),
+        _zaz("zaz", "application/vnd.zzazz.deck+xml"),
+        _vxml("vxml", "application/voicexml+xml"),
+        _wgt("wgt", "application/widget"),
+        _hlp("hlp", "application/winhlp"),
+        _wsdl("wsdl", "application/wsdl+xml"),
+        _wspolicy("wspolicy", "application/wspolicy+xml"),
+        _7z("7z", "application/x-7z-compressed"),
+        _abw("abw", "application/x-abiword"),
+        _ace("ace", "application/x-ace-compressed"),
+        _aab("aab", "application/x-authorware-bin"),
+        _aam("aam", "application/x-authorware-map"),
+        _aas("aas", "application/x-authorware-seg"),
+        _bcpio("bcpio", "application/x-bcpio"),
+        _torrent("torrent", "application/x-bittorrent"),
+        _bz("bz", "application/x-bzip"),
+        _bz2("bz2", "application/x-bzip2"),
+        _vcd("vcd", "application/x-cdlink"),
+        _chat("chat", "application/x-chat"),
+        _pgn("pgn", "application/x-chess-pgn"),
+        _cpio("cpio", "application/x-cpio"),
+        _csh("csh", "application/x-csh"),
+        _deb("deb", "application/x-debian-package"),
+        _dir("dir", "application/x-director"),
+        _wad("wad", "application/x-doom"),
+        _ncx("ncx", "application/x-dtbncx+xml"),
+        _dtb("dtb", "application/x-dtbook+xml"),
+        _res("res", "application/x-dtbresource+xml"),
+        _dvi("dvi", "application/x-dvi"),
+        _bdf("bdf", "application/x-font-bdf"),
+        _gsf("gsf", "application/x-font-ghostscript"),
+        _psf("psf", "application/x-font-linux-psf"),
+        _otf("otf", "application/x-font-otf"),
+        _pcf("pcf", "application/x-font-pcf"),
+        _snf("snf", "application/x-font-snf"),
+        _ttf("ttf", "application/x-font-ttf"),
+        _pfa("pfa", "application/x-font-type1"),
+        _woff("woff", "application/x-font-woff"),
+        _spl("spl", "application/x-futuresplash"),
+        _gnumeric("gnumeric", "application/x-gnumeric"),
+        _gtar("gtar", "application/x-gtar"),
+        _hdf("hdf", "application/x-hdf"),
+        _jnlp("jnlp", "application/x-java-jnlp-file"),
+        _latex("latex", "application/x-latex"),
+        _prc("prc", "application/x-mobipocket-ebook"),
+        _application("application", "application/x-ms-application"),
+        _wmd("wmd", "application/x-ms-wmd"),
+        _wmz("wmz", "application/x-ms-wmz"),
+        _xbap("xbap", "application/x-ms-xbap"),
+        _mdb("mdb", "application/x-msaccess"),
+        _obd("obd", "application/x-msbinder"),
+        _crd("crd", "application/x-mscardfile"),
+        _clp("clp", "application/x-msclip"),
+        _exe("exe", "application/x-msdownload"),
+        _mvb("mvb", "application/x-msmediaview"),
+        _wmf("wmf", "application/x-msmetafile"),
+        _mny("mny", "application/x-msmoney"),
+        _pub("pub", "application/x-mspublisher"),
+        _scd("scd", "application/x-msschedule"),
+        _trm("trm", "application/x-msterminal"),
+        _wri("wri", "application/x-mswrite"),
+        _nc("nc", "application/x-netcdf"),
+        _p12("p12", "application/x-pkcs12"),
+        _p7b("p7b", "application/x-pkcs7-certificates"),
+        _p7r("p7r", "application/x-pkcs7-certreqresp"),
+        _rar("rar", "application/x-rar-compressed"),
+        _sh("sh", "application/x-sh"),
+        _shar("shar", "application/x-shar"),
+        _swf("swf", "application/x-shockwave-flash"),
+        _xap("xap", "application/x-silverlight-app"),
+        _sit("sit", "application/x-stuffit"),
+        _sitx("sitx", "application/x-stuffitx"),
+        _sv4cpio("sv4cpio", "application/x-sv4cpio"),
+        _sv4crc("sv4crc", "application/x-sv4crc"),
+        _tar("tar", "application/x-tar"),
+        _tcl("tcl", "application/x-tcl"),
+        _tex("tex", "application/x-tex"),
+        _tfm("tfm", "application/x-tex-tfm"),
+        _texinfo("texinfo", "application/x-texinfo"),
+        _ustar("ustar", "application/x-ustar"),
+        _src("src", "application/x-wais-source"),
+        _der("der", "application/x-x509-ca-cert"),
+        _fig("fig", "application/x-xfig"),
+        _xpi("xpi", "application/x-xpinstall"),
+        _xdf("xdf", "application/xcap-diff+xml"),
+        _xenc("xenc", "application/xenc+xml"),
+        _xhtml("xhtml", "application/xhtml+xml"),
+        _xml("xml", "application/xml"),
+        _dtd("dtd", "application/xml-dtd"),
+        _xop("xop", "application/xop+xml"),
+        _xslt("xslt", "application/xslt+xml"),
+        _xspf("xspf", "application/xspf+xml"),
+        _mxml("mxml", "application/xv+xml"),
+        _yang("yang", "application/yang"),
+        _yin("yin", "application/yin+xml"),
+        _zip("zip", "application/zip"),
+        _adp("adp", "audio/adpcm"),
+        _au("au", "audio/basic"),
+        _mid("mid", "audio/midi"),
+        _mp4a("mp4a", "audio/mp4"),
+        _mpga("mpga", "audio/mpeg"),
+        _oga("oga", "audio/ogg"),
+        _uva("uva", "audio/vnd.dece.audio"),
+        _eol("eol", "audio/vnd.digital-winds"),
+        _dra("dra", "audio/vnd.dra"),
+        _dts("dts", "audio/vnd.dts"),
+        _dtshd("dtshd", "audio/vnd.dts.hd"),
+        _lvp("lvp", "audio/vnd.lucent.voice"),
+        _pya("pya", "audio/vnd.ms-playready.media.pya"),
+        _ecelp4800("ecelp4800", "audio/vnd.nuera.ecelp4800"),
+        _ecelp7470("ecelp7470", "audio/vnd.nuera.ecelp7470"),
+        _ecelp9600("ecelp9600", "audio/vnd.nuera.ecelp9600"),
+        _rip("rip", "audio/vnd.rip"),
+        _weba("weba", "audio/webm"),
+        _aac("aac", "audio/x-aac"),
+        _aif("aif", "audio/x-aiff"),
+        _m3u("m3u", "audio/x-mpegurl"),
+        _wax("wax", "audio/x-ms-wax"),
+        _wma("wma", "audio/x-ms-wma"),
+        _ram("ram", "audio/x-pn-realaudio"),
+        _rmp("rmp", "audio/x-pn-realaudio-plugin"),
+        _wav("wav", "audio/x-wav"),
+        _cdx("cdx", "chemical/x-cdx"),
+        _cif("cif", "chemical/x-cif"),
+        _cmdf("cmdf", "chemical/x-cmdf"),
+        _cml("cml", "chemical/x-cml"),
+        _csml("csml", "chemical/x-csml"),
+        _xyz("xyz", "chemical/x-xyz"),
+        _bmp("bmp", "image/bmp"),
+        _cgm("cgm", "image/cgm"),
+        _g3("g3", "image/g3fax"),
+        _gif("gif", "image/gif"),
+        _ief("ief", "image/ief"),
+        _jpeg("jpeg", "image/jpeg"),
+        _jpg("jpg", "image/jpeg"),
+        _pjpeg("pjpeg", "image/pjpeg"),
+        _ktx("ktx", "image/ktx"),
+        _png("png", "image/x-citrix-png"),
+        _btif("btif", "image/prs.btif"),
+        _svg("svg", "image/svg+xml"),
+        _tiff("tiff", "image/tiff"),
+        _psd("psd", "image/vnd.adobe.photoshop"),
+        _uvi("uvi", "image/vnd.dece.graphic"),
+        _sub("sub", "image/vnd.dvb.subtitle"),
+        _djvu("djvu", "image/vnd.djvu"),
+        _dwg("dwg", "image/vnd.dwg"),
+        _dxf("dxf", "image/vnd.dxf"),
+        _fbs("fbs", "image/vnd.fastbidsheet"),
+        _fpx("fpx", "image/vnd.fpx"),
+        _fst("fst", "image/vnd.fst"),
+        _mmr("mmr", "image/vnd.fujixerox.edmics-mmr"),
+        _rlc("rlc", "image/vnd.fujixerox.edmics-rlc"),
+        _mdi("mdi", "image/vnd.ms-modi"),
+        _npx("npx", "image/vnd.net-fpx"),
+        _wbmp("wbmp", "image/vnd.wap.wbmp"),
+        _xif("xif", "image/vnd.xiff"),
+        _webp("webp", "image/webp"),
+        _ras("ras", "image/x-cmu-raster"),
+        _cmx("cmx", "image/x-cmx"),
+        _fh("fh", "image/x-freehand"),
+        _ico("ico", "image/x-icon"),
+        _pcx("pcx", "image/x-pcx"),
+        _pic("pic", "image/x-pict"),
+        _pnm("pnm", "image/x-portable-anymap"),
+        _pbm("pbm", "image/x-portable-bitmap"),
+        _pgm("pgm", "image/x-portable-graymap"),
+        _ppm("ppm", "image/x-portable-pixmap"),
+        _rgb("rgb", "image/x-rgb"),
+        _xbm("xbm", "image/x-xbitmap"),
+        _xpm("xpm", "image/x-xpixmap"),
+        _xwd("xwd", "image/x-xwindowdump"),
+        _eml("eml", "message/rfc822"),
+        _igs("igs", "model/iges"),
+        _msh("msh", "model/mesh"),
+        _dae("dae", "model/vnd.collada+xml"),
+        _dwf("dwf", "model/vnd.dwf"),
+        _gdl("gdl", "model/vnd.gdl"),
+        _gtw("gtw", "model/vnd.gtw"),
+        _mts("mts", "model/vnd.mts"),
+        _vtu("vtu", "model/vnd.vtu"),
+        _wrl("wrl", "model/vrml"),
+        _ics("ics", "text/calendar"),
+        _css("css", "text/css"),
+        _csv("csv", "text/csv"),
+        _html("html", "text/html"),
+        _n3("n3", "text/n3"),
+        _txt("txt", "text/plain"),
+        _dsc("dsc", "text/prs.lines.tag"),
+        _rtx("rtx", "text/richtext"),
+        _sgml("sgml", "text/sgml"),
+        _tsv("tsv", "text/tab-separated-values"),
+        _t("t", "text/troff"),
+        _ttl("ttl", "text/turtle"),
+        _uri("uri", "text/uri-list"),
+        _curl("curl", "text/vnd.curl"),
+        _dcurl("dcurl", "text/vnd.curl.dcurl"),
+        _scurl("scurl", "text/vnd.curl.scurl"),
+        _mcurl("mcurl", "text/vnd.curl.mcurl"),
+        _fly("fly", "text/vnd.fly"),
+        _flx("flx", "text/vnd.fmi.flexstor"),
+        _gv("gv", "text/vnd.graphviz"),
+        _3dml("3dml", "text/vnd.in3d.3dml"),
+        _spot("spot", "text/vnd.in3d.spot"),
+        _jad("jad", "text/vnd.sun.j2me.app-descriptor"),
+        _wml("wml", "text/vnd.wap.wml"),
+        _wmls("wmls", "text/vnd.wap.wmlscript"),
+        _s("s", "text/x-asm"),
+        _c("c", "text/x-c"),
+        _f("f", "text/x-fortran"),
+        _p("p", "text/x-pascal"),
+        _java("java", "text/x-java-source"),
+        _etx("etx", "text/x-setext"),
+        _uu("uu", "text/x-uuencode"),
+        _vcs("vcs", "text/x-vcalendar"),
+        _vcf("vcf", "text/x-vcard"),
+        _3gp("3gp", "video/3gpp"),
+        _3g2("3g2", "video/3gpp2"),
+        _h261("h261", "video/h261"),
+        _h263("h263", "video/h263"),
+        _h264("h264", "video/h264"),
+        _jpgv("jpgv", "video/jpeg"),
+        _jpm("jpm", "video/jpm"),
+        _mj2("mj2", "video/mj2"),
+        _mp4("mp4", "video/mp4"),
+        _mpeg("mpeg", "video/mpeg"),
+        _ogv("ogv", "video/ogg"),
+        _qt("qt", "video/quicktime"),
+        _uvh("uvh", "video/vnd.dece.hd"),
+        _uvm("uvm", "video/vnd.dece.mobile"),
+        _uvp("uvp", "video/vnd.dece.pd"),
+        _uvs("uvs", "video/vnd.dece.sd"),
+        _uvv("uvv", "video/vnd.dece.video"),
+        _fvt("fvt", "video/vnd.fvt"),
+        _mxu("mxu", "video/vnd.mpegurl"),
+        _pyv("pyv", "video/vnd.ms-playready.media.pyv"),
+        _uvu("uvu", "video/vnd.uvvu.mp4"),
+        _viv("viv", "video/vnd.vivo"),
+        _webm("webm", "video/webm"),
+        _f4v("f4v", "video/x-f4v"),
+        _fli("fli", "video/x-fli"),
+        _flv("flv", "video/x-flv"),
+        _m4v("m4v", "video/x-m4v"),
+        _asf("asf", "video/x-ms-asf"),
+        _wm("wm", "video/x-ms-wm"),
+        _wmv("wmv", "video/x-ms-wmv"),
+        _wmx("wmx", "video/x-ms-wmx"),
+        _wvx("wvx", "video/x-ms-wvx"),
+        _avi("avi", "video/x-msvideo"),
+        _movie("movie", "video/x-sgi-movie"),
+        _ice("ice", "x-conference/x-cooltalk"),
+        _par("par", "text/plain-bas"),
+        _yaml("yaml", "text/yaml"),
+        _dmg("dmg", "application/x-apple-diskimage"),
+        _xww("form", "application/x-www-form-urlencoded");
+
+        private final String mime;
+        private final String extension;
+
+        MediaType(String extension, String mime) {
+            this.mime = mime;
+            this.extension = extension;
+        }
+
+        public static MediaType getByExtension(String extension) {
+            for (MediaType type : values()) {
+                if (type.extension.equals(extension)) {
+                    return type;
+                }
+            }
+            return null;
+        }
+
+        public String getMIME() {
+            return mime;
+        }
+
+        public String getExtension() {
+            return extension;
+        }
+    }
+
+}
-- 
GitLab