Init servlet instantly after the embedded jetty server starts - java

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()
);

Related

Java create/overwrite http server

I'm creating a plugin on a certain platform (the details are irrelevant) and need to create a HTTP endpoint. In normal circumstances you'd create a http server and stop it whenever you're done using it or when the application stops, however, in my case I can't detect when the plugin is being uninstalled/reinstalled.
The problem
When someone installs my plugin twice, the second time it will throw an error because I'm trying to create a http server on a port which is already in use. Since it's being reinstalled, I can't save the http server on some static variable either. In other words, I need to be able to stop a previously created http server without having any reference to it.
My attempt
I figured the only way to interact with the original reference to the http server would be to create a thread whenever the http server starts, and then overwrite the interrupt() method to stop the server, but somehow I'm still receiving the 'port is already in use' error. I'm using Undertow as my http server library, but this problem applies to any http server implementation.
import io.undertow.Undertow;
import io.undertow.util.Headers;
public class SomeServlet extends Thread {
private static final String THREAD_NAME = "some-servlet-container-5391301";
private static final int PORT = 5839;
private Undertow server;
public static void listen() { // this method is called whenever my plugin is installed
deleteExistingServer();
new SomeServlet().start();
}
private static void deleteExistingServer() {
for (Thread t : Thread.getAllStackTraces().keySet()) {
if (t.getName().equals(THREAD_NAME)) {
t.interrupt();
}
}
}
#Override
public void run() {
createServer();
}
#Override
public void interrupt() {
try {
System.out.println("INTERRUPT");
this.server.stop();
} finally {
super.interrupt();
}
}
private void createServer() {
this.server = Undertow.builder()
.addHttpListener(PORT, "localhost")
.setHandler(exchange -> {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
exchange.getResponseSender().send("Hello World!");
})
.build();
this.server.start();
}
}
Desired behaviour
Whenever listen() is called, it will remove any previously existing http server and create a new one, without relying on storing the server on a static variable.
You could try com.sun.net.httpserver.HttpServer. Use http://localhost:8765/stop to stop and 'http://localhost:8765/test' for test request:
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
public class TestHttpServer {
public static void main(String[] args) throws IOException {
final HttpServer server = HttpServer.create();
server.bind(new InetSocketAddress(8765), 0);
server.createContext("/test", httpExchange -> {
String response = "<html>TEST!!!</html>";
httpExchange.sendResponseHeaders(200, response.length());
OutputStream os = httpExchange.getResponseBody();
os.write(response.getBytes());
os.close();
});
server.createContext("/stop", httpExchange -> server.stop(1));
server.start();
}
}

Fail to connect to server when creating an EJBClient

I have an EJB Client as follow:
public class EJBTestClient {
public static void main(String[] args) throws NamingException {
Properties jndiProps = new Properties();
jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
jndiProps.put(Context.PROVIDER_URL,"http-remoting://localhost:8080"); // create a context passing these properties Context ctx = new InitialContext(jndiProps);
jndiProps.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
InitialContext context = new InitialContext(jndiProps);
System.out.println("Context lookup finished");
MyFirstEJBRemote proxy = (MyFirstEJBRemote) context.lookup("MyFirstEJB/Remote");
System.out.println(proxy.getClass().toString());
System.out.println(proxy.getDescription());
proxy.doSomething();
}
}
but when I run the program it showed an Exception javax.naming.CommunicationException: Failed to connect to any server. Servers tried: [http-remoting://127.0.0.1:8080 (java.io.IOException: JBREM000202: Abrupt close on Remoting connection 0c05035f to /127.0.0.1:8080 of endpoint "config-based-naming-client-endpoint" <2ce1483d>)]
And my EJB Container named EJBTestApp which contains MyFirstEJB Stateless Session Bean and MyFirstEJBRemote Interface:
#Stateless
#Remote
public class MyFirstEJB implements MyFirstEJBRemote {
private Logger log = Logger.getLogger(MyFirstEJB.class);
/**
* Default constructor.
*/
public MyFirstEJB() {
// TODO Auto-generated constructor stub
}
#Override
public void doSomething() {
log.info("doSomething() has been call");
}
#Override
public String getDescription() {
return "getDescription() has returned some values";
}
}
And this EJB Container is deployed on Wildfly 10 under localhost:8080. Can anyone help me how to solve this problem
Try to set
jndiProps.put(jboss.naming.client.ejb.context, true);
Add the libraries from wildfly10directory/bin/client to the project
If doesn't working, try to set lookup to:
MyFirstEJBRemote proxy = (MyFirstEJBRemote) context.lookup("MyFirstEJB/BeanName!path_to_package_remote_bean");

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);
}

Injecting objects into jersey ressources

