Tomcat: Change the Virtual hosts programmatically? - java

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!

Related

Using external server restlet framework

I am using the Restlet Framework, but now I want to change to a proper server instead of using localhost.
I have already added my php files (they access the java files using the rest_server URL) to the server's folder and my java files as well, but I am not sure how to change the code so it identifies where the new location of the files is.
Here is the code from IdentiscopeServer (constructor empty):
public static void main(String[] args) throws Exception {
//setsup our security manager
if (System.getSecurityManager() == null){
System.setSecurityManager(new SecurityManager());
}
identiscopeServerApp = new IdentiscopeServerApplication();
IdentiscopeServer server = new IdentiscopeServer();
server.getServers().add(Protocol.HTTP,8888);
server.getDefaultHost().attach("", identiscopeServerApp);
server.start();
}
I guess that the correct line to change is the one with "Protocol.HTTP, 8888". If the address of my new server is http://devweb2013.co.uk/research/Identiscope, how exactly do I set this up? Is there anything else necessary for it to work apart from just moving the files to a folder in the server?
The IdensticopeServerApplication is the following:
public class IdentiscopeServerApplication extends Application {
public IdentiscopeServerApplication() {
}
public Restlet createInboundRoot() {
Router router = new Router(getContext());
//attaches the /tweet path to the TweetRest class
router.attach("/collectionPublic", CollectionPublicREST.class);
router.attach("/collectionPrivate", CollectionPrivateREST.class);
router.attach("/analysis", AnalysisREST.class);
return router;
}
}
Thank you in advance, it is my first time using this Framework.
If I understand you correctly, you just want to run your main() method as the server, correct? In this case, the code for main() needs to be in a location that -- when running -- can provide the service at http://devweb2013.co.uk/research/Identiscope. Since you haven't stated what kind of server you are putting the code, I can't say where the best place to put the code would be. I assume you have superuser privileges on your deployment server, since the URL you provided implies port 80 will be serving your Identiscope web service (port 80 is a privileged port on most OS's). So as an answer, I can only provide general information.
On your deployment server, port 80 must be free (i.e. nothing else should be acting as a web server on port 80 on that machine) and the IdentiscopeApplication must be running on port 80. To do that, you need only change the line:
server.getServers().add(Protocol.HTTP,8888);
to:
server.getServers().add(Protocol.HTTP, 80);
then run the application as a user that is allowed to start servers on port 80 (preferably NOT the superuser). If you haven't already, you will need to get Java running on your deployment server and make sure all Restlet libraries are in the classpath where you plan to run your application.
If I understand what you are trying to do, then this should do the trick.

Websphere Application Level Environment Variables

