Jetty 6: How to remove a Servlet? - java

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

Related

Adding REST route to an existing Jetty endpoint in Camel at runtime

I have been inventing a way how to work around the problem of adding consumers to a jetty endpoint (it does not allow multiple consumers). The way we do it in our company is to build our own router and a broadcasting endpoint which consumes from jetty and routes requests to underlying "subscriptions". Only one of them will eventually process the request. It kind of works but it's not completely ok, since recently when updating to latest Camel we have found our custom built component to leak memory and in general I consider using built-in functionality over custom hacks.
I started investigating the Camel REST API and found it very nice and pretty much replacing our home-grown component apart from one thing - you cannot re-configure it at runtime - you have to stop the context basically for this to work. Below I include my unit test with a happy path and the path that fails. Frankly I think is a bug, but if there is a legitimate way to achieve what I want, I'd like to hear sound advice:
package com.anydoby.camel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Test;
/**
* Test tries to add/remove routes at runtime.
*/
public class RoutesTest {
private DefaultCamelContext ctx;
#Before
public void pre() throws Exception {
ctx = new DefaultCamelContext();
new RouteBuilder(ctx) {
#Override
public void configure() throws Exception {
restConfiguration("jetty").host("localhost").port(8080);
rest("/")
.get("/issues/{isin}").route().id("issues")
.process(e -> e.getOut().setBody("Here's your issue " + e.getIn().getHeader("isin"))).endRest()
.get("/listings").route().id("listings").process(e -> e.getOut().setBody("some listings"));
}
}.addRoutesToCamelContext(ctx);
ctx.start();
}
#Test
public void test() throws IOException {
{
InputStream stream = new URL("http://localhost:8080/issues/35").openStream();
assertEquals("Here's your issue 35", IOUtils.toString(stream));
}
{
InputStream stream = new URL("http://localhost:8080/listings").openStream();
assertEquals("some listings", IOUtils.toString(stream));
}
}
#Test
public void disableRoute() throws Exception {
ctx.stopRoute("issues");
ctx.removeRoute("issues");
try (InputStream stream = new URL("http://localhost:8080/issues/35").openStream()) {
fail();
} catch (Exception e) {
}
new RouteBuilder(ctx) {
#Override
public void configure() throws Exception {
rest().get("/issues/{isin}/{sedol}").route().id("issues")
.process(e -> e.getOut()
.setBody("Here's your issue " + e.getIn().getHeader("isin") + ":" + e.getIn().getHeader("sedol")))
.endRest();
}
}.addRoutesToCamelContext(ctx);
{
InputStream stream = new URL("http://localhost:8080/issues/35/65").openStream();
assertEquals("Here's your issue 35:65", IOUtils.toString(stream));
}
}
}
The disableRoute() test fails since I cannot add another consumer to an existing endpoint.
So my question is - "is there a way to add a new URL mapping to a restful camel-jetty endpoint"? If you do it during first configuration it works fine, but when later you want to reconfigure one of the routes the error is:
org.apache.camel.FailedToStartRouteException: Failed to start route because of Multiple consumers for the same endpoint is not allowed: jetty:http://localhost:8080/issues/%7Bisin%7D/%7Bsedol%7D?httpMethodRestrict=GET

Jetty adding web fragment to web context programmatically

