Saturday, August 29, 2009

Map an external directory into your application directory in Tomcat

This article will explain how to map an external directory into your application directory (i.e. how to create a virtual directory) using Tomcat.
Let me first describe why you may need to create a virtual directory in your application. Suppose your application allows a user to upload images and there is no restriction on the total number of images that can be uploaded in your application. The folder in which the images are loaded will keep on growing in size from KBs to MBs to GBs, eventually the size of the application will also grow which will hamper the speed of your application.

Creating a virtual directory inside your context in Glassfish v3 Preview is straight forward as discussed in my previous post. While setting up the same in tomcat you need to make sure if you are using Tomcat 6 then it is version 6.0.18 or higher. The method that I had followed from the Tomcat manual does not works in version 6.0.16.

You might know that you can create a virtual host in tomcat and run your application by placing it anywhere outside of the "webapps" folder. Basically you are creating a new context which is outside the "webapps" folder. What I will be talking about is having something like "/yourContext/images", where the "images" folder is anywhere outside the "yourContext" folder which is inside "webapps" i.e. "images" can be anywhere other than "/webapps/yourContext/images".

Let us begin with what the Tomcat documentation says here,

The Context element represents a web application, which is run within a particular virtual host. Each web application is based on a Web Application Archive (WAR) file, or a corresponding directory containing the corresponding unpacked contents, as described in the Servlet Specification (version 2.2 or later).
You may define as many Context elements as you wish. Each such Context MUST have a unique context path. In addition, a Context must be present with a context path equal to a zero-length string. This Context becomes the default web application for this virtual host, and is used to process all requests that do not match any other Context's context path.

For Tomcat 6, unlike Tomcat 4.x, it is NOT recommended to place elements directly in the server.xml file. This is because it makes modifing the Context configuration more invasive since the main conf/server.xml file cannot be reloaded without restarting Tomcat.

Context elements may be explicitly defined:
  • In the $CATALINA_BASE/conf/context.xml file: the Context element information will be loaded by all webapps.
  • In the $CATALINA_BASE/conf/[enginename]/[hostname]/context.xml.default file: the Context element information will be loaded by all webapps of that host.
  • In individual files (with a ".xml" extension) in the $CATALINA_BASE/conf/[enginename]/[hostname]/ directory. The name of the file (less the .xml extension) will be used as the context path. Multi-level context paths may be defined using #, e.g. foo#bar.xml for a context path of /foo/bar. The default web application may be defined by using a file called ROOT.xml.
  • Only if a context file does not exist for the application in the $CATALINA_BASE/conf/[enginename]/[hostname]/, in an individual file at /META-INF/context.xml inside the application files. If the web application is packaged as a WAR then /META-INF/context.xml will be copied to $CATALINA_BASE/conf/[enginename]/[hostname]/ and renamed to match the application's context path. Once this file exists, it will not be replaced if a new WAR with a newer /META-INF/context.xml is placed in the host's appBase.
  • Inside a Host element in the main conf/server.xml.
With the exception of server.xml, files that define Context elements may only define a single Context element.

Let us concentrate on the bullet point:
  • In individual files (with a ".xml" extension) in the $CATALINA_BASE/conf/[enginename]/[hostname]/ directory. The name of the file (less the .xml extension) will be used as the context path. Multi-level context paths may be defined using #, e.g. foo#bar.xml for a context path of /foo/bar. The default web application may be defined by using a file called ROOT.xml.
It tells you how to create a multilevel context path, in other words how to create a virtual directory inside your context.

The method is simple, which involves the following steps:

1. Open the CATALINA_HOME location which is where you have installed tomcat.
2. Locate and change directory to "conf"
3. Now create a directory called "Catalina".
4. Now create another directory "localhost" inside the newly created directory "Catalina".
5. Inside "localhost" create a file called "yourContext#images.xml", replacing "yourContext" with your context name.
6. Now inside the above xml file put the following line:
<context docbase="d:/images"></context>
7. Now restart Tomcat server.

Now, when you load the Tomcat Manager you will find a new entry for the context "yourContext/images". Now this will point to the "images" directory in D drive as in the above example.