Is it possible to set environment variables at the application level in websphere?
Websphere servers can host multiple applications. Any environment variable applied at the server level applies to all the applications on that server. Is it possible to create variables that only apply to individual applications?
For example:
Lets say we have a SpreadsheetApp and a DocsApp running on the same server. They both share some common code that can be configured via an environment variable called DocStorageLocation. Is it possible to set the DocStorageLocation differently for both applications?
Alternatively, is there another way of configuring multiple applications running on the same server?
QUESTION 1
Is it possible to set the DocStorageLocation differently for both
applications?
I don't think it is possible. Websphere's environment variables are meant to be used by the server itself. A variable has only three possible scopes, which are Server, Cluster and Node.
For instance, an ORACLE_JDBC_DRIVER_PATH environment variable on server1, node1 scope could be used for the JDBC provider on node1 (classPath = ${ORACLE_JDBC_DRIVER_PATH}/ojdbc14.jar).
The question is : "why can't I set a different value for my application only" ? But my guess is that as long as the server, clusters and nodes are started, it does not make sense to override this value for a deployed application.
Although I think it is not possible, I still tried. But I did not manage to override an environment variable set for the websphere server.
QUESTION 2
Alternatively, is there another way of configuring multiple
applications running on the same server?
Environment entry
You could add an environment entry to your web.xml deployment descriptor, a variable you can look up for.
<env-entry>
<env-entry-name>DocStorageLocation</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>C:/DocStorage</env-entry-value>
</env-entry>
Then test, look up for this variable in the java class :
//TEST
Object l_test = null;
try {
Context l_ctx = new InitialContext();
l_test = l_ctx.lookup("java:comp/env/DocStorageLocation");
} catch (NamingException e1) {
// TODO
e1.printStackTrace();
}
URL ressource for .properties file
You can create an URL ressource. It would link to a .properties file set on a local host or any server, so each property could be set to a different value depending on the environment.
For instance, JNDI name url/environmentJndiName with value file:///server1/environment.properties on server1, and file:///server2/environment.properties on server2.
Then on server 1, you could set docStorageLocation=value1 in the environment.properties file, docStorageLocation=value2 on server2.
In your deployment descriptor web.xml, the ressource's reference would be the same. You wouldn't have to change this reference in the java source :
<resource-ref>
<res-ref-name>url/environment</res-ref-name>
<res-type>java.net.URL</res-type>
<res-auth>Application</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
Then use this ressource to read the properties.
try {
Context l_ctx = new InitialContext();
URL l_url = (URL) l_ctx.lookup("java:comp/env/url/environment");
// New properties
Properties l_properties = new Properties();
// Load properties
this.loadProps(l_properties, l_url.getPath());
} catch (NamingException e1) {
// TODO
e1.printStackTrace();
} catch (IOException e) {
// TODO Bloc catch auto-généré
e.printStackTrace();
}
...
private void loadProps(final Properties p_properties, final String p_fileLocation)
throws IOException
{
// Open stream
BufferedInputStream l_is = new BufferedInputStream(
new FileInputStream(
new File(p_fileLocation)));
p_properties.load(l_is);
// Close stream
l_is.close();
}
You will need to bind the ressource reference url/environment of the web.xml to the JNDI name url/environmentJndiName set for this ressource on the websphere server. Modify the ibm-web-bnd.xml file with websphere, the sun-web.xml file with glassfish, etc.
THANKS
If there is a better solution, or if it does not answer the question, let me know. I am still learning but I have been working with websphere for a while - even if I prefer other solutions. Thanks, #+.

Standalone ActiveMQ client on GlassFish

Hello guys i have such issue i make all thing like tutorial says. So now i want to lookup my Topics and connection factories that i configured but it do not see them. i make something like :
try {
Properties propertiesAMQ = new Properties();
propertiesAMQ.load(new FileInputStream("AMQ.properties"));
logger.info("Property file loaded succesfully...");
propertiesAMQ.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.activemq.jndi.ActiveMQInitialContextFactory");
propertiesAMQ.setProperty(Context.PROVIDER_URL,
"tcp://localhost:61616");
Context ctx = new InitialContext(propertiesAMQ);
javax.jms.TopicConnectionFactory factory = (javax.jms.TopicConnectionFactory) ctx
.lookup("amqpool");
javax.jms.Topic mytopic = (javax.jms.Topic) ctx.lookup("amqmsg")
}
And recieve NameNotFoundException. If i use name of connection factory such as "ConnectionFactory" it will be ok but then it will not see my Topic What i did wrong? Have u other examples of this subject? I'm using glassfish 3.0.1 and AMQ 5.5.0
Probably you are missing the namespace, you can look the exact name in the glassfish console, but most probably it should be;
javax.jms.Topic mytopic = (javax.jms.Topic) ctx.lookup("java:amqmsg")
How do you create your Topic resource? I had a similar problem and the solution was to create the Admin Resource Object using Glassfish command-line tool 'asadmin'. Creating it using the Glassfish admin console did not work (causing NameNotFoundException).
I ended up creating my Queu resource with the following command: 'create-admin-object –restype javax.jms.Queue –raname activemq-rar-5.7.0 –property PhysicalName=queueName queueName'

Can I globally set the timeout of HTTP connections?