Can't figure out how to make an object created at jersey-server start accessible in jersey resources. Basically, what i want to do is to inject a Database context into jersey resources.
JerseyServer:
public boolean startServer(String keyStoreServer, String trustStoreServer) {
//Check if GraphDb is setup
if (gdbLogic == null) {
//FIXME - maybe throw an exception here?
return false;
}
// create a resource config that scans for JAX-RS resources and providers
// in org.developer_recommender.server package
final org.glassfish.jersey.server.ResourceConfig rc = new org.glassfish.jersey.server.ResourceConfig().packages("org.developer_recommender.server").register(createMoxyJsonResolver());
WebappContext context = new WebappContext("context");
ServletRegistration registration = context.addServlet("ServletContainer", ServletContainer.class);
//TODO: value setzen
registration.setInitParameter("jersey.config.server.provider.packages", "org.developer_recommender.server.service;org.developer_recommender.server.auth");
registration.setInitParameter(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, SecurityFilter.class.getName());
SSLContextConfigurator sslContext = new SSLContextConfigurator();
sslContext.setKeyStoreFile(keyStoreServer);
sslContext.setTrustStoreFile(trustStoreServer);
//TODO -
sslContext.setKeyStorePass("123456");
sslContext.setTrustStorePass("123456");
// create and start a new instance of grizzly http server
// exposing the Jersey application at BASE_URI
HttpServer server = null;
try{
SSLEngineConfigurator sslec = new SSLEngineConfigurator(sslContext).setClientMode(false).setNeedClientAuth(true);
server = GrizzlyHttpServerFactory.createHttpServer(getBaseURI()/*URI.create(BASE_URI)*/, rc, true , sslec);
System.out.println("Jersey app started. Try out " + BASE_URI);
context.deploy(server);
return true;
} catch(Exception e ){
System.out.println(e.getMessage());
}
return false;
Service:
public class Service {
#Inject
protected GDBLogic gbdLogic;
}
So i want the instance of GDBLogic in startServer to be accessible in Jersey Resources. Any advice on how to achieve this?
I don't want to use a static field for GDBLogic to achieve this, cause we will have a minimum of two different Database configurations.
You need to set up the instance binding in order to get the injection to work. You can do that by adding an HK2 abstract binder to your resource config:
final ResourceConfig rc = new ResourceConfig()
.packages("org.developer_recommender.server")
.register(createMoxyJsonResolver())
.register(new AbstractBinder()
{
#Override
protected void configure()
{
bind(gdbLogic).to(GDBLogic.class);
}
});

How does one configure CXF generated client for preemptive HTTP auth?

I have a client that was generated by CXF using a local wsdl file. The client connects OK, and I get an expected 401 error from the web server.
The problem I've run into is not being able to properly configure preemptive auth in the client.
I've tried a number of things to no avail. The majority of examples on the web seem to focus on Spring, rather a plain old Java approach.
I'm including the main portion of the client. If anyone can give me an example of how this should be configured, I'd appreciate it. Note that I'm not looking for anything fancy. I just need to be able to authenticate and call the services.
public final class ServiceNowSoap_ServiceNowSoap_Client {
private static final QName SERVICE_NAME = new QName(
"http://www.service-now.com/foo",
"ServiceNow_foo");
private ServiceNowSoap_ServiceNowSoap_Client() {
}
public static void main(String args[]) throws java.lang.Exception {
URL wsdlURL = ServiceNowCmdbCiComm.WSDL_LOCATION;
if (args.length > 0 && args[0] != null && !"".equals(args[0])) {
File wsdlFile = new File(args[0]);
try {
if (wsdlFile.exists()) {
wsdlURL = wsdlFile.toURI().toURL();
} else {
wsdlURL = new URL(args[0]);
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
ServiceNowFoo ss = new ServiceNowFoo(wsdlURL,
SERVICE_NAME);
ServiceNowSoap port = ss.getServiceNowSoap();
{
System.out.println("Invoking deleteRecord...");
java.lang.String _deleteRecord_sysId = "";
java.lang.String _deleteRecord__return = port
.deleteRecord(_deleteRecord_sysId);
System.out.println("deleteRecord.result=" + _deleteRecord__return);
}
System.exit(0);
}
}
Another aproach would be:
import javax.xml.ws.BindingProvider;
public class CxfClientExample {
public static void main(String[] args) throws Exception {
String endPointAddress = "http://www.service-now.com/foo";
ServiceNowFoo service = new ServiceNowFoo();
ServiceNowFooPortType port = service.getServiceNowFoo();
BindingProvider bindingProvider = (BindingProvider) port;
bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endPointAddress);
bindingProvider.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "theusername");
bindingProvider.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "thepassword");
String deleteRecord_return = port.deleteRecord("");
System.out.println("deleteRecord.result=" + deleteRecord_return);
}
}
OK, I figured this out. Pretty straightforward when it comes down to it. Hope this saves somebody a couple of minutes...
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;
private static final QName SERVICE_NAME = new QName(
"http://www.service-now.com/foo",
"ServiceNow_foo");
private ServiceNowSoap_ServiceNowSoap_Client() {
}
public static void main(String args[]) throws java.lang.Exception {
URL wsdlURL = ServiceNowFoo.WSDL_LOCATION;
if (args.length > 0 && args[0] != null && !"".equals(args[0])) {
File wsdlFile = new File(args[0]);
try {
if (wsdlFile.exists()) {
wsdlURL = wsdlFile.toURI().toURL();
} else {
wsdlURL = new URL(args[0]);
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
ServiceNowFoo ss = new ServiceNowFoo(wsdlURL,
SERVICE_NAME);
ServiceNowSoap port = ss.getServiceNowSoap();
Client client = ClientProxy.getClient(port);
HTTPConduit http = (HTTPConduit) client.getConduit();
http.getAuthorization().setUserName("theusername");
http.getAuthorization().setPassword("thepassword");
// Do your work here.
}
You can also use interceptors. One benefit of using interceptor is that you can attach it to all your clients , streamlining your pre-emptive authentication approach.
Take a look at :
How do i modify HTTP headers for a JAX-WS response in CXF?
Hi Friend you have configured the authentication part after invoking the webservice, how does this works ?
Client client = ClientProxy.getClient(port);
HTTPConduit http = (HTTPConduit) client.getConduit();
http.getAuthorization().setUserName("theusername");
http.getAuthorization().setPassword("thepassword");

Categories