I have code for starting a web app in Jetty. I want to add reverse proxy handling inside the app. When I call API,
I get error UnavailableException: Init parameter 'proxyTo' is required.
Server server = new Server(port);
WebAppContext webapp = createWebAppContext();
ServletHolder proxyServlet = new ServletHolder(ProxyServlet.Transparent.class);
proxyServlet.setInitParameter("ProxyTo", "http://attachments.dev.balcia.com:30310/api/");
proxyServlet.setInitParameter("Prefix", "/api");
webapp.addServlet(proxyServlet, "/api/attachments/*");
server.setHandler(webapp);
The init-parameter is called proxyTo not ProxyTo.
Servlet init-parameters are case sensitive.
Related
If I start a jetty server from an external jar using java -jar , and then how can I add another java web application to that specific port that has already been started ? for example, this code :
public class Main {
private static Logger logger = Logger.getLogger(Main.class);
public static void main(String[] args) throws Exception {
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
Server jettyServer = new Server(5701);
jettyServer.setHandler(context);
ServletHolder jerseyServlet = context.addServlet(
org.glassfish.jersey.servlet.ServletContainer.class, "/*");
jerseyServlet.setInitOrder(0);
jerseyServlet.setInitParameter(
"jersey.config.server.provider.classnames",
Calculator.class.getCanonicalName());
try {
jettyServer.start();
jettyServer.join();
} catch (Exception e){
logger.error("error during server starting",e);
jettyServer.stop();
jettyServer.destroy();
}
}
}
If I take the try/catch out, Would the application be added to the already existed 5701 jetty server?
your code starts its own Jetty server on port 5701, there's nothing about that code that will add to a different Jetty server.
Lets say you have ServerFoo on port 5701 already started and running in its own JVM.
You now have another Webapp you want to add to that ServerFoo instance.
You will start a new JVM, lets call it DeployerBar, to control/manipulate the ServerFoo instance.
To do this, you need to communicate with that ServerFoo instance and give it everything it needs to start the WebApp itself (all of the classes, the configuration, etc) first.
Then ServerFoo will need a custom ClassLoader to load these new classes and configuration that it just received, giving it the ability to start this new webapp.
If this is kinda what you are looking to do, consider instead modifying ServerFoo to use the Jetty DeploymentManager to monitor a common webapps directory.
Then your deployment process is just putting all of the files (classes/jars/libs/configuration) into this common webapps directory for the ServerFoo DeploymentManager to just pick up and start using.
See LikeJettyXml.java for an example how this works.
DeploymentManager deployer = new DeploymentManager();
DebugListener debug = new DebugListener(System.err,true,true,true);
server.addBean(debug);
deployer.addLifeCycleBinding(new DebugListenerBinding(debug));
deployer.setContexts(contexts);
deployer.setContextAttribute(
"org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
".*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$");
WebAppProvider webapp_provider = new WebAppProvider();
webapp_provider.setMonitoredDirName(jetty_base + "/webapps");
webapp_provider.setDefaultsDescriptor(jetty_home + "/etc/webdefault.xml");
webapp_provider.setScanInterval(1);
webapp_provider.setExtractWars(true);
webapp_provider.setConfigurationManager(new PropertiesConfigurationManager());
deployer.addAppProvider(webapp_provider);
server.addBean(deployer);
I'm migrating a web application with embedded Jetty from 7 to 9.3.2 and as such am in need of updating the code a little. The application itself has a multitude of connectors written for it, for web UI, API endpoint and also for authenticating to the web interface via a smart card. The connector method for achieving that is implemented as follows (for Jetty 7).
private Connector createSmartCardConnector() {
SslContextFactory sslContextFactory = createSslContextFactory(smartCardUiKeyStoreFile);
LOG.info("Using truststore file: " + trustStoreFile);
sslContextFactory.setTrustStore(trustStoreFile);
sslContextFactory.setTrustStorePassword("password");
sslContextFactory.setNeedClientAuth(true);
Connector connector = new SslSocketConnector(sslContextFactory) {
#Override public void accept(int acceptorID) throws IOException, InterruptedException {
Socket socket = _serverSocket.accept();
configure(socket);
SslConnectorEndPoint connection = new SslConnectorEndPoint(socket);
SMART_CARD_SOCKETS.add((SSLSocket) socket);
connection.dispatch();
}
};
As is apparent from the code, the SslSocketConnector.accept() method is overridden and the only part that is added is SMART_CARD_SOCKETS.add((SSLSocket) socket);. SMART_CARD_SOCKETS is a set that is later used for destroying the objects (sockets) being added to it. My question here is how to achieve the same functionality in Jetty 9, the point of which is that when the smart card is removed from the user's computer, the socket would be destroyed when the user attempts to navigate further.
I have tried to override the ServerConnector.accept() method in Jetty 9, however it uses a private method in it, which makes this impossible.
What you want is a custom implementation of HttpConfiguration.Customizer.
Add it to your HttpConfiguration for the ServerConnector you are interested in, and then it will run with each accept.
Example use of SecureRequestCustomizer.java.
// Setup SSL
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath("/path/to/keystore");
sslContextFactory.setKeyStorePassword("password");
sslContextFactory.setKeyManagerPassword("password");
// Setup HTTPS Configuration
HttpConfiguration httpsConf = new HttpConfiguration();
httpsConf.setSecurePort(8443);
httpsConf.setSecureScheme("https");
httpsConf.addCustomizer(new SecureRequestCustomizer()); // adds ssl info to request
// Establish the ServerConnector
ServerConnector httpsConnector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory,"http/1.1"),
new HttpConnectionFactory(httpsConf));
httpsConnector.setPort(httpsPort);
server.addConnector(httpsConnector);
While my application is running, I'm not able to save static assets that are being served. This is kind of a pain for debugging as I'd like to just be able to refresh the page when editing css, js and html.
I thought that the line listener.getFileCache().setEnabled(false); below would do the trick, but it still won't let me edit these files. Is there something else I missed in the configuration?
Here's my application...
final HttpServer server = HttpServer.createSimpleServer(".", 8181);
WebappContext ctx = new WebappContext("Socket", "/");
//enable annotation configuration
ctx.addContextInitParameter("contextClass", "org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
ctx.addContextInitParameter("contextConfigLocation", "com.production");
//allow spring to do all of it's stuff
ctx.addListener("org.springframework.web.context.ContextLoaderListener");
//enable web socket support
final WebSocketAddOn addon = new WebSocketAddOn();
for (NetworkListener listener : server.getListeners()) {
listener.registerAddOn(addon);
//if false, local files (html, etc.) can be modified without restarting the server
listener.getFileCache().setEnabled(false);
}
//add jersey servlet support
//#todo add spring support to jersey
ServletRegistration jerseyServletRegistration = ctx.addServlet("JerseyServlet", new ServletContainer());
jerseyServletRegistration.setInitParameter("com.sun.jersey.config.property.packages", "com.production.resource");
jerseyServletRegistration.setLoadOnStartup(1);
jerseyServletRegistration.addMapping("/api/*");
//add atmosphere servlet support
AtmosphereServlet atmosphereServlet = new AtmosphereServlet();
AtmosphereFramework f = atmosphereServlet.framework();
ReflectorServletProcessor r = new ReflectorServletProcessor();
r.setServletClassName("com.sun.jersey.spi.spring.container.servlet.SpringServlet");
f.addAtmosphereHandler("/socket/*", r);
ServletRegistration atmosphereServletRegistration = ctx.addServlet("AtmosphereServlet", atmosphereServlet);
atmosphereServletRegistration.setInitParameter("org.atmosphere.websocket.messageContentType", "application/json");
atmosphereServletRegistration.setInitParameter("com.sun.jersey.config.property.packages", "com.production.resource");
atmosphereServletRegistration.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
//atmosphereServletRegistration.addMapping("/socket/*");
atmosphereServletRegistration.setLoadOnStartup(1);
//serve static assets
StaticHttpHandler staticHttpHandler = new StaticHttpHandler("src/main/web");
server.getServerConfiguration().addHttpHandler(staticHttpHandler, "/");
Update:
I ended up migrating my AngularJS application outside of the java world due to the nature of AngularJS not needing to be a part of the java project.
I'm using Jetty 6 as an embedded web server in my Java app. Heretofore, I've had no reason to serve static content, but now I'd like to not only serve static content but also show directory listings.
I've tried using the ResourceHandler class to do this, but at some point mortbay removed the ability for ResourceHandler to do directory listing.
I'd like to do this without adding .jsp or servlet functionality and without web.xml configuration. In short I'm trying to do this programmatically.
For the life of me, I can't find any examples for this online. Could someone point me in the right direction?
Thanks much!
Okay, I figured out how to get Jetty to do what I wanted, which once again was to host some static content in addition to handling some custom servlets.
Ostensibly, the way to do this was to create a DefaultServlet and set the resourceBase and pathSpec accordingly, to allow me to host some directory on /www/*. However, this never worked. In fact, I couldn't find any explanation as to how the pathSpecs actually work or are supposed to be defined.
Thus, I had to create an additional ServletHandler and Context and add both my orginal Context and the new one for static content hosting to the Server.
I did that like so:
Server srv = new Server( port );
// create context and handler for my servlets
Context ctx = new Context();
ServletHandler sh = new ServletHandler();
// ... adding servlets here ...
// create context and handler for static content
ServletHandler sh2 = new ServletHandler();
ServletHolder holder = new ServletHolder( new DefaultServlet() );
holder.setInitParameter("resourceBase", staticResourceBase);
sh2.addServletWithMapping( holder, "/*" );
staticContext.setContextPath(staticPathSpec);
staticContext.setServletHandler(sh2);
// add both contexts to server
ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.setHandlers(new Handler[] { staticContext, ctx });
srv.setHandler(contexts);
This may not be the preferred way to do this, but it did allow me to add static content hosting to my Jetty-based app programmatically.
If you have a webapp and just jetty running, I think the default is to serve any static content out of the webapp root directory (e.g. the same directory that WEB-INF resides in). So for example you might have the following directories:
mywebapp
- WEB-INF
- static
- site_img.gif
And you can now serve http://mysite.com/static/site_img.gif
Correct me if I'm wrong and I'll remove this answer, this is just off the top of my head.
Tomcat offers a build in "Virtual Hosting" Support: An Engine/Web-Application can be configured to be responsible for a list of Domains. These Domains have to be put into the server.xml/context.xml files with a special xml directive.
=> Is there any possibility to change the Tomcat Configuration (in general) and especially the "Virtual Hosts" of a Web-Application/Engine programmatically?
For example if a new user signs up, I have to add his domain to the list of "accepted virtual hosts/domains". The only way I currently think of is changing the xml files via a script and then restart Tomcat.
Is there any way to add them add runtime via some Java-Methods programmatically?
Thank you very much!
Jan
Tomcat provides APIs to create new virtual host. To get access to the wrapper object needed for this, you need to implement a ContainerServlet. You can create virtual host like this,
Context context = (Context) wrapper.getParent();
Host currentHost = (Host) context.getParent();
Engine engine = (Engine) currentHost.getParent();
StandardHost host = new StandardHost();
host.setAppBase(appBase);
host.setName(domainName);
engine.addChild(host);
You need to make sure appBase directory exist and you have to find ways to persist the new host to the server.xml or you lose the host on restart.
However, this approach rarely works. If your users run their own apps, you really want run separate instances of Tomcat so you can sandbox the apps better. e.g. One app running out of memory doesn't kill all other apps.
If you provide the app, you can just use one host (defaultHost). You can get the domain name from Host header and do whatever domain-specific stuff in your code.
You shouldn't change the server environment programmatically and there are no reliable and standard ways to do this. Best is to do and keep it all on the webapp side. To start, a Filter is perfectly suitable for this. Store the names somewhere in a database table or a properties file which you cache in the application scope. Check the HttpServletRequest#getRequestURI() (or the getServerName() if it is a subdomain instead of pathinfo) and do the forwarding task accordingly.
Hope this helps.
Use JMX
ArrayList serverList = MBeanServerFactory.findMBeanServer(null);
MBeanServer server = (MBeanServer) serverList.get(0);
Object[] params = { "org.apache.catalina.core.StandardHost", hostName };
String[] signature = { "java.lang.String", "java.lang.String" };
server.invoke(new ObjectName("Catalina:type=Engine"), "addChild", params, signature);
If needed, retrieve the host object and work with it:
ObjectName host = new ObjectName("Catalina:type=Host,host=" + hostName);
server.setAttribute(host, new Attribute("autoDeploy", false));
server.invoke(host, "start", null, null);
I would suggest you set your application to be the default virtual host in server.xml so your single virtual host can respond to requests addressed to any host name. Tomcat ships with the localhost application set as the default virtual host. So you can see how to do this by simply inspecting the server.xml file of a vanilla tomcat installation. You can programatically determine the host name the user sent the request to using the ServletRequest.getServerName() method.
Tomcat used to ship with a web application called "host-manager". Note: this is different than the "manager" web application that still comes with Tomcat. Host manager allowed for changing configuration or adding new virtual hosts on the fly without restarting the server. You could interact with the host-manager over HTTP (programmatically if desired). However, it had the unfortunate flaw of not committing its changes to server.xml so they were all lost on a web server restart. For whatever reason, starting with version 6, Tomcat no longer ships with the host-manager application. So it doesn't appear to be supported anymore.
To sum up ZZ Coder answer which guided me a lot:
You have to create a servlet that implements ContainerServlet and override setWrapper method to get the org.apache.catalina.Wrapper object.
For doing that you have to have privileged="true" in your context.xml Context tag or it will throw an exception. Then you can use the Wrapper object and:
StandardContext context = (StandardContext) wrapper.getParent();
StandardHost currentHost = (StandardHost) context.getParent();
StandardEngine engine = (StandardEngine) currentHost.getParent();
StandardHost host = new StandardHost();
host.setAppBase(currentHost.getAppBase()); //in my case I created another instance of the same application
host.setDomain(currentHost.getDomain());
host.setAutoDeploy(false); // not restarting app whenever changes happen
host.setName("domain.com");
host.setThrowOnFailure(true);// tell it to throw an exception here if it fails to create the host
host.setDeployOnStartup(true);
host.setStartChildren(true);
host.setParent(engine);
// you can add multiple aliases
host.addAlias(alias);
StandardContext ctx = new StandardContext();
ctx.setDocBase(context.getDocBase()); //again I reused my same application setting
ctx.setPath("");
if(currentHost.getWorkDir() != null)
{//create a working directory based on your new host's name
ctx.setWorkDir(currentHost.getWorkDir().replace(currentHost.getName(), host.getName()));
}
ctx.setName(host.getDomain());
//some extra config that you can use
ctx.setUseHttpOnly(false);
ctx.setReloadable(false);
ctx.setXmlValidation(false);
ctx.setXmlNamespaceAware(false);
ctx.setCrossContext(false);
ctx.setParent(host);
// you have to have this or it will not work!!
ctx.addLifecycleListener(new ContextConfig());
//you can also create resources and add it to the context like so:
final ContextResource res = new ContextResource();
res.setName("name");
res.setAuth("Container");
res.setType("javax.sql.DataSource");
ctx.getNamingResources().addResource(res);
host.addChild(ctx);
engine.addChild(host);
You can add properties to your resource by calling res.setProperty("name", "value")
Some properties that you can use are:
initialSize,maxTotal,maxIdle,maxWaitMillis,removeAbandonedOnBorrow,removeAbandonedTimeout,validationQuery,timeBetweenEvictionRunsMillis,driverClassName,url,username,password.
Another exciting thing to is to get the host from the tomcat engine by calling engine.findChild(domain) and use stop(), start(), getStateName() and have your own Tomcat Admin panel!