Saturday, August 28, 2010

Accessing a Network Drive from Apache and Tomcat on Windows Server

A few quick tips if you find yourself having to access a network drive from Apache and Tomcat on a Windows Server. This is all pretty much old hat but since I still get questions about this fairly regularly and was just setting up some things on some servers this weekend, I figured I'd write this up.


In my situation we're running an application on three VMs behind a load balancer, and users can upload files from the application. Since I didn't want to set up a process behind the scenes that copied uploaded files between all three servers (though it would have given me a chance to take another run at using Unison), we have an uploads directory living on a SAN. This way no matter which VM the user is on, the uploads all go to and are served from a single file system.


On Windows Server, by default services run under the Local System Account, which doesn't have access to network resources. So if you install Apache and Tomcat as services and expect to be able to point them to a UNC path for some of your content, that won't work out of the box. You need to be running Apache and Tomcat under an account that has network access. In most environments in which I've worked this type of account is typically called a "service account," because you'll end up getting a domain account just like your typical Active Directory user account, but of course it won't be associated with a human being.


Once you have that account in place, you go into your services panel, right click on the service, click on "Properties," and then click the "Log On" tab. You'll see by default the Local System Account radio button will be checked. Click the radio button next to "This Account," enter your service account information, click "OK," and then restart the service. At this point your service will be running under the service account and will have access to network resources. Note that you'll have to do this for each service that needs access to the network drive, which in my case meant doing this for both Apache and Tomcat.


That takes care of the web server and things at the Tomcat level in terms of basic access, but you'll likely be configuring an alias of some sort to point to the network drive. In my case I wanted /uploads to point to \serversharenameuploads, which meant setting up an alias in Apache, a Context in Tomcat, and a mapping in OpenBD. This is where a lot of people get confused, so I'll go through each of these settings one by one.


The necessity for a web server alias is probably pretty obvious. If you're serving an image directly from your web server, e.g. http://server/uploads/image.gif, if /uploads doesn't exist under your virtual host's docroot, then Apache will throw a 404.


Allowing Apache to access the network drive involves (depending on how you have Apache configured) using a Directory directive to give Apache permission to access the directory, and then an Alias directive so Apache knows where to go when someone requests something under /uploads. So the following goes in your virtual host configuration file:



<Directory "\serversharenameuploads">
  Order allow,deny
  Allow from all
</Directory>

Alias /uploads "\serversharenameuploads"


You may have other stuff in your Directory directive as well but that's the basics of what will allow Apache to see /uploads as the appropriate location on your SAN.


The next layer down will be your CFML engine. Remember that if in your CFML code you want to read or write files to /uploads, even though Apache knows what that is now, your CFML engine will not. I'm emphasizing that point because it's such a common source of confusion for people. If things are happening in your CFML code, it won't be interacting with Apache at all, so it won't know about the Alias you set up in Apache. Simple enough to solve with a mapping; just go into your CFML engine's admin console and create a mapping that points /uploads to \serversharenameuploads and that handles things at the CFML level.


Lastly comes Tomcat. Depending on how you're serving files, you may be proxying from Apache to Tomcat, so if Tomcat needs to know where /uploads lives, since it's not in the webapp's base directory Tomcat will throw a 404 unless you tell it where /uploads is located.


Tomcat doesn't have Aliases in the same way Apache does, but what you can do in Tomcat is configure multiple Contexts under a single host. So in Tomcat's server.xml (or in a separate host config file if you prefer), you simply add a Context that points /uploads to the network location:



<Host name="myapp">
  <Context path="" docBase="C:/location/of/my/app" />
  <Context path="/uploads" docBase="\serversharenameuploads" />
</Host>


So now you have things set up in such a way that Apache, your CFML engine, and Tomcat all know where /uploads really lives.


Another point of confusion for people on Windows is the concept of "mapped drive" letters. A lot of people think that if you map \serversharename to a drive letter of let's say D:, than in your code you can then access the uploads directory we've been using as our example via D:uploads.


The simplest way to explain why this doesn't work is to point out that mapped drive letters are associated with a Windows user account. They don't exist at the operating system level. While you may remote into the server using your credentials, map to a network location, and assign that to drive letter D:, another user logging in won't see that mapping, and services running on the server under various user accounts definitely won't know anything about mapped drives.


This is why in all the examples above you see the full UNC path to the network resource being used. You have to use the UNC path in order to get this all to work correctly because that's the only way services running under a service account will be able to address the network resource.


Hope this helps eliminate some of the persistent confusion I see around this issue.

