How can I deploy a WAR in an embedded Jetty 8? - java

With the following code, how can I deploy a WAR application located on the classpath ?
private Server s;
#BeforeClass
public static void setUp() throws Exception {
// Start http server
Random r = new Random();
int port = 1024 + r.nextInt(8976);
s = new Server(new InetSocketAddress("127.0.0.1", port));
// Add my WAR for deployment here ...
s.start();
}
Jetty 8.0.1
JDK 6

Something like
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
webapp.setWar(warURL);
server.setHandler(webapp);
The war does not have to be on the class path.

Related

Init servlet instantly after the embedded jetty server starts

I need to run my own logic after the jetty embedded server starts. I'm not starting it from the main class due to classloader issues. An ideal solution seemed to be running my server logic from a servlet initialization. But the init function and also the constructor is not called after the jetty server start. An instance of the servlet is being created during the first HTTP request. Is it possible to tell jetty to initialize my servlet instantly or do I really need to load all classes with my custom classloader and then start the jetty server?
This is the main class:
public class ServerLauncher {
public static void main(String[] args) {
JettyServerLauncher.launchHttp("target/server.war", "0.0.0.0", 8080);
// Starting my own logic here is causing classloader issues, because WebSocket classes are loaded by other classloader than my classes, that is the reason why I moved it into the servlet
}
}
This is my jetty embedded server launcher:
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.*;
import java.io.File;
public class JettyServerLauncher {
private static boolean isHttps;
private static File keyStoreFile;
private static String warPath;
private static String host;
private static int httpPort;
private static int httpsPort;
private static String keyStorePath;
private static String keyStorePass;
private static boolean needClientAuth;
public static void launchHttp(String warPath, String host, int httpPort) {
JettyServerLauncher.isHttps = false;
JettyServerLauncher.warPath = warPath;
JettyServerLauncher.host = host;
JettyServerLauncher.httpPort = httpPort;
launch();
}
public static void launchHttps(String warPath, String host, String keyStorePath, String keyStorePass, int httpPort, int httpsPort, boolean needClientAuth) {
JettyServerLauncher.isHttps = true;
JettyServerLauncher.warPath = warPath;
JettyServerLauncher.host = host;
JettyServerLauncher.httpPort = httpPort;
JettyServerLauncher.httpsPort = httpsPort;
JettyServerLauncher.keyStorePath = keyStorePath;
JettyServerLauncher.keyStorePass = keyStorePass;
JettyServerLauncher.needClientAuth = needClientAuth;
launch();
}
private static void launch() {
Server server = null;
try {
System.out.println("Initializing jetty server...");
if (isHttps) loadKeyStores(keyStorePath);
// Create jetty server
server = new Server(httpPort);
// Setup connectors
Connector httpConnector = createHttpConnector(server, host, httpPort, httpsPort);
if (isHttps) {
Connector httpsConnector = createHttpsConnector(server, host, httpsPort, keyStoreFile, keyStorePass, needClientAuth);
server.setConnectors(new Connector[]{httpConnector, httpsConnector});
} else {
server.setConnectors(new Connector[]{httpConnector});
}
// Add handlers for requests to collection of handlers
HandlerCollection handlers = new ContextHandlerCollection();
//handlers.addHandler(new SecuredRedirectHandler());
handlers.addHandler(createWebApp(warPath));
server.setHandler(handlers);
server.dump();
System.out.println("Starting jetty websocket and web server...");
server.start();
server.join();
} catch (Throwable t) {
t.printStackTrace();
System.err.println("Server initialization failed!");
System.out.println("Stopping the server...");
try {
server.stop();
} catch (Exception ignored) {}
}
}
private static WebAppContext createWebApp(String warPath) {
WebAppContext webApp = new WebAppContext();
webApp.setContextPath("/");
webApp.setWar(new File(warPath).getAbsolutePath());
webApp.setThrowUnavailableOnStartupException(true);
// Enable support for JSR-356 javax.websocket
webApp.setAttribute("org.eclipse.jetty.websocket.jsr356", Boolean.TRUE);
// Jetty will scan project for configuration files... This is very important for loading websocket endpoints by annotation automatically
webApp.setConfigurations(new Configuration[] {
new AnnotationConfiguration(),
new WebInfConfiguration(),
new WebXmlConfiguration(),
new MetaInfConfiguration(),
new FragmentConfiguration(),
new EnvConfiguration(),
new PlusConfiguration(),
new JettyWebXmlConfiguration()
});
return webApp;
}
private static Connector createHttpConnector(Server server, String host, int httpPort, int httpsPort) {
HttpConfiguration httpConf = new HttpConfiguration();
httpConf.setSendServerVersion(false);
if (isHttps) httpConf.setSecurePort(httpsPort);
ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConf));
connector.setPort(httpPort);
connector.setHost(host);
return connector;
}
private static Connector createHttpsConnector(Server server, String host, int httpsPort, File keyStoreFile, String keyStorePass, boolean needClientAuth) {
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keyStoreFile.getAbsolutePath());
sslContextFactory.setKeyStorePassword(keyStorePass);
sslContextFactory.setNeedClientAuth(needClientAuth);
// Setup HTTPS Configuration
HttpConfiguration httpsConf = new HttpConfiguration();
httpsConf.setSendServerVersion(false);
httpsConf.setSecureScheme("https");
httpsConf.setSecurePort(httpsPort);
httpsConf.setOutputBufferSize(32768);
httpsConf.setRequestHeaderSize(8192);
httpsConf.setResponseHeaderSize(8192);
httpsConf.addCustomizer(new SecureRequestCustomizer()); // adds ssl info to request object
// Establish the HTTPS ServerConnector
ServerConnector httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), new HttpConnectionFactory(httpsConf));
httpsConnector.setPort(httpsPort);
httpsConnector.setHost(host);
return httpsConnector;
}
private static void loadKeyStores(String keyStorePath) {
keyStoreFile = new File(keyStorePath);
if (!keyStoreFile.exists()) {
throw new RuntimeException("Key store file does not exist on path '"+keyStoreFile.getAbsolutePath()+"'");
}
}
}
This is my servlet:
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#WebServlet(displayName = "MyServlet", urlPatterns = { "/*" })
public class MyServlet extends HttpServlet {
#Override
public void init() {
// start new Thread with my server logic here (avoid classloader issues)
// but at least one HTTP request is needed to start it from this place
}
#Override
public void destroy() {}
#Override
public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
// handle http requests
}
}
I found this on google, but I don't know how to use it in my case. https://www.eclipse.org/lists/jetty-users/msg02109.html
Thank you for your help.
If you just want the servlet to init on startup, then use the annotation ...
#WebServlet(
displayName = "MyServlet",
urlPatterns = { "/*" },
loadOnStartup = 1
)
Alternatively, you could register a javax.servlet.ServletContextListener that does the contextInitialized(ServletContextEvent sce) behavior you need.
Tip: if you define a custom the ServletContextListener for embedded use, you can just add it to the WebAppContext from outside of the WAR you are using.
Example:
webApp.getServletHandler()
.addListener(new ListenerHolder(MyContextListener.class));
Also, this block of code is wrong and shows you copy/pasted from an old code snippet (this technique is from circa Jetty 9.0.0 thru 9.2.16)
webApp.setConfigurations(new Configuration[] {
new AnnotationConfiguration(),
new WebInfConfiguration(),
new WebXmlConfiguration(),
new MetaInfConfiguration(),
new FragmentConfiguration(),
new EnvConfiguration(),
new PlusConfiguration(),
new JettyWebXmlConfiguration()
});
In Jetty 9.4.x you never directly configure the webApp.setConfigurations() like that, use the Configuration.ClassList defined on the server instead ...
From: 9.4.44.v20210927 - embedded/LikeJettyXml.java
Configuration.ClassList classlist = Configuration.ClassList
.setServerDefault(server);
classlist.addAfter(
"org.eclipse.jetty.webapp.FragmentConfiguration",
"org.eclipse.jetty.plus.webapp.EnvConfiguration",
"org.eclipse.jetty.plus.webapp.PlusConfiguration");
classlist.addBefore(
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
"org.eclipse.jetty.annotations.AnnotationConfiguration");
Starting in Jetty 10.0.0, you never specify the Configuration classes, or their order, as the existence of the support JAR is enough, and internally in Jetty 10 the order is resolved properly.
But if you need to add Configurations (due to non-standard deployment concerns where the Java ServiceLoader doesn't work), then you still configure the additional Configurations on the server object (but without worrying about the correct order for those configurations)
From 10.0.7 - embedded/demos/LikeJettyXml.java
Configurations.setServerDefault(server).add(
new EnvConfiguration(),
new PlusConfiguration(),
new AnnotationConfiguration()
);

Java web.xml location for embedded jetty

I'm trying to understand the way we should configure the web application.
Now i have a simple gradle project with embedded jetty
Dependencies:
dependencies {
compile('org.eclipse.jetty:jetty-servlet:9.3.10.v20160621')
compile('org.eclipse.jetty:jetty-webapp:9.3.10.v20160621')
testCompile group: 'junit', name: 'junit', version: '4.11'
}
Application main:
package test;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
public class App {
public static void main(String[] args) throws Exception {
System.out.println(">> Running");
WebAppContext webAppContext = new WebAppContext();
webAppContext.setDescriptor("src/main/resources/WEB-INF/web.xml");
webAppContext.setResourceBase("/");
webAppContext.setContextPath("/");
Server server = new Server(8080);
server.setHandler(webAppContext);
server.start();
server.join();
}
}
In web.xml I defined only ServletContextListener implementation to find if it was catched with application.
My problem is: webAppContext.setDescriptor("src/main/resources/WEB-INF/web.xml")
Jetty can find web.xml only with this weird location path.
Why do I need to target it from project folder?
If I run jar task with gradle the wouldn't be any src directory inside the jar.
Is exist a way to something like: App.class.getResource("/WEB-INF/web.xml") and load web.xml related to classpath?
Seems it was some class loaders issue.
After some further searches I ended with next solution:
public class App {
private static final String WEBAPP_RESOURCES_LOCATION = "webapp";
public static void main(String[] args) throws Exception {
System.out.println(">> Running");
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/");
URL webAppDir = Thread.currentThread().getContextClassLoader().getResource(WEBAPP_RESOURCES_LOCATION);
webAppContext.setResourceBase(webAppDir.toURI().toString());
// if setDescriptor set null or don't used jetty looking for /WEB-INF/web.xml under resource base
// webAppContext.setDescriptor(webAppDir.toURI().toString() + "web.xml");
Server server = new Server(8080);
server.setHandler(webAppContext);
server.start();
server.join();
}
}
An the layout:
Thanks github user arey for the examle examle
your web.xml should be in
src/main/webapp/WEB-INF
UPD: sorry, pressed submit before finalising the post.
above works for me and then I can run the test like:
Server server; //jetty server
private static Integer portNum = 9999;
private static String ENDPOINT_URL = "http://localhost:" + portNum + "/appName/";
#Before
public void startJetty() throws Exception{
server = new Server(portNum);
server.setStopAtShutdown(true);
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/appName");
webAppContext.setResourceBase("src/main/webapp");
webAppContext.setClassLoader(getClass().getClassLoader());
server.setHandler(webAppContext);
server.start();
}
#After
public void stopJetty(){
try {
server.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
#Test
public void serverNotNull(){
assertNotNull("jetty must be initialised", server);
}

two contexts one server jetty

i have two jetty embedded servers,
localhost:9001/WebApp1 and localhost:9002/WebApp2,
as you can see they're on different ports.
I'd like them to share the same port during creation of server
is it possible? (BTW they are two separate jar files as well).
so can i do something like this instead
localhost:9001/WebApp1 and localhost:9001/WebApp2
or am I stuck with producing war files then having them
contained by a tomcat/glassfish server
during creation of server i usually see this
ContextHandler context = new ContextHandler();
context.setContextPath("/WebApp1");
context.setHandler(new WebApp1());
Server server = new Server(9001);
server.setHandler(context);
server.start();
server.join();
on second app i'd like to have something that looks like this
ContextHandler context = new ContextHandler();
context.setContextPath("/WebApp2");
context.setHandler(new WebApp2());
Server server = getExistingServer(9001);
server.addHandler(context);
i see that there is such method server.getHandlers(); which returns an array of handlers how do i add new handler to the existing list, or get the existing jetty server running at port 9001
Jetty is a standard servlet container and can of course handle different contexts.
See section Embedding Contexts in Chapter 24 of the Jetty documentation.
Here is the ManyContexts example (part of Jetty docs):
public class ManyContexts
{
public static void main( String[] args ) throws Exception
{
Server server = new Server(8080);
ContextHandler context = new ContextHandler("/");
context.setContextPath("/");
context.setHandler(new HelloHandler("Root Hello"));
ContextHandler contextFR = new ContextHandler("/fr");
contextFR.setHandler(new HelloHandler("Bonjoir"));
ContextHandler contextIT = new ContextHandler("/it");
contextIT.setHandler(new HelloHandler("Bongiorno"));
ContextHandler contextV = new ContextHandler("/");
contextV.setVirtualHosts(new String[] { "127.0.0.2" });
contextV.setHandler(new HelloHandler("Virtual Hello"));
ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.setHandlers(new Handler[] { context, contextFR, contextIT, contextV });
server.setHandler(contexts);
server.start();
server.join();
}
}

How to gracefully shutdown embeded jetty

I have an application runs on an embedded jetty server. Now i want to start/stop the server as a service.
I use a script to start the server.
java $JAVA_OPTS -DREQ_JAVA_VERSION=$JAVA_VERSION -jar myjetty.jar
Main Class
Server server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(PORT);
server.addConnector(connector);
HandlerCollection handlers = new HandlerCollection();
NCSARequestLog requestLog = new NCSARequestLog();
requestLog.setFilename(home + "/logs/access_" + logFileDateFormat
+ ".log");
requestLog.setFilenameDateFormat(logFileDateFormat);
requestLog.setRetainDays(10);
requestLog.setAppend(true);
requestLog.setExtended(false);
requestLog.setLogCookies(false);
requestLog.setLogTimeZone(TimeZone.getDefault().getID());
RequestLogHandler requestLogHandler = new RequestLogHandler();
requestLogHandler.setRequestLog(requestLog);
handlers.addHandler(requestLogHandler);
server.setHandler(handlers);
server.start();
server.join();
This starts the server.Stopping and/or Restarting an embedded Jetty instance via web call can be used to stop server but,
How to stop the server from the script? and what changes should i make to shout down server in the main class.
Since Jetty 7.5.x you can use org.eclipse.jetty.server.handler.ShutdownHandler in your code:
Server server = new Server(8080);
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[]
{ someOtherHandler, new ShutdownHandler("secret_password", false, true) });
server.setHandler(handlers);
server.start();
... which will allow you to shut down your jetty by issuing the following http POST request:
curl -X POST http://localhost:8080/shutdown?token=secret_password
You can call setStopTimeout(long timeout) to shutdown Jetty in a relatively graceful way. A statisticsHandler must be configured when calling this method.
Referencing: Jetty Server.class setStopTimeout(long)
e.g.
YourServletHandler servletHandler = new YourServletHandler();
StatisticsHandler statsHandler = new StatisticsHandler();
statsHandler.setHandler(servletHandler);
Server server = new Server(80);
server.setHandler(statsHandler);
server.setStopTimeout(3000L);
//...
server.start();
//...
server.stop();
There is no predefined solution to shut-down the Jetty server. The only ordered way to shut-down the Jetty server is to call the method stop() on the running server instance. You must implement the way how this method is called yourself.
You could achieve this (for example) by...
implementing an RMI server thread and invoke the method from a RMI client
implementing a JMX MBean and from a client call a method on that MBean
implementing a custom handler like described in the link you have posted
If you only want to find a way which does not depend on additional tools like curl, than you could solve it for example like below (it's your own code with small modifications)
public class MyJetty {
public static void main(String[] args) throws Exception {
int PORT = 9103;
String home = System.getProperty("user.home");
String logFileDateFormat = "yyyy_MM_dd";
// execute a request to http://localhost:9103/stop
// instead of `curl -v http://localhost:9103/stop`
if (args.length == 1 && "stop".equalsIgnoreCase(args[0])) {
URL url = new URL("http", "localhost", PORT, "/stop");
try (InputStream in = url.openStream()) {
int r;
while ((r = in.read()) != -1) {
System.out.write(r);
}
return;
} catch (IOException ex) {
System.err.println("stop Jetty failed: " + ex.getMessage());
}
}
Server server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(PORT);
server.addConnector(connector);
HandlerCollection handlers = new HandlerCollection();
NCSARequestLog requestLog = new NCSARequestLog();
requestLog.setFilename(home + "/logs/access_" + logFileDateFormat + ".log");
requestLog.setFilenameDateFormat(logFileDateFormat);
requestLog.setRetainDays(10);
requestLog.setAppend(true);
requestLog.setExtended(false);
requestLog.setLogCookies(false);
requestLog.setLogTimeZone(TimeZone.getDefault().getID());
RequestLogHandler requestLogHandler = new RequestLogHandler();
requestLogHandler.setRequestLog(requestLog);
handlers.addHandler(requestLogHandler);
// the class YourHandler is the one from your link
handlers.addHandler(new YourHandler(server));
server.setHandler(handlers);
server.start();
server.join();
}
}
start the server with java MyJetty
stop the server with java MyJetty stop
I don't know why (or if it is a bug) but in my case I had to set shutdownAtStart argument to false to get it working. If I set it as true the Server connector never starts, so it doesn't attend external requests like http://localhost:8888/shutdown?token=secret
new ShutdownHandler("secret", false, false);

jetty server 9.1 multiple embeded ports and application in same server instance

I should use one Server object, and need to open multiple ports and multiple application(WAR files).
Ex, one server object,
8080 addition.war
8081 subraction.war
etc.
I'm using Jetty server 9.1.0
How can I do this?
To accomplish this, you need:
Each ServerConnector should have a unique name declared via ServerConnector.setName(String)
When you define your WebAppContext, declare a set of virtual hosts that take a named virtual host syntax "#{name}", where the {name} is the same one you chose for the connector. (Note: A virtualhost without the "#" sign is a traditional virtualhost based on hostnames)
Like this ...
package jetty.demo;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.webapp.WebAppContext;
public class ConnectorSpecificContexts
{
public static void main(String[] args)
{
Server server = new Server();
ServerConnector connectorA = new ServerConnector(server);
connectorA.setPort(8080);
connectorA.setName("connA"); // connector name A
ServerConnector connectorB = new ServerConnector(server);
connectorB.setPort(9090);
connectorB.setName("connB"); // connector name B
server.addConnector(connectorA);
server.addConnector(connectorB);
// Basic handler collection
HandlerCollection contexts = new HandlerCollection();
server.setHandler(contexts);
// WebApp A
WebAppContext appA = new WebAppContext();
appA.setContextPath("/a");
appA.setWar("./webapps/webapp-a.war");
appA.setVirtualHosts(new String[]{"#connA"}); // connector name A
contexts.addHandler(appA);
// WebApp B
WebAppContext appB = new WebAppContext();
appB.setContextPath("/b");
appB.setWar("./webapps/webapp-b.war");
appB.setVirtualHosts(new String[]{"#connB"}); // connector name B
contexts.addHandler(appB);
try
{
server.start(); // start server thread
server.join(); // wait for server thread to end
}
catch (Throwable t)
{
t.printStackTrace(System.err);
}
}
}

Categories