Understanding FastCGI Settings for IIS 5.1 & 6
So here it is, my first ever blog post.
I am not new to posting about IIS on the internet. A quick newsgroup search reminds me that I've posted to around 900 threads related to IIS. The first time was on June 10, 1997, where I helped someone to get "server push" to work from a CGI on IIS.
It strikes me as a bit ironic that my first usenet post for IIS was on CGI, and my first blog post for IIS will be on FastCGI. In the interim 10 years, I've worked far more with ISAPI than CGI. In fact, it's my expertise with ISAPI that's drawn me to doing FastCGI support for IIS 5.1 and 6.
So what does ISAPI have to do with FastCGI? To answer the question, let's start by looking at plain old CGI.
CGI, which stands for "Common Gateway Interface" is a way for application developers to interact with a web server. And it is a very old specification. Every version of IIS that has ever shipped has supported CGI. It's a simple specification: Basically, when a web server receives a request, it creates a new process to handle a request. For the new process, the stdin handle is remapped so that it receives request entity from the client, the stdout handle is remapped so that it writes response data to the client, and the command line and environment variables are set to provide other server and request information to the CGI process. There is some magic that happens in the response that allows the CGI to specify custom response status and headers, but there's not much more to it than that.
The problem with CGI, from IIS's perspective, is that IIS is somewhat at a disadvantage to its competition in handling requests. The issue is that every single request will result in a new process spinning up, doing work, and shutting down. If you are on an operating system that has very light weight process creation, the performance is bound by the "doing work" part of the equation. Unfortunately, if you are on an operating system where process creation is an expensive operation, the "doing work" part of the equation can be overwhelmed by the "process creation" part. This is the reason that CGI flourishes on our main competition running on a Unix-based platform, but is not recommended on IIS.
This is most definitely not to say that IIS has a performance disadvantage. To the contrary, IIS is quite capable of keeping up with - and often surpassing - the performance of other web servers. The reason for this is ISAPI.
ISAPI, which stands for Internet Server Application Programming Interface, is completely internal to the web server process. When a new request comes in for an ISAPI, no new process is created. Instead, the web server calls an entry point in a dll that is loaded into the web server process. If the ISAPI is written with an understanding of how the operating system threading model works, it can be blindingly fast.
So what is FastCGI, and why do we need it? Basically, we need FastCGI due to the existence of popular CGI-based development platforms, not the least of which is PHP.
PHP has run on IIS for years, with both CGI and ISAPI implementations. Unfortunately, both implementations are a compromise when running on IIS. The CGI implementation is a compromise due to the performance characteristics of process creation on Windows. So why is the ISAPI implementation a compromise? Remember what I said above about "threading model"? When PHP runs as an ISAPI, it runs inside the web server process in a highly multithreaded process. As it turns out, the PHP implementation itself is thread safe - but lots of folks use extensions to PHP that may or may not be thread safe. If you end up using a non-thread-safe extension to PHP with the ISAPI, the result is an unstable server. For this reason, some people are able to run the ISAPI version of PHP, and some are not.
FastCGI offers a compromise solution that can deliver both performance and stability. To do this, FastCGI preserves almost all of the original CGI specification, including a non-multithreaded environment - but it allows the host CGI process to remain alive after the request finishes, so that it's ready to be reused for another request. Since the process can be used over and over, the cost of process creation drops out of the equation. The difference between a regular CGI and a FastCGI is that a FastCGI has a layer in the process that maps the FastCGI protocol into the stdin, stdout and such that CGI uses. There are available libraries (from outside of Microsoft) that can be simply linked into existing CGI source code with very minor modifications.
So what does my ISAPI background have to do with FastCGI?
Well, for the IIS 7 version of the PHP handler, it has nothing to do with it. In fact, the main parts of the FastCGI handler, and all of the IIS 7 module code were written by Rick James (who has written lots of FastCGI stuff in his blog, and is very active on the FastCGI forums.) This is because IIS 7 has an all new, and very powerful API, that works well for hosting many different platforms.
Earlier versions of IIS, on the other hand, don't have this rich new API. Because of that, it's necessary that the FastCGI handler for these versions of IIS to run on top of ISAPI. My day-to-day responsibilities don't give me much time to write code any more, so when I saw a need to port FastCGI to ISAPI, I jumped at the chance to keep the project for myself.
With the history lesson over, let's talk a bit about what the FastCGI handler has to do in IIS, which will lead to understanding what all of the configuration options do.
The FastCGI handler is broken into a few parts: Applications, the Application Manager, and various code to support the FastCGI protocol.
Because you probably want to be able to handle multiple, concurrent requests, it is necessary to have a pool of processes available and ready to handle a request. In the FastCGI handler, this pool of processes is called an application (and since the term "application" is already so overused in the web world, I often just go ahead and use the term "process pool".) There are a number of properties of a process pool that you might want to manage. For example, you probably want to specify the number of processes in the pool, or the number of requests a process is allowed to accept before it's shut down and recycled, etc.
The FastCGI handler can support multiple process pools. The reason for this is that you may want to run more than one kind of FastCGI on one server - for example, both PHP and Ruby. You may also have multiple sites on your server, and you may not want requests for those sites to share the same processes, especially if you want the site processes to run as different users. The piece that handles multiple process pools is called the application manager.
Finally, you need to wire the FastCGI handler into IIS, so that requests can be routed to it. As it turns out, IIS 7 does this in a different way than IIS 5.1 and 6. In this blog entry, I am only going to discuss IIS 5.1 and 6. This is done with an IIS configuration setting called "script maps", which basically associates a file extension with the ISAPI that should handle it. In addition, the script mapping has an optional setting that will only allow requests to be processed where the physical file associated with the request exists. This is the default setting for security reasons (which may be another good blog topic), but there are times when you may want to allow a URL which has no physical file behind it. The ISAPI extension that contains the FastCGI handler is called fcgiext.dll. So if you want the FastCGI handler to accept PHP requests, you would set up a script mapping that associates ".php" with fcgiext.dll; for Ruby, you would map ".rb" to fcgiext.dll, etc. In IIS 6 (but not 5.1), it is also possible to set up a "wildcard script map" where all requests can be routed to an ISAPI dll. If you create a wildcard script map for fcgiext.dll, all requests would go to FastCGI, regardless of file extension.
So now that we know how the FastCGI handler works, let's talk about the specific things that you can put in the fcgiext.ini file.
There is one section in fcgiext.ini file that is required, no matter what FastCGI you are trying to run. That is the types section. The purpose of the types section is to associate a file extension with a specific process pool. Note that this is different than the script mapping described above. If you set up script mappings, for example, for both .php and .rb, the FastCGI handler needs to know how they are different from each other, and the types section does this work.
To see what is possible, let me show the types section from the fcgiext.ini file on my test machine. This is the actual file that I used while writing and testing the FastCGI ISAPI:
[Types]
echo=c:\echo\echo.exe
php=c:\php\php-cgi.exe
fooby=c:\echo\echo.exe
php:123154536=PHP Site 2
php:123154537=PHP Site 3
*=Wildcard Mapping
The format of the file is pretty simple at first glance. It is basically "file-extension:process-pool". You can tell from the first three entries in this ini file that I probably have script maps set up that associate ".echo", ".php" and ".fooby" with fcgiext.dll, and you can see each of those is associated with a FastCGI compliant exe file. And in the first technical preview, this would have been it.
The next three entries are demonstrate new capabilites that were added in the second technical preview. There are two more ".php" associations, but they have some long number behind them, and they don't have exe files associated with them. The numbers behind them are site instance IDs. These numbers tell the FastCGI handler that the association should only apply to a specific site with that ID. And, starting with the second technical preview, the process name is no longer associated with the file extension in the types section. Now, the association is made between a file extension (which is optionally site specifc) and a section name that must appear later in the fcgiext.ini file. The reason for the latter change is that we need to be able to support declaring arguments for the process, and since we might want different pools running the same process with different arguments (a likely scenario for Ruby), we can't depend on the process name to define all of the process pool settings. For compatibility with the first technical preview, we will still use the section name as the exe path if it's not otherwise specified in the section.
Let's look at the section for the ".echo" association:
[c:\echo\echo.exe]
QueueLength=999
MaxInstances=20
InstanceMaxRequests=500
This section was created during development of the first technical preview. Since it doesn't have an ExePath setting, which is how the second tech preview declares the exe, the FastCGI handler will just use the section name for the path to the exe.
Now, lets look at the section for PHP Site 2, which only applies to the site with 123154536 as its ID:
[PHP Site 2]
ExePath=c:\php\php-cgi.exe
Arguments=site2
QueueLength=1001
MaxInstances=20
IdleTimeout=200
ActivityTimeout=20
RequestTimeout=60
InstanceMaxRequests=1000
In this section, you can see that we are associating ".php" requests on that site with c:\php\php-cgi.exe. Also, you can see that we are passing "site2" as an argument to the process when we start it. This is different from PHP Site 3, which looks like:
[PHP Site 3]
ExePath=c:\php\php-cgi.exe
Arguments=site3
QueueLength=1001
MaxInstances=20
IdleTimeout=400
ActivityTimeout=40
RequestTimeout=120
InstanceMaxRequests=1000
In this case, we are passing "site3" as the argument when we start c:\php\php-cgi.exe. Note that the Arguments setting is optional, and you can pass multiple arguments using a space delimiter just like you would on the console command line. If you need spaces within the arguments, instead of as a delimiter, you should be able to use quotes - again, just line the command line.
Before talking about the Wildcard Mapping, let's take this opportunity to look at the other settings that can be used in each process pool:
ExePath - The path to the process to use in the pool
Arguments - Arguments to pass to each process in the pool at start time. This setting is optional
QueueLength - What happens if you get more simultaneous requests than you have processes to handle them? Why, we queue them, of course. This setting controls the number of requests for the process pool that we will queue before they start failing. The default value is 1000 requests.
MaxInstances - This is the highest number of processes we will allow in the pool. The default is 10. Note that this number is a maximum. We won't create the processes until we need them. If you never have more than two concurrent requests, we will only start two processes.
InstanceMaxRequests - This is the number of requests that we will send to a process in the pool before we shut it down and recycle it. The default value is 1000.
IdleTimeout - This is the number of seconds that a process can site idle, without working on a request, before we will shut it down. The default is 300 seconds.
ActivityTimeout - The is the number of seconds that the FastCGI handler will wait for I/O activity from a process before we terminate it. The default is 30 seconds. Note that, internally, we double that number on the first I/O from a newly started process. This allows a process to take some extra time to start up without incurring the wrath of the timeout period.
RequestTimeout - The is the maximum amount of time that we'll allow for a FastCGI process to handle a request before we terminate it. The default is 90 seconds.
Now let's take a look at that '*' association with Wildcard Mapping. This is something that applies only to IIS 6. IIS 6 allows a special kind of script map that applies to all requests. If you set up fcgiext.dll as a "Wildcard Scriptmap", this assocation will catch any requests that don't have a more specific association. For example, with the types section above, if you request a "/foo.bar", the ".bar" file extension doesn't have a specific association, so it will be caught by this '*' association and go to the process pool called "Wildcard Mapping". Let's take a look at that section:
[Wildcard Mapping]
ExePath=c:\echo\echo.exe
QueueLength=1000
MaxInstances=20
InstanceMaxRequests=100
IgnoreExistingFiles=1
;IgnoreDirectories=1
Most of it looks just like the other process pool sections, but it has two new settings that are only used for wildcard script maps (and thus, only IIS 6): IgnoreExistingFiles and IgnoreDirectories. Remember way back in my discussion above about script mappings where I said that you might want to handle requests that don't map to a script file? If you do this, it would be very convenient if you could have the FastCGI handler handle these non-file requests, but still get IIS to handle other files in the directory normally - and that's exactly what these two settings provide:
IgnoreExistingFiles - If this is set to 1, the FastCGI handler will check to see if the requested URL exists as a file. If so, the FastCGI handler will defer processing of the URL so that it will be processed without FastCGI. The default value is 0.
IgnoreDirectories - If this is set to 1, the FastCGI handler will check to see if the requested URL exists as a directory. If so, the FastCGI handler will defer processing of the URL so that it will be processed without FastCGI. This effectively allows default documents and directory listings to be served. The default value is 0.
It is possible to set a site-specific wildcard mapping using the same site ID syntax that file extensions use (ie. "*:12345678=Section Name". The search order for file extensions is significant. When a request reaches the FastCGI handler, the associations in the types section are checked in the following order:
- A specific file extension association specific to the site ID
- A '*' association specific to the site ID
- A specific file extension association with no side ID specified
- A '*' association with no site ID specified
There is one other process pool setting that is pretty esoteric:
FlushNamedPipe - There are some cases where a FastCGI might not read all of the data from the named pipe that communicates with the web server. If this happens, the web server will wait for a read that isn't coming, effectively deadlocking that member of the process pool. This most often happens in the case where the FastCGI process exits when we don't expect, due to its having an internal notion of the max number of request is will handle that is lower than our InstanceMaxRequests setting. Setting FlushNamedPipe=1 causes FastCGI to flush data that might lead to this condition. The default is 0.
----------
So that is my first blog entry - whew! It turned out to be a whole lot longer than I expected, but I wanted to be thorough about how the FastCGI handler works on IIS 6 and how it is affected by each of the available configuration options.
If you have specific feedback about the content of the material or the presentation style, I would love to hear it. I promise that they won't all be this long...