All the request made to "/yourContext/images" will be mapped to your directory "d:\images".

Now, you have an external directory mapped into your context which can be used to upload files or images.

23 comments:

Edward said...

Thanks, but setting up a nested context like this loses the 'security-constraint' settings defined in the web.xml of 'yourContext'. That is, yourContext#images doesn't use the web.xml for yourContext.
Any idea how to make it use the same security constraints?

Harkiran said...

Thanks for the comment.
Edward, you are correct in saying that the security constraint from the web.xml in yourContext will not work in the mapped context using yourContext#images.

Edward, I have not tried what you want to achieve, but as per my understanding the mapped directory is a context within Tomcat with a URL inside your context. So, you may try to create a WEB-INF directory inside the images folder and put a web.xml there with the required constraints.

Hope this works. Do post a comment on this if it works or not.

Edward Ivanovic said...

Thanks, Harkiran. That's actually exactly what I did just after I posted my comment and it worked fine. It's not ideal because I need to replicate the security constraints between the two web.xml files. Still, they don't change often, so it's not really a problem.

Harkiran said...

Thanks Edward for posting what worked for you. I am happy to know that my blog provided you the information that you were looking for :)

Unknown said...

Hi Harikiran, I wanted to use an external directory as a repository of my static files which I want to make available for download.
I created a mapping in under CATALINA/localhost with the following xml(named admin#download.xml)
context docBase="D:/dev/apps"/

It did not work. Tomcat reports error on
startup "SEVERE: Error deploying configuration descriptor"
I modified the context definition to this.
Context path="/download/" docBase="D:/dev/apps"

This did not report any error. However, when I request a resource
http://localhost:8080/admin/download/ts/asc.jar,

it throws 404 error. The resource is present at D:\dev\apps\ts\asc.jar.
Am I missing something?

Harkiran said...

Hi Alok, I believe u have created the admin#download.xml file in the location c:\tomcat\conf\Catalina\localhost assuming the tomcat is installed at C:\tomcat

Now in the xml file edit your entry and make it

<context docbase="D:/dev/apps">
</context>

After doing this restart tomcat. Now login into the admin console and make sure there is an entry for /admin/download context in application manager. Now put a HTML/JSP file under D:\dev\apps and try to load it in the browser.

http://localhost:8080/admin/download/xyz.html

It should work.

Note: I have removed path="/download/" from the admin#download.xml

Anonymous said...

@Harkiran

What if the mapped directory is not a context within tomcat?

Edward Petersen said...

Harkiran, I just found this: http://jefferyhaynes.net/2009/12/30/severe-error-deploying-configuration-descriptor/

Which says that the configuration files are case-sensitive. So you want:

<Context docBase="D:/dev/apps">
</Context>

... noting the capital C in Context and the capital B in docBase.

Much thanks to Jeffery Haynes.

I had the same error and it now works for me.

But I hope you have found that solution by now, since it's been almost a year.

Now if I could just get it so requests to files in my download directory are always treated as downloads, that would be great.

For instance, if a file in the downloads directory is test.xml, force the browser to prompt for download instead of displaying the file. But I need my solution to cover ANY file places in the downloads directory...

Harkiran said...

Hi Edward,

I am not sure about the capital 'C' and 'B' in the config file. The post is an old one, and I posted it after implementing it. So, I am not sure if its a typo. Thanks for pointing it out.

Now for creating a downloadable link to a file you can write a servlet which will make the browser to prompt to download. I had done a similar thing earlier where I created a link which gave a prompt to download a pdf file instead of opening it in the browser. I have a sample code on my blog which is for a zip file. I am sure you will be able to make use of it.

http://harkiran-howtos.blogspot.com/2010/01/creating-zip-file.html

Sarath said...

Hi all I am newbie to java, trying to make file upload script which is working fine when path is hard coded like c:\mycon\profile-pictures\
But when I try to follow the steps here, couldn't make the script work :(
Any help would be of great help.

Below are my settings:

1. In mycon#profile-pictures.xml I have...



I have created profile-pictures folder in mycon root.

2. In tomcat app manager does show the new context path: http://localhost:8080/mycon/profile-pictures/

3. In web.xml I am trying to map using:

Location to store uploaded file
file-upload

/profile-pictures/



4. In my fileUpload servlet script when I try to get path from web.xml using:
String filePath =
getServletContext().getInitParameter("file-upload");

Following error message is displayed when trying to upload files:
java.io.FileNotFoundException: \profile-pictures\0412_Tech_Rgst(1)b.png (The system cannot find the path specified)

King of Fools said...

Harkiran,

Just dropping a note to say that I think I can confirm the capitol C and B.

I just tried the implementation, and it didn't work. Then I made the change to capitolize those letters, and it worked like a charm.

Michael said...

I did this confugartion as you explained, but I ran into one following problem:
- I used authentification for my website and so the user had to login twice, one time for each docBase, and this is not what I wanted, so I searched around and I found the following easier way:

An easier way to map an external directory into your webapp is the usage of symoblic links, see: http://en.wikipedia.org/wiki/Symbolic_link,
into your web app.

If you use windows than please do not use the shortcut operation, just open the dos command with administrative rights and execute e.g. the following line:
mklink /d "C:\GWT\workspace\SFB799\WebContent\files" "E:\Java\sfb799_dir"

The advantage of this is, that you do not have to reconfigure Tomcat and the new programmer sees the included directory in the webcontent folder.

Anonymous said...

Hi can u pls tell me how we can give the external folder's path in the url as parameter like http://10.207.66.41:8080/external/Project?id=${ContextPath.id}&show=C:\"

And i want to map the context to external folder from the jsp or html file itself.

Harkiran said...

Hi Mohammed, if u have a fixed folder where u will be uploading content then u may define the path in web.xml and can concatinate it further using request parameters. hope this helps.

Thank you everybody for contributing. I have not been able to track or update my blog for over two years now. Had been preoccupied with projects and their timelines.

Unknown said...

Hi, Is it possible to set default path location whenever we click on browse button for uploading file? So user can directly select file from same

Anonymous said...

Hello Harikiran,
This question may not be related to your post but still any help would be appreciated.
I have a requirement to keep resources outside of tomcat like d:/resources.xml how can i load this xml file containg database resources on tomcat startup.

Thanks-

Sylvain said...

Hi guys, just to let you know that Tomcat7 as now an "aliases" attribute to set in the Context.
This permit to do the same and solves Edward issue, because the directory will be considered as part of the Context and the security-constraints of the web.xml will be applied.

Unknown said...

Hi guys, is there anyway to make this work with a windows network path or share?

Example



I can get this working if the docBase in on the machine where tomcat is running but get a 404 when pointing to a network path or share.
In the stderr log I see this error message:

java.lang.IllegalArgumentException: Document base \\xx.xx.xx.xxx\stage2_E\ATC_Documents does not exist or is not a readable directory

If I cut and paste the network path into a browser window and add the document I want to display it works (changers the \\ to file://)

Unknown said...

sorry...

Example

Unknown said...

sorry again ... doesn't seem to like the tags so here is the example without the opening and closing tags.

Context docBase="\\94.229.129.232\stage2_E\ATC_Documents\" path="/documents"

Harkiran said...

Hello Ian, it looks like your tomcat server is also on a Windows machine. Can you map the network path to a drive letter in My Computer and then use the drive letter in configuration and test again. Hope it works.

Duvan said...

Hi Harkiran, I has been working, trying to add a external folder in my tomcat, but i can´t to do this.

I try with the solution that you said, but i give this error, when i try to access to an images.

Failed to load resource: the server responded with a status of 404 (Not Found)
http://localhost:8080/erpsaas/images/Koala.jpg

I create this file
E:\CREDIORBE\WORK_SPACE\TRONCO\Servers\Tomcat v7.0 Server at localhost-config\Catalina\localhost\erpsaas#images.xml

E:\CREDIORBE\INSTALL\apache-tomcat-7.0.23\apache-tomcat-7.0.23\conf\Catalina\localhost\erpsaas#images.xml

I don´t know what is the problem.


Unknown said...

Thank you sir for posting this one.

Post a Comment