Edgewall Software

Running Trac on IIS 6 using PyISAPIe

Contributed by: Aa`Koshh

Configuration:

  • Windows Server 2003
  • Python 2.6
  • IIS 6
  • Trac 0.12

I have multiple Django sites running on the server based on the instructions at https://code.djangoproject.com/wiki/DjangoOnWindowsWithIISAndSQLServer, which suggests using PyISAPIe, so that each site can use its own application pool, which is necessary to support Django configuration. Using this setup, each site can be restarted independently from the others. I wanted Trac to be served up the same way, and with a few modifications, it works, more or less.

  1. To support multiple sites, create a directory somewhere local to IIS and copy the PyISAPIe.dll and the Http module into it. This will only serve Trac.
  2. Follow the instructions on the Django site to set up a virtual directory based on the above copy of the dll, up until when the Info.py file works in its own application pool. Don't forget to add read permission on the dll to network service.
  3. Modify Isapy.py in the Http directory to call the Trac WSGI handler:
    # $URL: https://pyisapie.svn.sourceforge.net/svnroot/pyisapie/Tags/1.1.0-rc4/PyISAPIe/Python/Examples/Django/Isapi.py $
    from trac.web.main import dispatch_request 
    from Http.WSGI import RunWSGI
    from Http import Env
    import os
    
    os.environ['TRAC_ENV'] = r"\\myserver\path\to\trac\env"
    os.environ['PYTHON_EGG_CACHE'] = r"\\myserver\path\to\trac\env\eggs"
    
    # This is how the WSGI module determines what part of the path
    # SCRIPT_NAME should consist of. If you configure PyISAPIe as
    # a wildcard map on the root of your site, you can leave this
    # value as-is.
    # 
    Base = "/Trac"
    
    # This is an example of what paths might need to be handled by
    # other parts of IIS that still come here first. This value's
    # default of "/media" assumes that you've mapped a virtual
    # directory to Django's admin media folder and so expect the
    # files to be served by the static file handler.
    #
    Exclude = ["/Trac/chrome/common", "/chrome/common"]
    
    # The main request handler.
    
    Handler = dispatch_request
    
    def Request():
      PathInfo = Env.PATH_INFO.lower()
    
      # Check for anything we know shouldn't be handled by Python and
      # pass it back to IIS, which in most cases sends it to the static
      # file handler.
      
      if not PathInfo.startswith(Base.lower()):
        return True
      
      for Excl in Exclude:
        if PathInfo.startswith(Excl.lower()):
          return True
      
      return RunWSGI(Handler, Base=Base)
    
    
  4. Edit the WSGI.py module in the Http directory and uncomment REMOTE_USER in the IsapeEnvAuto list so that Trac can pick up the logged in user.
  5. When there is an error, Trac tries to present its error.html template, but also passes the exception info to the WSGI function start_response. PyISAPIe, however, re-raises the exception even though it was handled, so for example unhandled paths result in an IIS Internal Error page and a stack trace. I edited the WSGI.py again so that if Trac supports an exception info but headers are also present, no exception is raised to IIS:
    def StartResponse(Status, Headers, ExcInfo = None):
      if ExcInfo and not Headers : # only raise exception to IIS if Trac is not about to present the error page
        try:
          raise ExcInfo[0], ExcInfo[1], ExcInfo[2]
          
        finally:
          ExcInfo = None
         
      Status = int(Status.split(" ",1)[0])
      Header(Status = Status) 
      
      for NameValue in Headers:
        Lname = NameValue[0].lower()
        
        if Lname == "content-length":     
          Header(Length = int(NameValue[1]))       
          continue
          
        elif Lname == "connection":
          if NameValue[1].lower() == "close":
            Header(Close = True)
          continue
        
        Header("%s: %s" % NameValue)   
        
      return Write 
    
  6. After changes are made to either Python modules or the trac.ini file, right click on the application pool and click recycle. The site should display at this point.
  7. I had some problems with static files (Trac css and Javascript) over 4K in size not being served. The exception seems to be raised when the content length header is set in StartResponse above, maybe it is sent twice. I ended up setting up an IIS virtual directory to serve the htdocs directory of Trac and added the htdocs_location = /trac_media entry to trac.ini
  8. To support authentication, create an empty directory in the base Trac virtual directory in IIS and name it "login". Set directory security to Digest Authentication (it works behind Nginx, whereas Integrated Windows Authentication does not).
  9. I had some trouble with the admin account: when I accessed the site directly by IP and logged in with my Windows credentials, the remote user was "domain\user", but through our router it became "DOMAIN\user" with capital letters, and the roles defined were picked up only for the one that I explicitly granted it for with trac-admin. Whichever appears on the site, add permissions for that username like this: trac-admin $ENV permission add DOMAIN\user TRAC_ADMIN
  10. The header and footer did not appear on Trac pages (it did in the standalone server). I had to modify layout.html and remove the fallback tag at the bottom of the document like this:
      <xi:include href="$chrome.theme"></xi:include>
      <xi:include href="site.html"><xi:fallback /></xi:include>
    </html>
    
  11. I really hope nothing new will come up…
Last modified 13 years ago Last modified on Jun 27, 2011, 1:18:29 PM
Note: See TracWiki for help on using the wiki.