I have a program that uses javax.xml.ws.Service to call a remote service defined by a WSDL. This program runs on the Google App Engine which, by default, sets the HTTP connection timeout to 5 seconds{1}. I need to increase this timeout value since this service often takes a long time to respond, but since this request is not being made with URLConnection, I cannot figure out how to call URLConnection.setReadTimeout(int){2}, or otherwise change the timeout.
Is there any way to globally set the HTTP connection timeout on the App Engine? And, for purposes of sharing knowledge, how would one go about solving this sort of problem generally?
{1}: https://developers.google.com/appengine/docs/java/urlfetch/overview#Requests
{2}: http://docs.oracle.com/javase/1.5.0/docs/api/java/net/URLConnection.html#setReadTimeout(int)
You could try setting the sun.net.client.defaultConnectTimeout and sun.net.client.defaultReadTimeout system properties documented here, e.g.
System.setProperty("sun.net.client.defaultReadTimeout", "30000");
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");
EDIT
Sorry, just re-read and noticed this is on Google App Engine. I don't know for sure, but given the litigious relationship Google and Oracle have lately, I'm guessing GAE doesn't run the Oracle JVM. I'll leave this here in case someone else runs into a similar problem.
Try this:
Port port = service.getPort(endPointInterface); //or another "getPort(...)"
((BindingProvider) port).getRequestContext()
.put(BindingProviderProperties.REQUEST_TIMEOUT, 30);
See https://developers.google.com/appengine/docs/java/urlfetch/usingjavanet
You can do something like this to get a URLConnection:
URL url = new URL("http://www.example.com/atom.xml");
URLConnection tempConnection = url.openConnection();
tempConnection.setReadTimeout(10);
For App Engine with JAX-WS you have to set the request context (tested today with SDK 1.9.15). For normal machines you cannot go higher than 60s and would have to switch to the bigger machines (Bx) for better use a task queue.
For local testing you would normally use BindingProviderProperties.CONNECT_TIMEOUT and BindingProviderProperties.REQUEST_TIMEOUT, but they are not on the App Engine JRE White List and your code inspection might constantly warn you about that.
The equivalent strings can be used though:
com.sun.xml.internal.ws.connect.timeout
com.sun.xml.internal.ws.connect.timeout
For deployment to App Engine:
com.sun.xml.ws.connect.timeout
com.sun.xml.ws.request.timeout
A full example how to apply that to auto-generated code from JAX-WS 2.x, values have to be provided in milliseconds:
#WebEndpoint(name = "Your.RandomServicePort")
public YourServiceInterface getYourRandomServicePort() {
YourRandomServiceInterface port = super.getPort(YOURRANDOMSERVICE_QNAME_PORT, YourRandomServiceInterface.class);
Map<String, Object> requestContext = ((BindingProvider)port).getRequestContext();
requestContext.put("com.sun.xml.ws.connect.timeout", 10000);
requestContext.put("com.sun.xml.ws.request.timeout", 10000);
return port;
}

Getting instance ip from weblogic cluster

I have a problem on how to get an instance URL within a cluster with weblogic.
Description:
We have 2 domains: X and Y.
In each domain I have 2 clusters: c01 and c02
In each cluster I have instances: s01,s02,s03,s04
In each instances I have our system which contains of several components, let’s call the components A,B,C and D. I want to make a REST call from A to D which are still in the same instance. How will we get the URL and port to this REST service programmatically?
The problem is that I am just getting the cluster URL when calling InetAddress or alike. I have also played around with MBean, but we are not sure it’s correct way to go since I wont have any user/pass to fill in for Enviroment object when creating the context.
We don’t want this as a build property since we don’t want to do builds for each different instance.
Env:
SpringIntegration
Weblogic 10.3.3
Jersey
Maven
Thanks
Solution:
Got it from an RuntimeServiceMBean:
service = new ObjectName(
"com.bea:Name=RuntimeService,Type=weblogic.management.mbeanservers.runtime.RuntimeServiceMBean");
InitialContext ctx = new InitialContext();
MBeanServer mBeanServer = (MBeanServer) ctx.lookup("java:comp/env/jmx/runtime");
ObjectName rt = (ObjectName) mBeanServer.getAttribute(service, "ServerRuntime");
listenAddress = (String) mBeanServer.getAttribute(rt, "ListenAddress");
server = listenAddress.substring(0, listenAddress.indexOf("/"));
port = (Integer)mBeanServer.getAttribute(rt, "ListenPort");

Categories

Resources