I've started to use embedded Jetty Server to start some 3rd-party WAR.
So I use WebAppContext:
Server server = new Server(port);
WebAppContext ctx = new WebAppContext();
ctx.setContextPath("/samplePath");
ctx.setWar(<PATH_TO_WAR>);
server.setHandler(ctx);
server.start();
server.join();
This code works (I've omitted exception handling for the sake of brevity here), however now I would like to add some functionality to the war which I want to leave intact (I mean, don't extract change and compress again).
My functionality should include an integration with some custom SSO solution which should add the following:
A Context Listener
A Filter
Some Context Param Variables definitions that should be read by these Filter and listener
I can't change this SSO solution because its not developed by our team, we rather take it as a thirdparty.
So I thought that adding all this to module with web-fragment would be the best approach.
My question is: what is the best way to make Jetty's web context to be 'aware' of this web fragment? A working code snippet would be great :)
Of course if there is a better alternative than web fragments for this, I'll be glad to know
The version of Jetty I currently use is (from my pom.xml): 9.2.10.v20150310
Thanks a lot in advance!
Here is the way you can specify a web app as well as a filter
import java.io.IOException;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.webapp.WebAppContext;
public class MultipleHandler {
/**
* #param args
*/
public static void main(String[] args) {
Server server = new Server();
ServerConnector connectorA = new ServerConnector(server);
connectorA.setPort(55);
connectorA.setName("connA"); // connector name A
server.addConnector(connectorA);
HandlerCollection contexts = new HandlerCollection();
server.setHandler(contexts);
// A WebApp
WebAppContext appA = new WebAppContext();
appA.setContextPath("/samplePath");
appA.setWar("<warFilePath>");
appA.setVirtualHosts(new String[] { "#connA" }); // connector name A
contexts.addHandler(appA);
//Filter handler
ServletHandler handler = new ServletHandler();
handler.addFilterWithMapping(DoWork.class, "/filter",
EnumSet.of(DispatcherType.REQUEST));
contexts.addHandler(handler);
try {
server.start();
server.join();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
public static class DoWork implements Filter {
#Override
public void destroy() {
// TODO Auto-generated method stub
}
#Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
System.out.print("Request filtered");
}
#Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
}

using jetty to test a method that performs http post

I am writing a junit test for a program that performs HTTP post, I am trying to use jetty to handle the request, but I can't find good examples or explanation on how to use it for that purpose, does anyone have a good example on how to do that?
a Junit is a unit test, not an integration test. The integration test means that you need have the server up and running to test that feature.
Here you can see an example of a Integration test:
#Test
public void test() {
HttpClient loggedClient = new HttpClient();
PostMethod loginPostMethod = new PostMethod(loginURL);
loginPostMethod.addParameter("j_username", login);
loginPostMethod.addParameter("j_password", password);
loginPostMethod.addParameter("remember", "1");
loginPostMethod.addParameter("clientType", clientType);
loginPostMethod.addParameter("clientVersion", clientVersion);
httpClient.executeMethod(postMethod);
String USERS_URL = HTTP_SERVER_DOMAIN + "/service";
PutMethod put = new PutMethod(USERS_URL);
put.addRequestHeader("Content-Type", "application/json");
put.setRequestBody("your_body");
try {
loggedClient.executeMethod(put);
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
assertEquals(HttpStatus.SC_OK, put.getStatusCode());
}
I don't know if this could help anyone, but this is the solution I came up with for my problem, so I thought I share it with you...
my problem was that in a specific part of the program that I am trying to test with junit, it makes a POST request, and I wanted to validate that it is making the request properly.. of course I could have used mockito to mock the static method that is making the request, but I just thought that using a real server like jetty would be better for testing the request being sent by my program, and this way, all my program would be fully tested, without skipping any part of it... so here is the jetty code I wrote...
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
public class JettyHttpServer extends AbstractHandler
{
private static Server server = null;
public Server getServer()
{
return server;
}
public static void startServer(int webServerPort) throws Exception
{
System.out.println("Initializing server");
server = new Server(webServerPort);
server.setHandler(new JettyHttpServer());
server.start();
}
public static void stopServer() throws Exception
{
try
{
server.stop();
} catch (Exception ex)
{
ex.printStackTrace();
throw new Exception("Could not stop server", ex);
}
}
public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException
{
System.out.println("inside Jetty Server...");
boolean requestWrongFlag = true
if (httpRequest.getMethod().equals("POST"))
{
BufferedReader reader = httpRequest.getReader();
String line;
do {
line = reader.readLine();
//do the validation here on the request received and in case it is not correct raise the requestWrongFlag
System.out.println(line);
}while (line!=null);
httpResponse.setContentType("text/html;charset=utf-8");
if(!requestWrongFlag)
httpResponse.setStatus(HttpServletResponse.SC_ACCEPTED);
else
httpResponse.setStatus(httpServletResponse.sc_BAD_REQUEST)
httpResponse.getWriter().println("OK");
request.setHandled(true);
}
}
}
then I called that jetty server in my TestCase just before I call the program under test...
if anyone has better suggestions... please share with me...

Servlet filters - passing an attribute not working with RequestDispatcher#forward

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.

Getting server name in ContextLoaderListener

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

Categories

Resources