15 comments:

Anonymous said...

Doing this via the windows services is a pain, I have previously used http://jcifs.samba.org/ - a java smb client instead of mucking around with all this stuff. Really useful when your working with a cluster of serverslets you access a network drive using whatever account you like

Matthew Woodward said...

Very nice--hadn't heard of jcifs so thanks for the link!

tomliberty said...

Hi MattThanks for the article... started me off in the correct direction. One thing I have found that may be useful to someone like me who was struggling with accessing UNC paths with Apache 2.2 on Windows Server 2008...I needed to escape the "\\" before the server name. E.G.\\\SERVERNAME\Share (notice the additional whack at the start)Until I did this I was getting -client denied by server configuration: C:/SERVERNAME- in the error log.Cheers!

Matthew Woodward said...

Thanks! Seems a bit odd though since I'd think if you need to escape one you'd have to escape them both.Was the error in the Apache logs or where did you see the error message?I'll double-check my Windows settings but I think I was setting this up on Windows 2008 when I wrote this, but maybe it was 2003 and something changed. Still seems like you'd need four backslashes instead of three but anything's possible with windows. ;-)Thanks again for the additional info.

tomliberty said...

Yes indeed, you would think you would need to escape both whacks.The error was in %Program Files%\%apache dir%\logs\error.logInterestingly, if I generate a 404 I get //SERVERNAME/share/<path> in the error log...I will try the UNC alias set up with UNIX whacks and let you know if it works.

tomliberty said...

There we go. The UNIX style whacks do indeed appear to be the answer e.g.//SERVERNAME/shareOr\\\SERVERNAME\sharework for me... \\SERVERNAME\share results in Apache thinking \share is in my root drive.

Matthew Woodward said...

Nice! Thanks for the follow-up. Good info to have.

mandeep06 said...

Dear MatthewThanks for sharing this info. Really helpful. However, in my httpd.conf file when I use the Windows UNC path with ( \ ) slash to create Alais , save the file and restart apache. It restarts btu I can't browse to the directory.When I use the ( / ) slash to create an alias, save file and restart apache, it does not restart? Please can you suggest something ?

Matthew Woodward said...

Are you using two forward slashes at the beginning, e.g. //server/share?

ventdouest said...

Dear Matthew,Thanks for all theses infos.But for me, it doesn't work with TOMCAT.For information, I have a platform Business Objects BOXI R2 sp3 on windows 2003 environment. Some users schedule reports and use File location destination to send reports results on a directory located on an Unix server. This directory is accessible by a samba sharing. Anyway, these results must be accessible to all BO users by HTTP. So, on my tomcat server, i would create an alias in Tomcat to give access to excel reports for users . So, on Tomcat, i can create a context with followed parameters : Document base : //Servername/folder Path : /ExtractBO Creation have succeeded, but the problem is that i can’t access to folder by HTTP : On Tomcat server, When i go to http://localhost/ExtractBO, i have an error 404 with description : requested resource is not available. It seems that the new tomcat context fails to access to samba sharing. Can you help me ?

Steve Rozic said...

After two days of searching for documentation or an article based on the subject matter of your blog, I found nothing that explains it as well as this.

This is a fantastic post and helped clarify a lot of what was happening under Apache Tomcat and a Windows Server scenario (without all the detailed understanding of Windows, Account structure, etc).

My situation related directly to Java server side code that needed to copy a file that was uploaded to a Virtual Server. I originally set-up a network share between the two computers thinking this should work (until I started testing and received an IOException error). By creating an account that had access on both computers, then running Apache Tomcat under this new account and accessing the shared drive location using Windows UNC paths, the file that was uploaded to the application could now be copied from one server to the other.

Again, a big thanks for writing this up. Your are totally correct about the confusion people have understanding the suttle intricacies of Windows network paths and so on.

-=Steve

Matt Woodward said...

Thanks Steve -- glad you found this helpful!

And my apologies to people who've commented to whom I didn't respond. Blogger's notifications are inconsistent at best so I didn't know most of these comments were here. I'll take a look today and see if I can assist.

Leslie Milton said...

This saved my life :-) Thank you for the post

Matt Woodward said...

Love it when old posts still save people time. Or lives. ;-) Thanks for letting me know it's still useful!

Amit Dhumal said...

Hi, I read your blog. I am facing the issue related with hosting REST web service of my machine for the remote machine. It shows "Connection time out" error. Will you tell me how to solve it?? Does their is any need of updating httpd.conf file??