io.javalin.Javalin Maven / Gradle / Ivy
The newest version!
/*
* Javalin - https://javalin.io
* Copyright 2017 David Åse
* Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
*
*/
package io.javalin;
import io.javalin.apibuilder.ApiBuilder;
import io.javalin.apibuilder.EndpointGroup;
import io.javalin.core.JavalinConfig;
import io.javalin.core.event.EventListener;
import io.javalin.core.event.EventManager;
import io.javalin.core.event.HandlerMetaInfo;
import io.javalin.core.event.JavalinEvent;
import io.javalin.core.event.WsHandlerMetaInfo;
import io.javalin.core.security.AccessManager;
import io.javalin.core.security.RouteRole;
import io.javalin.core.util.JavalinBindException;
import io.javalin.core.util.JavalinException;
import io.javalin.core.util.JavalinLogger;
import io.javalin.core.util.Util;
import io.javalin.core.validation.JavalinValidation;
import io.javalin.http.Context;
import io.javalin.http.ErrorMapperKt;
import io.javalin.http.ExceptionHandler;
import io.javalin.http.Handler;
import io.javalin.http.HandlerType;
import io.javalin.http.JavalinServlet;
import io.javalin.http.sse.SseClient;
import io.javalin.http.sse.SseHandler;
import io.javalin.jetty.JavalinJettyServlet;
import io.javalin.jetty.JettyServer;
import io.javalin.jetty.JettyUtil;
import io.javalin.websocket.WsConfig;
import io.javalin.websocket.WsExceptionHandler;
import io.javalin.websocket.WsHandlerType;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import org.eclipse.jetty.server.Server;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings("unchecked")
public class Javalin implements AutoCloseable {
/**
* Do not use this field unless you know what you're doing.
* Application config should be declared in {@link Javalin#create(Consumer)}
*/
public JavalinConfig _conf = new JavalinConfig();
protected JettyServer jettyServer; // null in standalone-mode
protected JavalinJettyServlet javalinJettyServlet; // null in standalone-mode
protected JavalinServlet javalinServlet = new JavalinServlet(_conf);
protected EventManager eventManager = new EventManager();
protected Javalin() {
this.jettyServer = new JettyServer(_conf);
this.javalinJettyServlet = new JavalinJettyServlet(_conf, javalinServlet);
}
public Javalin(JettyServer jettyServer, JavalinJettyServlet jettyServlet) {
this.jettyServer = jettyServer;
this.javalinJettyServlet = jettyServlet;
}
/**
* Creates a new instance without any custom configuration.
*
* @see Javalin#create(Consumer)
*/
public static Javalin create() {
return create(config -> {
});
}
/**
* Creates a new instance with the user provided configuration.
* The server does not run until {@link Javalin#start()} is called.
*
* @return application instance.
* @see Javalin#start()
* @see Javalin#start(int)
*/
public static Javalin create(Consumer config) {
Javalin app = new Javalin();
JavalinValidation.addValidationExceptionMapper(app);
JavalinConfig.applyUserConfig(app, app._conf, config); // mutates app.config and app (adds http-handlers)
JettyUtil.maybeLogIfServerNotStarted(app.jettyServer);
return app;
}
// Create a standalone (non-jetty dependent) Javalin with the supplied config
public static Javalin createStandalone(Consumer config) {
Javalin app = new Javalin(null, null);
JavalinConfig.applyUserConfig(app, app._conf, config); // mutates app.config and app (adds http-handlers)
return app;
}
// Create a standalone (non-jetty dependent) Javalin
public static Javalin createStandalone() {
return createStandalone(config -> {
});
}
// Get JavalinServlet (for use in standalone mode)
public JavalinServlet javalinServlet() {
return this.javalinServlet;
}
// Get the JavalinServer
@Nullable
public JettyServer jettyServer() {
return this.jettyServer;
}
/**
* Synchronously starts the application instance on the specified port
* with the given host IP to bind to.
*
* @param host The host IP to bind to
* @param port to run on
* @return running application instance.
* @see Javalin#create()
* @see Javalin#start()
*/
public Javalin start(String host, int port) {
jettyServer.setServerHost(host);
return start(port);
}
/**
* Synchronously starts the application instance on the specified port.
* Use port 0 to start the application instance on a random available port.
*
* @param port to run on
* @return running application instance.
* @see Javalin#create()
* @see Javalin#start()
*/
public Javalin start(int port) {
jettyServer.setServerPort(port);
return start();
}
/**
* Synchronously starts the application instance on the configured port, or on
* the configured ServerConnectors if the Jetty server has been manually configured.
* If no port or connector is configured, the instance will start on port 8080.
*
* @return running application instance.
* @see Javalin#create()
*/
public Javalin start() {
Util.logJavalinBanner(this._conf.showJavalinBanner);
JettyUtil.disableJettyLogger();
long startupTimer = System.currentTimeMillis();
if (jettyServer.started) {
String message = "Server already started. If you are trying to call start() on an instance " +
"of Javalin that was stopped using stop(), please create a new instance instead.";
throw new IllegalStateException(message);
}
jettyServer.started = true;
Util.printHelpfulMessageIfLoggerIsMissing();
eventManager.fireEvent(JavalinEvent.SERVER_STARTING);
try {
JavalinLogger.startup("Starting Javalin ...");
Util.logJavalinVersion();
jettyServer.start(javalinJettyServlet);
JavalinLogger.startup("Javalin started in " + (System.currentTimeMillis() - startupTimer) + "ms \\o/");
eventManager.fireEvent(JavalinEvent.SERVER_STARTED);
} catch (Exception e) {
JavalinLogger.error("Failed to start Javalin");
eventManager.fireEvent(JavalinEvent.SERVER_START_FAILED);
if (Boolean.TRUE.equals(jettyServer.server().getAttribute("is-default-server"))) {
stop();// stop if server is default server; otherwise, the caller is responsible to stop
}
if (e.getMessage() != null && e.getMessage().contains("Failed to bind to")) {
throw new JavalinBindException("Port already in use. Make sure no other process is using port " + Util.getPort(e) + " and try again.", e);
} else if (e.getMessage() != null && e.getMessage().contains("Permission denied")) {
throw new JavalinBindException("Port 1-1023 require elevated privileges (process must be started by admin).", e);
}
throw new JavalinException(e);
}
return this;
}
/**
* Synchronously stops the application instance.
*
* Recommended to use {@link Javalin#close} instead with Java's try-with-resources
* or Kotlin's {@code use}. This differs from {@link Javalin#close} by
* firing lifecycle events even if the server is stopping or already stopped.
* This could cause your listeners to observe nonsensical state transitions.
* E.g. started -> stopping -> stopped -> stopping -> stopped.
*
* @return stopped application instance.
* @see Javalin#close()
*/
public Javalin stop() {
JavalinLogger.info("Stopping Javalin ...");
eventManager.fireEvent(JavalinEvent.SERVER_STOPPING);
try {
jettyServer.server().stop();
} catch (Exception e) {
eventManager.fireEvent(JavalinEvent.SERVER_STOP_FAILED);
JavalinLogger.error("Javalin failed to stop gracefully", e);
throw new JavalinException(e);
}
JavalinLogger.info("Javalin has stopped");
eventManager.fireEvent(JavalinEvent.SERVER_STOPPED);
return this;
}
/**
* Synchronously stops the application instance.
*
* Can safely be called multiple times.
*/
@Override
public void close() {
final Server server = jettyServer.server();
if (server.isStopping() || server.isStopped()) {
return;
}
stop();
}
public Javalin events(Consumer listener) {
EventListener eventListener = new EventListener(this.eventManager);
listener.accept(eventListener);
return this;
}
/**
* Get which port instance is running on
* Mostly useful if you start the instance with port(0) (random port)
*/
public int port() {
return jettyServer.getServerPort();
}
/**
* Registers an attribute on the instance.
* Instance is available on the {@link Context} through {@link Context#appAttribute}.
* Ex: app.attribute(MyExt.class, myExtInstance())
* The method must be called before {@link Javalin#start()}.
*/
public Javalin attribute(String key, Object value) {
_conf.inner.appAttributes.put(key, value);
return this;
}
/**
* Retrieve an attribute stored on the instance.
* Available on the {@link Context} through {@link Context#appAttribute}.
* Ex: app.attribute(MyExt.class).myMethod()
* Ex: ctx.appAttribute(MyExt.class).myMethod()
*/
public T attribute(String key) {
return (T) _conf.inner.appAttributes.get(key);
}
/**
* Creates a temporary static instance in the scope of the endpointGroup.
* Allows you to call get(handler), post(handler), etc. without without using the instance prefix.
*
* @see Handler groups in documentation
* @see ApiBuilder
*/
public Javalin routes(@NotNull EndpointGroup endpointGroup) {
ApiBuilder.setStaticJavalin(this);
try {
endpointGroup.addEndpoints();
} finally {
ApiBuilder.clearStaticJavalin();
}
return this;
}
// ********************************************************************************************
// HTTP
// ********************************************************************************************
/**
* Adds an exception mapper to the instance.
*
* @see Exception mapping in docs
*/
public Javalin exception(@NotNull Class exceptionClass, @NotNull ExceptionHandler super T> exceptionHandler) {
javalinServlet.getExceptionMapper().getHandlers().put(exceptionClass, (ExceptionHandler) exceptionHandler);
return this;
}
/**
* Adds an error mapper to the instance.
* Useful for turning error-codes (404, 500) into standardized messages/pages
*
* @see Error mapping in docs
*/
public Javalin error(int statusCode, @NotNull Handler handler) {
javalinServlet.getErrorMapper().getErrorHandlerMap().put(statusCode, handler);
return this;
}
/**
* Adds an error mapper for the specified content-type to the instance.
* Useful for turning error-codes (404, 500) into standardized messages/pages
*
* @see Error mapping in docs
*/
public Javalin error(int statusCode, @NotNull String contentType, @NotNull Handler handler) {
return error(statusCode, ErrorMapperKt.contentTypeWrap(contentType, handler));
}
/**
* Adds a request handler for the specified handlerType and path to the instance.
* Requires an access manager to be set on the instance.
* This is the method that all the verb-methods (get/post/put/etc) call.
*
* @see AccessManager
* @see Handlers in docs
*/
public Javalin addHandler(@NotNull HandlerType handlerType, @NotNull String path, @NotNull Handler handler, @NotNull RouteRole... roles) {
Set roleSet = new HashSet<>(Arrays.asList(roles));
javalinServlet.addHandler(handlerType, path, handler, roleSet);
eventManager.fireHandlerAddedEvent(new HandlerMetaInfo(handlerType, Util.prefixContextPath(javalinServlet.getConfig().contextPath, path), handler, roleSet));
return this;
}
/**
* Adds a request handler for the specified handlerType and path to the instance.
* This is the method that all the verb-methods (get/post/put/etc) call.
*
* @see Handlers in docs
*/
public Javalin addHandler(@NotNull HandlerType httpMethod, @NotNull String path, @NotNull Handler handler) {
return addHandler(httpMethod, path, handler, new RouteRole[0]); // no roles set for this route (open to everyone with default access manager)
}
/**
* Adds a GET request handler for the specified path to the instance.
*
* @see Handlers in docs
*/
public Javalin get(@NotNull String path, @NotNull Handler handler) {
return addHandler(HandlerType.GET, path, handler);
}
/**
* Adds a POST request handler for the specified path to the instance.
*
* @see Handlers in docs
*/
public Javalin post(@NotNull String path, @NotNull Handler handler) {
return addHandler(HandlerType.POST, path, handler);
}
/**
* Adds a PUT request handler for the specified path to the instance.
*
* @see Handlers in docs
*/
public Javalin put(@NotNull String path, @NotNull Handler handler) {
return addHandler(HandlerType.PUT, path, handler);
}
/**
* Adds a PATCH request handler for the specified path to the instance.
*
* @see Handlers in docs
*/
public Javalin patch(@NotNull String path, @NotNull Handler handler) {
return addHandler(HandlerType.PATCH, path, handler);
}
/**
* Adds a DELETE request handler for the specified path to the instance.
*
* @see Handlers in docs
*/
public Javalin delete(@NotNull String path, @NotNull Handler handler) {
return addHandler(HandlerType.DELETE, path, handler);
}
/**
* Adds a HEAD request handler for the specified path to the instance.
*
* @see Handlers in docs
*/
public Javalin head(@NotNull String path, @NotNull Handler handler) {
return addHandler(HandlerType.HEAD, path, handler);
}
/**
* Adds a OPTIONS request handler for the specified path to the instance.
*
* @see Handlers in docs
*/
public Javalin options(@NotNull String path, @NotNull Handler handler) {
return addHandler(HandlerType.OPTIONS, path, handler);
}
// ********************************************************************************************
// Secured HTTP verbs
// ********************************************************************************************
/**
* Adds a GET request handler with the given roles for the specified path to the instance.
* Requires an access manager to be set on the instance.
*
* @see AccessManager
* @see Handlers in docs
*/
public Javalin get(@NotNull String path, @NotNull Handler handler, @NotNull RouteRole... roles) {
return addHandler(HandlerType.GET, path, handler, roles);
}
/**
* Adds a POST request handler with the given roles for the specified path to the instance.
* Requires an access manager to be set on the instance.
*
* @see AccessManager
* @see Handlers in docs
*/
public Javalin post(@NotNull String path, @NotNull Handler handler, @NotNull RouteRole... roles) {
return addHandler(HandlerType.POST, path, handler, roles);
}
/**
* Adds a PUT request handler with the given roles for the specified path to the instance.
* Requires an access manager to be set on the instance.
*
* @see AccessManager
* @see Handlers in docs
*/
public Javalin put(@NotNull String path, @NotNull Handler handler, @NotNull RouteRole... roles) {
return addHandler(HandlerType.PUT, path, handler, roles);
}
/**
* Adds a PATCH request handler with the given roles for the specified path to the instance.
* Requires an access manager to be set on the instance.
*
* @see AccessManager
* @see Handlers in docs
*/
public Javalin patch(@NotNull String path, @NotNull Handler handler, @NotNull RouteRole... roles) {
return addHandler(HandlerType.PATCH, path, handler, roles);
}
/**
* Adds a DELETE request handler with the given roles for the specified path to the instance.
* Requires an access manager to be set on the instance.
*
* @see AccessManager
* @see Handlers in docs
*/
public Javalin delete(@NotNull String path, @NotNull Handler handler, @NotNull RouteRole... roles) {
return addHandler(HandlerType.DELETE, path, handler, roles);
}
/**
* Adds a HEAD request handler with the given roles for the specified path to the instance.
* Requires an access manager to be set on the instance.
*
* @see AccessManager
* @see Handlers in docs
*/
public Javalin head(@NotNull String path, @NotNull Handler handler, @NotNull RouteRole... roles) {
return addHandler(HandlerType.HEAD, path, handler, roles);
}
/**
* Adds a OPTIONS request handler with the given roles for the specified path to the instance.
* Requires an access manager to be set on the instance.
*
* @see AccessManager
* @see Handlers in docs
*/
public Javalin options(@NotNull String path, @NotNull Handler handler, @NotNull RouteRole... roles) {
return addHandler(HandlerType.OPTIONS, path, handler, roles);
}
// ********************************************************************************************
// Server-sent events
// ********************************************************************************************
/**
* Adds a lambda handler for a Server Sent Event connection on the specified path.
*/
public Javalin sse(@NotNull String path, @NotNull Consumer client) {
return sse(path, client, new RouteRole[0]);
}
/**
* Adds a lambda handler for a Server Sent Event connection on the specified path.
*/
public Javalin sse(@NotNull String path, @NotNull SseHandler handler) {
return get(path, handler);
}
/**
* Adds a lambda handler for a Server Sent Event connection on the specified path.
* Requires an access manager to be set on the instance.
*/
public Javalin sse(@NotNull String path, @NotNull Consumer client, @NotNull RouteRole... roles) {
return get(path, new SseHandler(client), roles);
}
// ********************************************************************************************
// Before/after handlers (filters)
// ********************************************************************************************
/**
* Adds a BEFORE request handler for the specified path to the instance.
*
* @see Handlers in docs
*/
public Javalin before(@NotNull String path, @NotNull Handler handler) {
return addHandler(HandlerType.BEFORE, path, handler);
}
/**
* Adds a BEFORE request handler for all routes in the instance.
*
* @see Handlers in docs
*/
public Javalin before(@NotNull Handler handler) {
return before("*", handler);
}
/**
* Adds an AFTER request handler for the specified path to the instance.
*
* @see Handlers in docs
*/
public Javalin after(@NotNull String path, @NotNull Handler handler) {
return addHandler(HandlerType.AFTER, path, handler);
}
/**
* Adds an AFTER request handler for all routes in the instance.
*
* @see Handlers in docs
*/
public Javalin after(@NotNull Handler handler) {
return after("*", handler);
}
// ********************************************************************************************
// WebSocket
// ********************************************************************************************
/**
* Adds a WebSocket exception mapper to the instance.
*
* @see Exception mapping in docs
*/
public Javalin wsException(@NotNull Class exceptionClass, @NotNull WsExceptionHandler super T> exceptionHandler) {
javalinJettyServlet.getWsExceptionMapper().getHandlers().put(exceptionClass, (WsExceptionHandler) exceptionHandler);
return this;
}
/**
* Adds a specific WebSocket handler for the given path to the instance.
* Requires an access manager to be set on the instance.
*/
private Javalin addWsHandler(@NotNull WsHandlerType handlerType, @NotNull String path, @NotNull Consumer wsConfig, @NotNull RouteRole... roles) {
Set roleSet = new HashSet<>(Arrays.asList(roles));
javalinJettyServlet.addHandler(handlerType, path, wsConfig, roleSet);
eventManager.fireWsHandlerAddedEvent(new WsHandlerMetaInfo(handlerType, Util.prefixContextPath(javalinServlet.getConfig().contextPath, path), wsConfig, roleSet));
return this;
}
/**
* Adds a specific WebSocket handler for the given path to the instance.
*/
private Javalin addWsHandler(@NotNull WsHandlerType handlerType, @NotNull String path, @NotNull Consumer wsConfig) {
return addWsHandler(handlerType, path, wsConfig, new RouteRole[0]);
}
/**
* Adds a WebSocket handler on the specified path.
*
* @see WebSockets in docs
*/
public Javalin ws(@NotNull String path, @NotNull Consumer ws) {
return ws(path, ws, new RouteRole[0]);
}
/**
* Adds a WebSocket handler on the specified path with the specified roles.
* Requires an access manager to be set on the instance.
*
* @see AccessManager
* @see WebSockets in docs
*/
public Javalin ws(@NotNull String path, @NotNull Consumer ws, @NotNull RouteRole... roles) {
return addWsHandler(WsHandlerType.WEBSOCKET, path, ws, roles);
}
/**
* Adds a WebSocket before handler for the specified path to the instance.
*/
public Javalin wsBefore(@NotNull String path, @NotNull Consumer wsConfig) {
return addWsHandler(WsHandlerType.WS_BEFORE, path, wsConfig);
}
/**
* Adds a WebSocket before handler for all routes in the instance.
*/
public Javalin wsBefore(@NotNull Consumer wsConfig) {
return wsBefore("*", wsConfig);
}
/**
* Adds a WebSocket after handler for the specified path to the instance.
*/
public Javalin wsAfter(@NotNull String path, @NotNull Consumer wsConfig) {
return addWsHandler(WsHandlerType.WS_AFTER, path, wsConfig);
}
/**
* Adds a WebSocket after handler for all routes in the instance.
*/
public Javalin wsAfter(@NotNull Consumer wsConfig) {
return wsAfter("*", wsConfig);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy