We use tomcat 9 with spring/hibernate. Obviously its a production code and the tomcat manager is not there. For the tomcat hardening we are trying to turn off the "autoDeploy" via code dynamically. We can do that in server.xml as :
<Host appBase="webapps" autoDeploy="false" name="localhost" unpackWARs="true">
Can that be done? Is there a way to do this programatically?
EDIT: Code attempted:
package test.servlet;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.modeler.Registry;
public class TestServlet extends HttpServlet {
ObjectName oname = null;
MBeanServer mBeanServer = null;
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String name = request.getParameter("app");
PrintWriter writer = response.getWriter();
try {
oname = new ObjectName("Catalina:type=Deployer,host=localhost");
mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
if (!isDeployed(name) && !isServiced(name)) {
writer.println("deploying application -> " + name);
addServiced(name);
try {
// Perform new deployment
check(name);
} finally {
removeServiced(name);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
protected void check(String name) throws Exception {
String[] params = { name };
String[] signature = { "java.lang.String" };
mBeanServer.invoke(oname, "check", params, signature);
}
protected void addServiced(String name) throws Exception {
String[] params = { name };
String[] signature = { "java.lang.String" };
mBeanServer.invoke(oname, "addServiced", params, signature);
}
protected boolean isDeployed(String name) throws Exception {
String[] params = { name };
String[] signature = { "java.lang.String" };
Boolean result = (Boolean) mBeanServer.invoke(oname, "isDeployed", params, signature);
return result.booleanValue();
}
protected boolean isServiced(String name) throws Exception {
String[] params = { name };
String[] signature = { "java.lang.String" };
Boolean result = (Boolean) mBeanServer.invoke(oname, "isServiced", params, signature);
return result.booleanValue();
}
protected void removeServiced(String name) throws Exception {
String[] params = { name };
String[] signature = { "java.lang.String" };
mBeanServer.invoke(oname, "removeServiced", params, signature);
}
}
I guess you would have to disable deployOnStartup and autoDeploy both.
Reference from the Tomcat 9 docs for host :
autoDeploy :
This flag value indicates if Tomcat should check periodically for new
or updated web applications while Tomcat is running. If true, Tomcat
periodically checks the appBase and xmlBase directories and deploys
any new web applications or context XML descriptors found. Updated web
applications or context XML descriptors will trigger a reload of the
web application. The flag's value defaults to true.
deployOnStartup : This flag value indicates if web
applications from this host should be automatically deployed when
Tomcat starts. The flag's value defaults to true.
If you disable deployOnStartup and autoDeploy, then you would need to explicitly configure the manager app via a Context element in server.xml and then use it to deploy additional WAR files/directories.
Note : You can disable auto deploy via setting - autodeploy="false" in your server.xml file.
Read more at : Apache Tomcat 9 Configuration Reference
No, it cannot be, as you cannot set autoDeploy="false" from Java code.
The only way to configure this, is to do it in the server.xml.
Related
I have an integration test in one of my projects that I want to run against an embedded jetty server. I followed along with the example here: https://www.eclipse.org/jetty/documentation/jetty-9/index.html#jndi-embedded but when I go to actually run my test it fails with the error:
javax.naming.NameNotFoundException: env is not bound; remaining name 'env/jdbc/NavDS'
at org.eclipse.jetty.jndi.NamingContext.getContext(NamingContext.java:241)
at org.eclipse.jetty.jndi.NamingContext.lookup(NamingContext.java:491)
at org.eclipse.jetty.jndi.NamingContext.lookup(NamingContext.java:491)
at org.eclipse.jetty.jndi.NamingContext.lookup(NamingContext.java:505)
at org.eclipse.jetty.jndi.java.javaRootURLContext.lookup(javaRootURLContext.java:101)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at com.tura.eyerep.test.TestWebServices.setUpBeforeClass(TestWebServices.java:63)
I'm sure there must be a simple mistake I'm making somewhere but I just can't seem to spot it. Can anyone give a suggestion of what I'm doing wrong here?
In my test I'm setting up the server with:
#BeforeClass
public static void setUpBeforeClass() throws Exception {
server = new Server(8080);
ClassList classList = ClassList.setServerDefault(server);
classList.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration");
WebAppContext context = new WebAppContext();
context.setExtractWAR(false);
context.setDescriptor("src/main/webapp/WEB-INF/web.xml");
context.setResourceBase("src/main/webapp");
context.setConfigurationDiscovered(false);
BasicDataSource ds = null;
ds = new BasicDataSource();
ds.setUrl("jdbc:h2:mem:myDB;create=true;MODE=MSSQLServer;DATABASE_TO_UPPER=FALSE;");
org.eclipse.jetty.plus.jndi.Resource mydatasource = new org.eclipse.jetty.plus.jndi.Resource(context, "jdbc/NavDS",ds);
server.setHandler(context);
server.start();
}
#Test
public void testLookup()
{
InitialContext ctx = new InitialContext();
DataSource myds = (DataSource) ctx.lookup("java:comp/env/jdbc/NavDS");
assertNotNull( myds);
}
In my web.xml I have a resource ref entry:
<resource-ref>
<description>Nav Datasource</description>
<res-ref-name>jdbc/NavDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
Lets cleanup your testcase first.
// meaningless when you have a WAR that is a directory.
context.setExtractWAR(false); // remove this line
// This prevents Servlet 3 behaviors when using Servlet 2.x descriptors
context.setConfigurationDiscovered(false); // Remove this
The error you are getting ...
javax.naming.NameNotFoundException: env is not bound;
remaining name 'env/jdbc/NavDS'
That likely means that a server is still running somewhere, probably forgot to stop/cleanup the previous server instance. (Look at the example junit5 testcase below, for how it deals with non-fixed ports, how to reference a non-fixed port, and how it stops the lifecycle of the server).
Next, be aware of your scopes.
new org.eclipse.jetty.plus.jndi.Resource(context, "jdbc/NavDS",ds);
That will bind the resource entry to jdbc/NavDS on the scope context.
Which means if you look up the resource outside of the WebAppContext scope,
like you do with testLookup() method it will exist at initialContext.lookup("jdbc/NavDS"), and nowhere else, the java:comp/env prefix/tree doesn't even exist to that testLookup() method scope.
Inside of your webapp, such as in a Filter or Servlet, that context specific resource is bound and available at jdbc:comp/env/jdbc/NavDS.
You have 3 typical scopes.
Order
Scope
EnvEntry or Resource first parameter
1
WebApp
new EnvEntry(webappContext, ...) or new Resource(webappContext, ...)
2
Server
new EnvEntry(server, ...) or new Resource(server, ...)
3
JVM
new EnvEntry(null, ...) or new Resource(null, ...)
If the value doesn't exist at the WebApp scope, the Server scope is checked, and then the JVM scope is checked.
Your Server can have a value for the name val/foo and a specific webapp can have a different value for the same name val/foo, simply by how the scopes are defined.
Next, there's the binding in the Servlet spec, you have specify the <resource-ref> and this combined with the declaration at the server side, bound to context means you can access java:comp/env/jdbc/NavDS from your servlet in that specific webapp.
To see this a different way, in code ...
package jetty.jndi;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.List;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class WebAppWithJNDITest
{
private static Server server;
private static WebAppContext context;
public static class JndiDumpServlet extends HttpServlet
{
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
PrintStream out = new PrintStream(resp.getOutputStream(), false, StandardCharsets.UTF_8);
try
{
dumpJndi(out);
}
catch (NamingException e)
{
throw new ServletException(e);
}
}
}
#BeforeAll
public static void startServer() throws Exception
{
server = new Server(0); // let os/jvm pick a port
Configuration.ClassList classList = Configuration.ClassList.setServerDefault(server);
classList.addAfter(FragmentConfiguration.class.getName(),
EnvConfiguration.class.getName(),
PlusConfiguration.class.getName());
context = new WebAppContext();
context.setContextPath("/");
// This directory only has WEB-INF/web.xml
context.setBaseResource(new PathResource(Paths.get("src/main/webroots/jndi-root")));
context.addServlet(JndiDumpServlet.class, "/jndi-dump");
new org.eclipse.jetty.plus.jndi.Resource(null, "val/foo", Integer.valueOf(707));
new org.eclipse.jetty.plus.jndi.Resource(server, "val/foo", Integer.valueOf(808));
new org.eclipse.jetty.plus.jndi.Resource(context, "val/foo", Integer.valueOf(909));
new org.eclipse.jetty.plus.jndi.EnvEntry(null, "entry/foo", Integer.valueOf(440), false);
new org.eclipse.jetty.plus.jndi.EnvEntry(server, "entry/foo", Integer.valueOf(550), false);
new org.eclipse.jetty.plus.jndi.EnvEntry(context, "entry/foo", Integer.valueOf(660), false);
server.setHandler(context);
server.start();
}
#AfterAll
public static void stopServer()
{
LifeCycle.stop(server);
}
public static void dumpJndi(PrintStream out) throws NamingException
{
InitialContext ctx = new InitialContext();
List<String> paths = List.of("val/foo", "entry/foo");
List<String> prefixes = List.of("java:comp/env/", "");
for (String prefix : prefixes)
{
for (String path : paths)
{
try
{
Integer val = (Integer)ctx.lookup(prefix + path);
out.printf("lookup(\"%s%s\") = %s%n", prefix, path, val);
}
catch (NameNotFoundException e)
{
out.printf("lookup(\"%s%s\") = NameNotFound: %s%n", prefix, path, e.getMessage());
}
}
}
}
#Test
public void testLookup() throws NamingException, IOException
{
System.out.println("-- Dump from WebApp Scope");
HttpURLConnection http = (HttpURLConnection)server.getURI().resolve("/jndi-dump").toURL().openConnection();
try (InputStream in = http.getInputStream())
{
String body = IO.toString(in, StandardCharsets.UTF_8);
System.out.println(body);
}
System.out.println("-- Dump from Test scope");
dumpJndi(System.out);
}
}
Contents of src/main/webroot/jndi-root/WEB-INF/web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<resource-ref>
<description>My Foo Resource</description>
<res-ref-name>val/foo</res-ref-name>
<res-type>java.lang.Integer</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
The output looks like ...
2021-10-26 17:05:16.834:INFO:oejs.Server:main: jetty-9.4.44.v20210927; built: 2021-06-30T11:07:22.254Z; git: 526006ecfa3af7f1a27ef3a288e2bef7ea9dd7e8; jvm 11.0.12+7
2021-10-26 17:05:17.012:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext#19932c16{/,file:///home/joakim/code/jetty/junk/src/main/webroots/jndi-root/,AVAILABLE}
2021-10-26 17:05:17.033:INFO:oejs.AbstractConnector:main: Started ServerConnector#212b5695{HTTP/1.1, (http/1.1)}{0.0.0.0:45387}
2021-10-26 17:05:17.034:INFO:oejs.Server:main: Started #816ms
-- Dump from WebApp Scope
lookup("java:comp/env/val/foo") = 909
lookup("java:comp/env/entry/foo") = 660
lookup("val/foo") = 707
lookup("entry/foo") = 440
-- Dump from Test scope
lookup("java:comp/env/val/foo") = NameNotFound: env is not bound
lookup("java:comp/env/entry/foo") = NameNotFound: env is not bound
lookup("val/foo") = 707
lookup("entry/foo") = 440
2021-10-26 17:05:17.209:INFO:oejs.AbstractConnector:main: Stopped ServerConnector#212b5695{HTTP/1.1, (http/1.1)}{0.0.0.0:0}
2021-10-26 17:05:17.210:INFO:oejs.session:main: node0 Stopped scavenging
2021-10-26 17:05:17.214:INFO:oejsh.ContextHandler:main: Stopped o.e.j.w.WebAppContext#19932c16{/,file:///home/joakim/code/jetty/junk/src/main/webroots/jndi-root/,STOPPED}
Hopefully the scope differences are obvious above.
A variation of the above test is now available at the Eclipse Jetty Embedded Cookbook project.
https://github.com/jetty-project/embedded-jetty-cookbook/
Available in 3 different Jetty flavors
Jetty 9.4.x - WebAppContextWithJNDI.java
Jetty 10.0.x - WebAppContextWithJNDI.java
Jetty 11.0.x - WebAppContextWithJNDI.java
tldr; How do I deploy from WebApplicationContext in embedded jetty for a simple automatic test case?
What I have:
A spring application (not spring boot) that uses annotation configuration (no web.xml) and is - for production - build (with gradle) to an ear and deployed on wildfly.
What I ultimately want to do:
Have self-contained (in the applications test package) ui-tests, that can easily be executed (without building and deploying) like any of the other unit or integration tests.
What I have done so far:
The easy part: Setup the actual tests with Selenium WebDriver
The hard part: Start an embedded Jetty with a test WebApplicationContext
My code for the embedded Jetty (yes, I will do some refactoring when I get it running):
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import org.apache.tomcat.util.scan.StandardJarScanner;
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
import org.eclipse.jetty.jsp.JettyJspServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.session.HashSessionIdManager;
import org.eclipse.jetty.server.session.HashSessionManager;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.resource.Resource;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
public class JettyServerFactory {
private Server server = null;
public Server start(WebApplicationContext context) throws Exception {
if (server == null) {
this.server = new Server(9090);
setupServer(context);
server.start();
//server.join();
}
return server;
}
public void stop() throws Exception {
if (server != null) {
server.stop();
}
}
private void setupServer(WebApplicationContext context) throws Exception {
final ServletContextHandler contextHandler = new ServletContextHandler();
// Since this is a ServletContextHandler we must manually configure JSP support.
enableEmbeddedJspSupport(contextHandler);
// Spring Security
enableSpringSecurity(contextHandler, context);
/*
// TODO: Does not seem to have any effect
// Set reference to actual Spring ApplicationContext
ServletContext servletContext = contextHandler.getServletContext();
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
*/
// Set resource base folder
contextHandler.setBaseResource(getResourceBase());
// Add DispatcherServlet to context
final ServletHolder servletHolder = new ServletHolder("default", new DispatcherServlet(context));
servletHolder.setInitParameter("dirAllowed", "true");
contextHandler.setContextPath("/oauth");
contextHandler.addServlet(servletHolder, "/");
// Add context to server
server.setHandler(contextHandler);
}
private Resource getResourceBase() {
File file = new File("src/website/application.war");
//servletHolder.setInitParameter("resourceBase", getWebRootResourceUri().toASCIIString());
final Resource base = Resource.newResource(file);
return base;
}
/**
* Apply existing securityFilterChain to context
*
* #param contextHandler
* #param context
*/
private void enableSpringSecurity(ServletContextHandler contextHandler, WebApplicationContext context) {
FilterChainProxy filter = (FilterChainProxy) context.getBean("springSecurityFilterChain");
FilterHolder filterHolder = new FilterHolder(filter);
contextHandler.addFilter(filterHolder, "/*", EnumSet.allOf(DispatcherType.class));
server.setSessionIdManager(new HashSessionIdManager());
HashSessionManager manager = new HashSessionManager();
SessionHandler sessions = new SessionHandler(manager);
contextHandler.setHandler(sessions);
}
/**
* Setup JSP Support for ServletContextHandlers.
* <p>
* NOTE: This is not required or appropriate if using a WebAppContext.
* </p>
*
* #param servletContextHandler the ServletContextHandler to configure
* #throws IOException if unable to configure
*/
private void enableEmbeddedJspSupport(ServletContextHandler servletContextHandler) throws IOException {
// Establish Scratch directory for the servlet context (used by JSP compilation)
File tempDir = new File(System.getProperty("java.io.tmpdir"));
File scratchDir = new File(tempDir.toString(), "embedded-jetty-jsp");
if (!scratchDir.exists()) {
if (!scratchDir.mkdirs()) {
throw new IOException("Unable to create scratch directory: " + scratchDir);
}
}
servletContextHandler.setAttribute("javax.servlet.context.tempdir", scratchDir);
// Set Classloader of Context to be sane (needed for JSTL)
// JSP requires a non-System classloader, this simply wraps the
// embedded System classloader in a way that makes it suitable
// for JSP to use
ClassLoader jspClassLoader = new URLClassLoader(new URL[0], this.getClass().getClassLoader());
servletContextHandler.setClassLoader(jspClassLoader);
// Manually call JettyJasperInitializer on context startup
servletContextHandler.addBean(new JspStarter(servletContextHandler));
// Create / Register JSP Servlet (must be named "jsp" per spec)
ServletHolder holderJsp = new ServletHolder("jsp", JettyJspServlet.class);
holderJsp.setInitOrder(0);
holderJsp.setInitParameter("logVerbosityLevel", "DEBUG");
holderJsp.setInitParameter("fork", "false");
holderJsp.setInitParameter("xpoweredBy", "false");
holderJsp.setInitParameter("compilerTargetVM", "1.8");
holderJsp.setInitParameter("compilerSourceVM", "1.8");
holderJsp.setInitParameter("keepgenerated", "true");
servletContextHandler.addServlet(holderJsp, "*.jsp");
}
/**
* JspStarter for embedded ServletContextHandlers
*
* This is added as a bean that is a jetty LifeCycle on the ServletContextHandler. This bean's doStart method will
* be called as the ServletContextHandler starts, and will call the ServletContainerInitializer for the jsp engine.
*
*/
public static class JspStarter extends AbstractLifeCycle
implements ServletContextHandler.ServletContainerInitializerCaller {
JettyJasperInitializer sci;
ServletContextHandler context;
public JspStarter(ServletContextHandler context) {
this.sci = new JettyJasperInitializer();
this.context = context;
this.context.setAttribute("org.apache.tomcat.JarScanner", new StandardJarScanner());
}
#Override
protected void doStart() throws Exception {
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(context.getClassLoader());
try {
sci.onStartup(null, context.getServletContext());
super.doStart();
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}
}
private URI getWebRootResourceUri() throws FileNotFoundException, URISyntaxException, MalformedURLException {
String WEBROOT_INDEX = "/application.war/";
URL indexUri = this.getClass().getResource(WEBROOT_INDEX);
if (indexUri == null) {
throw new FileNotFoundException("Unable to find resource " + WEBROOT_INDEX);
}
File file = new File("src/website/application.war");
System.out.println(indexUri.toURI());
System.out.println(file.toURI());
System.out.println(file.getAbsolutePath());
return file.toURI();
}
}
The WebApplicationContext for the test case is simply provided somewhat like this:
...
#WebAppConfiguration
public class ATestClass {
#Autowired
WebApplicationContext context;
...
#Before
public void setupTest() throws Exception {
new JettyServerFactory().start(context);
...
}
...
...
Location of the Resource Base:
Resource placement in the project structure
All static resources like css, js and images are in themes
What does work:
Controllers are hit on requests
Spring Security is active
JSPs are found and rendered (thanks to https://github.com/jetty-project/embedded-jetty-jsp)
What does not work:
All other static resources are missing (css, js, images, etc.). Basically everything from themes
So, what are my questions:
How can I make the missing static resources available?
Is there a completely different/better approach to achieve my ultimate goal? The way, to what I have now, was quite rocky and I'm not sure the missing static content is the last of my problems.
I'm happy to provide additional information/code. But, since this is an actual product, I have some limits on that.
Edits:
Picture of the resource structure
Mind that application.war is just a folder, not actually a war
Resource Config
#EnableWebMvc
#Configuration
public class AppWebMvcConfiguration implements WebMvcConfigurer {
...
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/jsp/", ".jsp");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
boolean developmentMode = configuration.getBooleanFromNewconfig(false, "oauth2.developmentMode");
if (USE_VERSIONED_RESOURCES) {
registry.addResourceHandler("/themes/**")
.addResourceLocations("/themes/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
.resourceChain(developmentMode ? false : true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
.addTransformer(new CssLinkResourceTransformer());
} else {
registry.addResourceHandler("/themes/**")
.addResourceLocations("/themes/")
.setCacheControl(CacheControl.noCache());
}
}
...
}
Which case is used for the ResourceHandler doesn't matter. Neither works.
I need to programmatically add and remove a servlet on a Jetty 6 server.
While it is almost straighforward to add I cannot find an effective way to remove.
For my purposes it is important to add and remove a servlet because it is associated to a dynamic compontent architecture. I need to add a new service when I add a component and I need to remove the service when I remove the component.
To add a servlet I used this pattern:
Server server = new Server(8080);
class MyServlet extends HttpServlet
{
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
resp.getOutputStream().write("Hello World!".getBytes());
}
}
...
public void addServlet(HttpServlet s, String path)
{
Context root = new Context(server,"/",Context.SESSIONS);
root.addServlet(new ServletHolder(new MyServlet()), "/test/*");
root.getServletHandler().
}
public void removeServlet(HttpServlet s, String path)
{
//What I have to put here ? There is no removeServlet like methods in server/Context/ServletHolder
}
Why removing a servlet is not so obvious? Can you explain me the motivations ?
first off I would recommend updating to jetty 7 or 8 if its possible, jetty 6 is quite old at this point and is lacking the last couple years of development that are present in 7 and 8. heck, jetty 9 is being actively worked on now.
second I wouldn't look at this on the servlet level but the handler level, working with the server to add and remove handlers, which can be either static resource type handlers or full fledged servlet context handlers, or even webapp context handlers.
as to why the servlet context handlers do not have remove servlet type operations, its really not a part of the servlet spec to remove active servlets at that level, fits more at the war deploy/undeploy level. feel free to open an issue on it though, I did experiment with adding and removing at servlet context handler level and you can remove them but it seems to be problematic then adding more afterwards, so I suspect removing the context itself and adding a new one would be your best bet at this point.
Here's instructions for doing it on Jetty 7.
Jetty : Dynamically removing the registered servlet
It should be pretty straight forward to port that code to Jetty 6.
This solution seems to be working:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.ContextHandler;
import org.mortbay.jetty.handler.ContextHandlerCollection;
import org.mortbay.jetty.handler.ResourceHandler;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHandler;
import org.mortbay.jetty.servlet.ServletHolder;
import org.mortbay.jetty.servlet.ServletMapping;
public class MyServer extends Server
{
ServletHandler sh = new ServletHandler();
public MyServer()
{
super(9090);
setHandler(sh);
test();
}
class MyServlet extends HttpServlet
{
/**
*
*/
private static final long serialVersionUID = 1L;
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
resp.getWriter().println("CIAO!");
}
}
void test()
{
MyServlet ms = new MyServlet();
addServlet(ms, "/ciao/*");
//removeServlet(ms);//uncomment this ilne in order to remove the servlet right after the deploy
}
public void addServlet(HttpServlet s, String path)
{
sh.addServletWithMapping(new ServletHolder(s), path);
for (ServletHolder so : sh.getServlets())
try
{
System.out.println((so.getServlet() == s));
} catch (ServletException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void removeServlet(HttpServlet s)
{
try
{
HashSet<String> names = new HashSet<String>();
for (ServletHolder so : sh.getServlets())
if (so.getServlet() == s)
names.add(so.getName());
HashSet<ServletMapping> sms = new HashSet<ServletMapping>();
for (ServletMapping sm : sh.getServletMappings())
{
if (!names.contains(sm.getServletName()))
sms.add(sm);
}
sh.setServletMappings(sms.toArray(new ServletMapping[] {}));
} catch (ServletException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
I'm trying to set up a site which allows users to create their own page - as a subdomain of the site, and so what I'm currently trying to do is to have a filter which looks at the subdomain, and if it's not in use, or if it's reserved, then the user will be forwarded to a page where they choose their subdomain name.
The problem that I have is that when I set an attribute on the ServletRequest object, and then forward using the RequestDispatcher, the filter gets called again - but it can't see the attribute that I set.
I've debugged and watched it work (or not work!), and the attribute is being set, but after the forward, the attribute is not there.
Can someone help explain what's going on, and how I might fix this??
I should probably also mention that I'm developing for Java Google App Engine.
Here's my filter code.
import java.io.IOException;
import java.util.Enumeration;
import java.util.logging.Logger;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class SubdomainFilter implements Filter{
private static Logger log = Logger.getLogger(SubdomainFilter.class.getName());
private static final String[] RESERVED_SUBDOMAINS = {"admin", "home", "www"};
private static final String registerPage = "/register_a_page";
#Override
public void destroy() {
// TODO Auto-generated method stub
}
#Override
public void doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain arg2) throws IOException, ServletException {
//if we've been forwarded, it must have been because of an invalid subdomain
if(arg0.getAttribute("subdomain") != null) {
arg2.doFilter(arg0, arg1);
} else { //otherwise, look at the subdomain and determine what to do
boolean invalidSubdomain = false;
try {
String requestURLInfo = ((HttpServletRequest)arg0).getRequestURL().toString();
String subdomain = URLUtils.getLowestSubdomainFromURL(requestURLInfo);
arg0.setAttribute("subdomain", subdomain);
if(subdomainReserved(subdomain) || subdomainInUse(subdomain)) {
invalidSubdomain = true;
}//Otherwise the subdomain must be valid
} catch(Exception ex) {
log.severe("Filter could not get subdomain:\n" + ex.toString());
} finally {
if(invalidSubdomain) {
RequestDispatcher dispatcher = arg0.getRequestDispatcher(registerPage);
dispatcher.forward(arg0, arg1);
} else {
arg2.doFilter(arg0, arg1);
}
}
}
}
private boolean subdomainReserved(String subdomain) {
boolean subdomainReserved = false;
for(String reservedSubdomain : RESERVED_SUBDOMAINS) {
if(reservedSubdomain.equals(subdomain)) {
subdomainReserved = true;
break;
}
}
return subdomainReserved;
}
private boolean subdomainInUse(String subdomain) {
boolean subdomainInUse = false;
//TODO: implement
return subdomainInUse;
}
#Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
I think the problem was that I didn't declare the following in my web.xml:
<filter-mapping>
<filter-name>SubdomainFilter</filter-name>
<servlet-name>*</servlet-name>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
It should work. Either the URLUtils.getLowestSubdomainFromURL() has just returned null, or something else in the request-response chain has fired HttpServletResponse#sendRedirect().
I know this post is 3 years old, but i still want to confirm Louis's post, that forwarding works fine without the following web.xml ONLY IF you don't need attributes forwarded (i.e. request.getAttribute, request.setAttribute).
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
I'm not sure if there is any overhead by specifying the dispatcher tags in the web.xml when you don't NEED to, but you definitely DO need that in the web.xml for attributes to work.
My listener is filling Cache (Terracota) and if something goes wrong at application start, ExceptionInInitializerError is thrown. I would like to get server name (like on HttpServletRequest - getServerName()) to know where this happened.
How can I come to this information??
import javax.servlet.ServletContextEvent;
import net.f.core.service.util.CacheUtil;
import org.apache.log4j.Logger;
import org.springframework.web.context.ContextLoaderListener;
/**
* Application Lifecycle Listener implementation class OnContextLoadListener
*
*/
public class OnContextLoadListener extends ContextLoaderListener {
private static final Logger log = Logger
.getLogger(OnContextLoadListener.class);
#Override
public void contextDestroyed(
#SuppressWarnings("unused") ServletContextEvent sce) {
// nothing here
}
#Override
public void contextInitialized(
#SuppressWarnings("unused") ServletContextEvent sce) {
try {
CacheUtil.getInstance();
} catch (ExceptionInInitializerError e) {
log.error("Problem with application start!", e);
// notify me
}
}
The server hostname is part of the request, as it depends on what URL the client used to reach your host.
If you are interested in the local hostname, you can try:
String hostname = InetAddress.getLocalHost().getHostName();
HttpServletRequest.getServerName():
Returns the host name of the server to
which the request was sent.
Its not a property of the server itself, it's a property of the request. It makes no sense outside of the context of the ContextLoaderListener.
What information are you actually looking for?
Simply:
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
....
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest req = sra.getRequest();
String serverName = req.getServerName();
If you're just trying to determine if you're on localhost:
boolean isLocalHost = "localhost/127.0.0.1".equals(InetAddress.getLoopbackAddress().toString());