0
Apache Cookbook
Recipes for managing your Apache HTTP Server




          /         Rich Bowen - rbowen@apache.org
Shameless Plug




          Buy!
          Buy!
          Buy!
Old Hat ---->




     • 1.3 is end of life
     • 2.0 will be by the end of
       the year
     • 2.2 is now
     • 2.4 ...
URLs you need




    • http://people.apache.org/~rbowen
    • http://wiki.apache.org/httpd
    • http://httpd.apache.org/...
Recipes




 Photo CC by Chemical Heritage Foundation - Flickr
Directory listings are boring
Say it with style

IndexStyleSheet /styles/dir.css

...

.odd {
  background-color: #eef;
}

.even {
  background-color:
 ...
Caveat: Some features 2.4 only




     • In 2.2 and earlier, you can specify
       a style sheet, but no classes are
   ...
Now, with extra class
  <table id="indexlist">
   <tr class="indexhead">
<th class="indexcolicon">...
<th class="indexcoln...
An exercise for the reader:


                              • Nifty mouse-over effects
                                (JS...
mod_substitite




    •  New module in 2.2
    • Rewrite content using
       regular expressions
    • Syntax is identic...
s/foo/bar/


    • Simple example - switch fonts
   LoadModule substitute_module 
          libexec/apache2/mod_substitute...
Proxying

    • More useful example
    • Proxying to back-end server that returns
      fully-qualified URLs

LoadModule ...
mod_security




    • Are you running mod_security?
mod_security




    • You should be
http firewall




     • The earlier you catch it ...
Holes




        Photo CC by
         Darwin Bell
SQL Injection




     # Prevent SQL injection attacks
     SecFilter "delete[[:space:]]+from"
     SecFilter "insert[[:sp...
Make sure you ...




     # Inspect POST payloads
     SecFilterScanPOST On

     # Default action set
     SecFilterDefa...
While you’re at it




     # Inspect POST payloads
     SecFilterScanPOST On

     SecFilter “vidocin”




20
Acceptable arguments



# Only for the FormMail script
<Location /cgi-bin/FormMail>
   # Reject request where the value of...
Which PHP script is slagging my server?




     •   Which process, and what is
         it doing?
     •   Look at /serve...
Step 1: Look at top:



     •  Run ‘top’
     • Order by CPU usage
     • Pick off the httpd processes
        that are c...
Step 2: /server-status



     •   Look at /server-status
         output
     •   Look for the PIDs you
         noted
  ...
/server-status
ExtendedStatus On


    • You’ll need “ExtendedStatus On”
      to get these details:


            <Location /server-stat...
SNI



      • General knowledge: SSL
       requires one certificate per IP
       address
      • That is, only one SSL ...
SSL handshake
SSL handshake


                Certificate




                Hostname
Clearly this sucks
SNI
SNI




      • Server Name Indication
      • Passes server name in initial
          handshake
      •   Simple solution...
Caveats


    • You knew there would be a catch
   Mozilla Firefox 2.0 or later
   Opera 8.0 or later (the TLS 1.1 protoco...
Apache       • 2.2.12 or later
Listen 443
# Listen for virtual host requests on all IP addresses
NameVirtualHost *:443

# ...
More Info




http://wiki.apache.org/httpd/NameBasedSSLVHostsWithSNI
Secure mod_dav deployment


    • Security rule #1: Content is not
      writable
    • Corollary: Anyone telling you to
 ...
However ...




     •Setting up WebDAV
       requires that content be
       writable ...
     •And owned by Apache
    ...
WebDAV




         <Directory /var/www/content>
           Dav On
         </Directory>
Why this is a problem



     • People write bad code
     • It’s easy to get PHP (or whatever
       else) to overwrite y...
Secure DAV




     • It’s possible to set up Dav without
       having the files written by the
       Apache user
     •...
Two Apache Processes




      Primary                     Secondary
       server,                      server,
     runn...
Two Apache Processes


                             Read-only
                   Running ordinary set of modules
         ...
Two Apache Processes




          Read/Write
     Remove all extra modules
               SSL                   Secondary...
Multi-server config.
           drwxr-xr-x 2 dav dav 68 Oct 3 12:41 /var/www
1
Listen *:80
User www
Group www
DocumentRoot...
drwxr-xr-x 2 dav dav 68 Oct 3 12:41 /var/www


1
Listen *:80                    Can write
User www
Group www
DocumentRoot ...
drwxr-xr-x 2 dav dav 68 Oct 3 12:41 /var/www


1             Can’t
Listen *:80
User www
Group www
DocumentRoot /var/www/

...
<If>                         Picture by BrewBooks (Flickr)




       My favorite new feature in 2.4
<If>


       <If ‘$req{Host} = “www.example.com”’>
         RedirectMatch (.*) http://example.com/$1
       </If>
<If>


       <If ‘$req{Host} = “www.example.com”’>
         RedirectMatch (.*) http://example.com/$1
       </If>


   Th...
<If>


       <If ‘$req{Host} = “www.example.com”’>
         RedirectMatch (.*) http://example.com/$1
       </If>



    ...
Logging - Conditional Logging




     • Don’t log certain things
     • Per-directory logging

51
Conditional LogFormat



     • The LogFormat directive
      supports some conditionals in
      the variables
     • "%!...
Conditional LogFormat




     • "%400,501{User-agent}i" logs
      User-agent on 400 errors and
      501 errors only
   ...
Conditional CustomLog




     • Stick Env=xyz on the end
     • or Env=!xyz
     • Yes, that’s =! not !=

54
For example ...




SetEnvIf Request_URI .gif$ gif-image
CustomLog gif-requests.log common env=gif-image
CustomLog nongif-...
For example ...




SetEnvIf Request_URI .gif$ gif-image
CustomLog gif-requests.log common env=gif-image
CustomLog nongif-...
For example ...




SetEnvIf Request_URI .gif$ gif-image
CustomLog gif-requests.log common env=gif-image
CustomLog nongif-...
So the images get logged here




SetEnvIf Request_URI .gif$ gif-image
CustomLog gif-requests.log common env=gif-image
Cus...
And everything else goes here




SetEnvIf Request_URI .gif$ gif-image
CustomLog gif-requests.log common env=gif-image
Cus...
Per directory




SetEnvIf Request_URI ^/marketing mkt
CustomLog marketing.log common env=mkt




 60
PCRE Zero-width assertions



   • Match everything except one thing
   • While you can do this with
     RewriteRule, it ...
Negative Lookahead




    • PCRE provides a regex
        syntax to say “not preceded
        by” or “not followed by”
  ...
Andrei Rocks




    • While we’re on the topic:
    • Pretty much the best
      presentation on regular
      expression...
Everything except ...


     •   “I want to redirect everything except /
         images”

     •   This is where you’d us...
Everything except ...



     • Match anything ...
     •

           RedirectMatch ^/(?!images/)(.*) 
             http:/...
Everything except ...



     • Match anything ...
     • That doesn’t start with images/

           RedirectMatch ^/(?!i...
Everything except ...



      This is called a “zero width” assertion,
      because it doesn’t fill $1, and doesn’t
    ...
What the heck is it doing?


           RewriteLog /var/log/rewrite.log
           RewriteLogLevel 9
What the heck is it doing?


           RewriteLog /var/log/rewrite.log
           RewriteLogLevel 9


     • Alas, not in...
What the heck is it doing?


           RewriteLog /var/log/rewrite.log
           RewriteLogLevel 9



     • Most entrie...
What the heck is it doing?


           RewriteLog /var/log/rewrite.log
           RewriteLogLevel 9


     • If there’s n...
Logging - RewriteLog




     RewriteLog /var/log/rewrite.log
     RewriteLogLevel 9




73
Logging - RewriteLog




     RewriteLog /var/log/rewrite.log
     RewriteLogLevel 9




74
Logging - RewriteLog




     RewriteLog /var/log/rewrite.log
     RewriteLogLevel 9




75
76
Kind of intimidating, isn’t it?




77
Learn to ignore the irrelevant bits




78
Which bits are those?




        121.14.76.185 - - [24/Sep/2008:21:35:51 --0400]
     [wooga.drbacchus.com/sid#b83444a8][...
Client address




        121.14.76.185 - - [24/Sep/2008:21:35:51 --0400]
     [wooga.drbacchus.com/sid#b83444a8][rid#b85...
Dunno what those are




        121.14.76.185 - - [24/Sep/2008:21:35:51 --0400]
     [wooga.drbacchus.com/sid#b83444a8][r...
Time




        121.14.76.185 - - [24/Sep/2008:21:35:51 --0400]
     [wooga.drbacchus.com/sid#b83444a8][rid#b85b8d00/
   ...
Unique id




        121.14.76.185 - - [24/Sep/2008:21:35:51 --0400]
     [wooga.drbacchus.com/sid#b83444a8][rid#b85b8d00...
Request ID




        121.14.76.185 - - [24/Sep/2008:21:35:51 --0400]
     [wooga.drbacchus.com/sid#b83444a8][rid#b85b8d0...
The useful bit




        121.14.76.185 - - [24/Sep/2008:21:35:51 --0400]
     [wooga.drbacchus.com/sid#b83444a8][rid#b85...
Wouldn’t it be nice if you could just see the
     useful part?




86
You could change the way mod_rewrite logs:
Piped logs



      • I actually use a piped lot handler to
         remove this superfluous stuff
      • Like so ...
Rew...
RewriteLog |/usr/local/bin/rewrite_log_pipe
     RewriteLogLevel 9
          with ...

     #!/usr/bin/perl
     $|++;

  ...
RewriteLog |/usr/local/bin/rewrite_log_pipe
 RewriteLogLevel 9



       This bit says “instead of logging to a text
     ...
#!/usr/bin/perl
 $|++;

 open (F, ">>/tmp/rewrite");
 select F;
 while (<>) {
   s/^.*((d).*)/$1/;
   print;
 }


     • L...
Results in:




(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched
(3) appl...
Results in:




(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched
(3) appl...
Requested URI




(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched
(3) ap...
Patterns applied




(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched
(3)...
None of them matched




(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched...
And now




     • We can actually make some
      sense of what’s happening
     • Less inscrutable noise
     • Yes, it ...
Examples


(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched




       • ...
Examples



(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched



       • ...
Examples



 (4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
 .com' [NC] => not-matched




      ...
Examples


 (4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
 .com' [NC] => not-matched




       ...
Another example


      (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/
                             index.php'


...
Again ...


      (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/
                             index.php'



     ...
And ...


      (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/
                             index.php'



       ...
Matched?

      (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/
                             index.php'


        ...
The whole thing




(3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite'
(2) rewrite '/books/rewrite' -...
The match:




(3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite'
(2) rewrite '/books/rewrite' -> 'ht...
Followed by




(3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite'
(2) rewrite '/books/rewrite' -> 'h...
[R]




(3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite'
(2) rewrite '/books/rewrite' -> 'http://ww...
But it all runs together!



       • Look for:
       •
      (2) init rewrite engine with requested uri /atom/1


      ...
Load balancing
Fortunately ...




                            Photo CC by Camo53 (flickr)



     • mod_proxy_balancer
     • Added in 2.1
Fairly simple to configure




       <Proxy balancer://mycluster>
         BalancerMember http://192.168.1.50:80
        ...
Fairly simple to configure




       <Proxy balancer://mycluster>
         BalancerMember http://192.168.1.50:80
        ...
Fairly simple to configure




       <Proxy balancer://mycluster>
         BalancerMember http://192.168.1.50:80
        ...
Sticky Sessions




      Ensures that connections go to the same server they
                         started with.


Pro...
Balancing measures




  ProxyPass / balancer://hotcluster/
  <Proxy balancer://hotcluster>
   BalancerMember http://1.2.3...
1.2.3.5 gets twice the traffic




  ProxyPass / balancer://hotcluster/
  <Proxy balancer://hotcluster>
   BalancerMember ...
Hot spare




  ProxyPass / balancer://hotcluster/
  <Proxy balancer://hotcluster>
   BalancerMember http://1.2.3.4:8009 l...
bytraffic or byrequests




  ProxyPass / balancer://hotcluster/
  <Proxy balancer://hotcluster>
   BalancerMember http://...
BalancerManager




      <Location /balancer-manager>
       SetHandler balancer-manager

       Order Deny,Allow
       ...
124
Disable a
      particular host




125
Notes




      • The things on the other end don’t have to
         be Apache
      • This is a popular way to set up Rub...
Bandwidth limiting
                       Picture by Joachim S. Müller (Flickr)




        People always want their websi...
In 2.4 ...




      • 2.4 adds two new modules for this
         purpose
      • mod_dialup
      • mod_ratelimit
mod_dialup




     Party like it’s 1999
v.92? Really?
                <Location /mysite>
                  ModemStandard V.92
                </Location>

      A...
mod_ratelimit

     <Location /downloads>
       SetHandler RATE_LIMIT
       SetEnv rate-limit 400
     </Location>




 ...
Prior to 2.4


      A variety of other modules do bandwidth
      kind of things:




                   mod_cband
      ...
Logging




    • mod_logio
    • mod_log_forensic
Obligatory ridiculous log photo




                                  Photo by Zevotron (Flickr)
Not enough




    • Your log files don’t tell you enough
    • Want more
Logging - mod_logio




      Complete INPUT and
      OUTPUT size



136
Logging - mod_logio




      Complete INPUT and
      OUTPUT size



137
Combined Log Format - Bytes transferred




138
Less than half the story

       • Only bytes transferred to the
          client
       • Doesn’t include headers
       ...
mod_logio



      •Adds two additional
       variables
      •%I - Input bytes
      •%O - Output bytes
      •Includes ...
LogFormat




141
mod_log_forensic




    • Did it ever finish?
ForensicLog

 ForensicLog /var/log/httpd/forensic.log
ForensicLog

 ForensicLog /var/log/httpd/forensic.log




    • Post-process with the check-forensic
       script to tell...
mod_speling




               CheckSpelling On
              CheckCaseOnly On
Image Theft


  SetEnvIf Referer 
          ".example.com/" local_referal
  # Allow browsers that do not send Referer info...
Or ...




RewriteEngine on
RewriteCond %{HTTP_REFERER} !=""
RewriteCond %{HTTP_REFERER} !example.com [NC]
RewriteRule .(j...
Or ...
RewriteEngine on
RewriteCond %{HTTP_REFERER} !=""
RewriteCond %{HTTP_REFERER} !example.com [NC]

# depending upon i...
Or ...
RewriteEngine on
RewriteCond %{HTTP_REFERER} !=""
RewriteCond %{HTTP_REFERER} !example.com [NC]

# depending upon i...
Query Strings and Path Info
Query Strings




monkeys.php?q=lemur&color=green&level=99.7&doc=info.txt

     • Query Strings are:
      • Ugly
      • ...
Query Strings




monkeys.php?q=lemur&color=green&level=99.7&doc=info.txt


     • Query Strings are:
      • Ugly
      •...
Path Info




monkeys.php?q=lemur&color=green&level=99.7&doc=info.txt


             /monkeys/lemur/green/99.7/info
mod_rewrite




monkeys.php?q=lemur&color=green&level=99.7&doc=info.txt

             /monkeys/lemur/green/99.7/info



  ...
Engineering for failure




     •   Why not do it right to
         begin with, and avoid the
         mess?
     •   PHP...
Step One: SetHandler
              /monkeys/lemur/green/99.7/info

    • We want monkeys to be a PHP script
    • We renam...
Step Two: explode()
             /monkeys/lemur/green/99.7/info

    • The rest of the solution is in your
       PHP:
   ...
While we’re at it


     • File extensions are *so* 1980s
     • All your files are php files, right?
     • Why give them...
Write a better FM




     • I’ll never get this far in the presentation,
       right?
Apache Cookbook - TekX Chicago 2010
Apache Cookbook - TekX Chicago 2010
Apache Cookbook - TekX Chicago 2010
Upcoming SlideShare
Loading in...5
×

Apache Cookbook - TekX Chicago 2010

4,494

Published on

Presentation for TekX in Chicago, 2010. Apache Cookbook by Rich Bowen

Published in: Technology
0 Comments
8 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
4,494
On Slideshare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
236
Comments
0
Likes
8
Embeds 0
No embeds

No notes for slide


  • Old Hat




























































































































































  • Transcript of "Apache Cookbook - TekX Chicago 2010"

    1. 1. Apache Cookbook Recipes for managing your Apache HTTP Server / Rich Bowen - rbowen@apache.org
    2. 2. Shameless Plug Buy! Buy! Buy!
    3. 3. Old Hat ----> • 1.3 is end of life • 2.0 will be by the end of the year • 2.2 is now • 2.4 is tomorrow Photo CC by “Lost Albatross” - Flickr
    4. 4. URLs you need • http://people.apache.org/~rbowen • http://wiki.apache.org/httpd • http://httpd.apache.org/docs/trunk/
    5. 5. Recipes Photo CC by Chemical Heritage Foundation - Flickr
    6. 6. Directory listings are boring
    7. 7. Say it with style IndexStyleSheet /styles/dir.css ... .odd { background-color: #eef; } .even { background-color: #fff; }
    8. 8. Caveat: Some features 2.4 only • In 2.2 and earlier, you can specify a style sheet, but no classes are added to the HTML • Useful, but not quite as useful
    9. 9. Now, with extra class <table id="indexlist"> <tr class="indexhead"> <th class="indexcolicon">... <th class="indexcolname">... <th class="indexcollastmod">... <th class="indexcolsize">... <th class="indexcoldesc">... <tr class="indexbreakrow">... <tr class="even"><td class="indexcolicon">...
    10. 10. An exercise for the reader: • Nifty mouse-over effects (JS in HeaderName file?) Photo CC by Ugglan - Flickr • AJAXy file interaction of some kind? • Photo gallery, entirely based on mod_autoindex and Javascript?
    11. 11. mod_substitite • New module in 2.2 • Rewrite content using regular expressions • Syntax is identical to sed
    12. 12. s/foo/bar/ • Simple example - switch fonts LoadModule substitute_module libexec/apache2/mod_substitute.so AddOutputFilterByType SUBSTITUTE text/html Substitute s/ariel/courier/i
    13. 13. Proxying • More useful example • Proxying to back-end server that returns fully-qualified URLs LoadModule substitute_module libexec/apache2/mod_substitute.so AddOutputFilterByType SUBSTITUTE text/html Substitute s/backend.local/www.example.com/ni
    14. 14. mod_security • Are you running mod_security?
    15. 15. mod_security • You should be
    16. 16. http firewall • The earlier you catch it ...
    17. 17. Holes Photo CC by Darwin Bell
    18. 18. SQL Injection # Prevent SQL injection attacks SecFilter "delete[[:space:]]+from" SecFilter "insert[[:space:]]+into" SecFilter "select.+from" 18
    19. 19. Make sure you ... # Inspect POST payloads SecFilterScanPOST On # Default action set SecFilterDefaultAction "deny,log,status:406" 19
    20. 20. While you’re at it # Inspect POST payloads SecFilterScanPOST On SecFilter “vidocin” 20
    21. 21. Acceptable arguments # Only for the FormMail script <Location /cgi-bin/FormMail> # Reject request where the value of parameter "recipient" # does not end with "@apache.org" SecFilterSelective ARG_recipient "![a-zA-Z0-9]+@apache.org$"> </Location> 21
    22. 22. Which PHP script is slagging my server? • Which process, and what is it doing? • Look at /server-status for a process list
    23. 23. Step 1: Look at top: • Run ‘top’ • Order by CPU usage • Pick off the httpd processes that are causing the problem and make note of their PIDs
    24. 24. Step 2: /server-status • Look at /server-status output • Look for the PIDs you noted • Move fast - the process may be gone already
    25. 25. /server-status
    26. 26. ExtendedStatus On • You’ll need “ExtendedStatus On” to get these details: <Location /server-status> SetHandler server-status </Location> ExtendedStatus On
    27. 27. SNI • General knowledge: SSL requires one certificate per IP address • That is, only one SSL site can be on each IP address • Limitation of SSL itself
    28. 28. SSL handshake
    29. 29. SSL handshake Certificate Hostname
    30. 30. Clearly this sucks
    31. 31. SNI
    32. 32. SNI • Server Name Indication • Passes server name in initial handshake • Simple solutions are always best
    33. 33. Caveats • You knew there would be a catch Mozilla Firefox 2.0 or later Opera 8.0 or later (the TLS 1.1 protocol must be enabled) Internet Explorer 7 (Vista or higher, not XP) or later Google Chrome (Vista or higher, not XP. OS X 10.5.7 or higher on Chrome 5.0.342.1 or newer) Safari Safari 3.2.1 and newer on Mac OS X 10.5.6 and Windows Vista or higher, not XP
    34. 34. Apache • 2.2.12 or later Listen 443 # Listen for virtual host requests on all IP addresses NameVirtualHost *:443 # Go ahead and accept connections for these vhosts # from non-SNI clients SSLStrictSNIVHostCheck off <VirtualHost *:443> DocumentRoot /www/example1 ServerName www.example.com </VirtualHost> <VirtualHost *:443> DocumentRoot /www/example2 ServerName www.example2.org </VirtualHost>
    35. 35. More Info http://wiki.apache.org/httpd/NameBasedSSLVHostsWithSNI
    36. 36. Secure mod_dav deployment • Security rule #1: Content is not writable • Corollary: Anyone telling you to ‘chmod 777’ is a monkey • Anyone telling you ‘chown apache something.php’ *might* be a monkey. Or they might be working around Apache’s annoying permissions model
    37. 37. However ... •Setting up WebDAV requires that content be writable ... •And owned by Apache •This is annoying
    38. 38. WebDAV <Directory /var/www/content> Dav On </Directory>
    39. 39. Why this is a problem • People write bad code • It’s easy to get PHP (or whatever else) to overwrite your content • Now your site is a radical terrorist site • This is a very unpleasant thing to wake up to on a Saturday morning
    40. 40. Secure DAV • It’s possible to set up Dav without having the files written by the Apache user • Sort of 40
    41. 41. Two Apache Processes Primary Secondary server, server, running as running as user www user dav File System, owned by dav 41
    42. 42. Two Apache Processes Read-only Running ordinary set of modules Running web apps, etc Primary server, running as user www File System, owned by dav 42
    43. 43. Two Apache Processes Read/Write Remove all extra modules SSL Secondary server, Authenticated running as user dav File System, owned by dav 43
    44. 44. Multi-server config. drwxr-xr-x 2 dav dav 68 Oct 3 12:41 /var/www 1 Listen *:80 User www Group www DocumentRoot /var/www/ Listen *:8080 2 DavLockDb /var/lock/dav User dav Group dav DocumentRoot /var/www/ 44
    45. 45. drwxr-xr-x 2 dav dav 68 Oct 3 12:41 /var/www 1 Listen *:80 Can write User www Group www DocumentRoot /var/www/ Listen *:8080 2 DavLockDb /var/lock/dav User dav Group dav DocumentRoot /var/www/ 45
    46. 46. drwxr-xr-x 2 dav dav 68 Oct 3 12:41 /var/www 1 Can’t Listen *:80 User www Group www DocumentRoot /var/www/ Listen *:8080 2 DavLockDb /var/lock/dav User dav Group dav DocumentRoot /var/www/ 46
    47. 47. <If> Picture by BrewBooks (Flickr) My favorite new feature in 2.4
    48. 48. <If> <If ‘$req{Host} = “www.example.com”’> RedirectMatch (.*) http://example.com/$1 </If>
    49. 49. <If> <If ‘$req{Host} = “www.example.com”’> RedirectMatch (.*) http://example.com/$1 </If> This was hard prior to 2.4, and probably required mod_rewrite, or a separate virtual host.
    50. 50. <If> <If ‘$req{Host} = “www.example.com”’> RedirectMatch (.*) http://example.com/$1 </If> $req $resp $env
    51. 51. Logging - Conditional Logging • Don’t log certain things • Per-directory logging 51
    52. 52. Conditional LogFormat • The LogFormat directive supports some conditionals in the variables • "%!200,304,302{Referer}i" logs Referer on all requests that do not return one of the three specified codes 52
    53. 53. Conditional LogFormat • "%400,501{User-agent}i" logs User-agent on 400 errors and 501 errors only • For other status codes, the literal string "-" will be logged 53
    54. 54. Conditional CustomLog • Stick Env=xyz on the end • or Env=!xyz • Yes, that’s =! not != 54
    55. 55. For example ... SetEnvIf Request_URI .gif$ gif-image CustomLog gif-requests.log common env=gif-image CustomLog nongif-requests.log common env=!gif-image 55
    56. 56. For example ... SetEnvIf Request_URI .gif$ gif-image CustomLog gif-requests.log common env=gif-image CustomLog nongif-requests.log common env=!gif-image 56
    57. 57. For example ... SetEnvIf Request_URI .gif$ gif-image CustomLog gif-requests.log common env=gif-image CustomLog nongif-requests.log common env=!gif-image 57
    58. 58. So the images get logged here SetEnvIf Request_URI .gif$ gif-image CustomLog gif-requests.log common env=gif-image CustomLog nongif-requests.log common env=!gif-image 58
    59. 59. And everything else goes here SetEnvIf Request_URI .gif$ gif-image CustomLog gif-requests.log common env=gif-image CustomLog nongif-requests.log common env=!gif-image 59
    60. 60. Per directory SetEnvIf Request_URI ^/marketing mkt CustomLog marketing.log common env=mkt 60
    61. 61. PCRE Zero-width assertions • Match everything except one thing • While you can do this with RewriteRule, it would be nice if you could do it with other directives DirectoryMatch FilesMatch RedirectMatch
    62. 62. Negative Lookahead • PCRE provides a regex syntax to say “not preceded by” or “not followed by” • Negative lookbehind and negative lookahead, respectively
    63. 63. Andrei Rocks • While we’re on the topic: • Pretty much the best presentation on regular expressions anywhere: • http://www.slideshare.net/ andreizm/andreis-regex-clinic
    64. 64. Everything except ... • “I want to redirect everything except / images” • This is where you’d use a negative lookahead • Necessary because RedirectMatch doesn’t support negation RedirectMatch ^/(?!images/)(.*) http://other.myhost.com/$1
    65. 65. Everything except ... • Match anything ... • RedirectMatch ^/(?!images/)(.*) http://other.myhost.com/$1
    66. 66. Everything except ... • Match anything ... • That doesn’t start with images/ RedirectMatch ^/(?!images/)(.*) http://other.myhost.com/$1
    67. 67. Everything except ... This is called a “zero width” assertion, because it doesn’t fill $1, and doesn’t consume any characters in the match. RedirectMatch ^/(?!images/)(.*) http://other.myhost.com/$1
    68. 68. What the heck is it doing? RewriteLog /var/log/rewrite.log RewriteLogLevel 9
    69. 69. What the heck is it doing? RewriteLog /var/log/rewrite.log RewriteLogLevel 9 • Alas, not in .htaccess • Logs are always opened at startup
    70. 70. What the heck is it doing? RewriteLog /var/log/rewrite.log RewriteLogLevel 9 • Most entries between 1-4
    71. 71. What the heck is it doing? RewriteLog /var/log/rewrite.log RewriteLogLevel 9 • If there’s nothing in there, your rules are being ignored.
    72. 72. Logging - RewriteLog RewriteLog /var/log/rewrite.log RewriteLogLevel 9 73
    73. 73. Logging - RewriteLog RewriteLog /var/log/rewrite.log RewriteLogLevel 9 74
    74. 74. Logging - RewriteLog RewriteLog /var/log/rewrite.log RewriteLogLevel 9 75
    75. 75. 76
    76. 76. Kind of intimidating, isn’t it? 77
    77. 77. Learn to ignore the irrelevant bits 78
    78. 78. Which bits are those? 121.14.76.185 - - [24/Sep/2008:21:35:51 --0400] [wooga.drbacchus.com/sid#b83444a8][rid#b85b8d00/ initial] (4) [perdir /var/www/vhosts/drbacchus/] RewriteCond: input='/var/www/vhosts/drbacchus/ podcasts/poetry/maninmoon.mp3' pattern='!-f' => not- matched 79
    79. 79. Client address 121.14.76.185 - - [24/Sep/2008:21:35:51 --0400] [wooga.drbacchus.com/sid#b83444a8][rid#b85b8d00/ initial] (4) [perdir /var/www/vhosts/drbacchus/] RewriteCond: input='/var/www/vhosts/drbacchus/ podcasts/poetry/maninmoon.mp3' pattern='!-f' => not- matched 80
    80. 80. Dunno what those are 121.14.76.185 - - [24/Sep/2008:21:35:51 --0400] [wooga.drbacchus.com/sid#b83444a8][rid#b85b8d00/ initial] (4) [perdir /var/www/vhosts/drbacchus/] RewriteCond: input='/var/www/vhosts/drbacchus/ podcasts/poetry/maninmoon.mp3' pattern='!-f' => not- matched 81
    81. 81. Time 121.14.76.185 - - [24/Sep/2008:21:35:51 --0400] [wooga.drbacchus.com/sid#b83444a8][rid#b85b8d00/ initial] (4) [perdir /var/www/vhosts/drbacchus/] RewriteCond: input='/var/www/vhosts/drbacchus/ podcasts/poetry/maninmoon.mp3' pattern='!-f' => not- matched 82
    82. 82. Unique id 121.14.76.185 - - [24/Sep/2008:21:35:51 --0400] [wooga.drbacchus.com/sid#b83444a8][rid#b85b8d00/ initial] (4) [perdir /var/www/vhosts/drbacchus/] RewriteCond: input='/var/www/vhosts/drbacchus/ podcasts/poetry/maninmoon.mp3' pattern='!-f' => not- matched 83
    83. 83. Request ID 121.14.76.185 - - [24/Sep/2008:21:35:51 --0400] [wooga.drbacchus.com/sid#b83444a8][rid#b85b8d00/ initial] (4) [perdir /var/www/vhosts/drbacchus/] RewriteCond: input='/var/www/vhosts/drbacchus/ podcasts/poetry/maninmoon.mp3' pattern='!-f' => not- matched 84
    84. 84. The useful bit 121.14.76.185 - - [24/Sep/2008:21:35:51 --0400] [wooga.drbacchus.com/sid#b83444a8][rid#b85b8d00/ initial] (4) [perdir /var/www/vhosts/drbacchus/] RewriteCond: input='/var/www/vhosts/drbacchus/ podcasts/poetry/maninmoon.mp3' pattern='!-f' => not- matched 85
    85. 85. Wouldn’t it be nice if you could just see the useful part? 86
    86. 86. You could change the way mod_rewrite logs:
    87. 87. Piped logs • I actually use a piped lot handler to remove this superfluous stuff • Like so ... RewriteLog |/usr/local/bin/rewrite_log_pipe RewriteLogLevel 9 88
    88. 88. RewriteLog |/usr/local/bin/rewrite_log_pipe RewriteLogLevel 9 with ... #!/usr/bin/perl $|++; open (F, ">>/tmp/rewrite"); select F; while (<>) { s/^.*((d).*)/$1/; print; } 89
    89. 89. RewriteLog |/usr/local/bin/rewrite_log_pipe RewriteLogLevel 9 This bit says “instead of logging to a text file, invoke this script and send the log entries there.” 90
    90. 90. #!/usr/bin/perl $|++; open (F, ">>/tmp/rewrite"); select F; while (<>) { s/^.*((d).*)/$1/; print; } • Look for the (1) or (2) bit • drop everything before that 91
    91. 91. Results in: (4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus .com' [NC] => not-matched (3) applying pattern 'wp-rss2.php' to uri '/index.php' (3) applying pattern '(journal/)?index.rdf' to uri '/index.php' (3) applying pattern '^/wordpress/wp-comments' to uri '/index.php' (3) applying pattern '^/perm/(.*)' to uri '/index.php' (3) applying pattern '^/articles?/(.*)' to uri '/index.php' (3) applying pattern '^/blog/(.*)' to uri '/index.php' (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/index.php' (3) applying pattern '^/book/cookbook' to uri '/index.php' (3) applying pattern '^/book/2.2' to uri '/index.php' (3) applying pattern '^/booklink/(.*)' to uri '/index.php' (3) applying pattern '^/books?/(.+)' to uri '/index.php' (1) pass through /index.php 92
    92. 92. Results in: (4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus .com' [NC] => not-matched (3) applying pattern 'wp-rss2.php' to uri '/index.php' (3) applying pattern '(journal/)?index.rdf' to uri '/index.php' (3) applying pattern '^/wordpress/wp-comments' to uri '/index.php' (3) applying pattern '^/perm/(.*)' to uri '/index.php' (3) applying pattern '^/articles?/(.*)' to uri '/index.php' (3) applying pattern '^/blog/(.*)' to uri '/index.php' better? See? Isn’t that (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/index.php' (3) applying pattern '^/book/cookbook' to uri '/index.php' (3) applying pattern '^/book/2.2' to uri '/index.php' (3) applying pattern '^/booklink/(.*)' to uri '/index.php' (3) applying pattern '^/books?/(.+)' to uri '/index.php' (1) pass through /index.php 93
    93. 93. Requested URI (4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus .com' [NC] => not-matched (3) applying pattern 'wp-rss2.php' to uri '/index.php' (3) applying pattern '(journal/)?index.rdf' to uri '/index.php' (3) applying pattern '^/wordpress/wp-comments' to uri '/index.php' (3) applying pattern '^/perm/(.*)' to uri '/index.php' (3) applying pattern '^/articles?/(.*)' to uri '/index.php' (3) applying pattern '^/blog/(.*)' to uri '/index.php' (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/index.php' (3) applying pattern '^/book/cookbook' to uri '/index.php' (3) applying pattern '^/book/2.2' to uri '/index.php' (3) applying pattern '^/booklink/(.*)' to uri '/index.php' (3) applying pattern '^/books?/(.+)' to uri '/index.php' (1) pass through /index.php 94
    94. 94. Patterns applied (4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus .com' [NC] => not-matched (3) applying pattern 'wp-rss2.php' to uri '/index.php' (3) applying pattern '(journal/)?index.rdf' to uri '/index.php' (3) applying pattern '^/wordpress/wp-comments' to uri '/index.php' (3) applying pattern '^/perm/(.*)' to uri '/index.php' (3) applying pattern '^/articles?/(.*)' to uri '/index.php' (3) applying pattern '^/blog/(.*)' to uri '/index.php' (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/index.php' (3) applying pattern '^/book/cookbook' to uri '/index.php' (3) applying pattern '^/book/2.2' to uri '/index.php' (3) applying pattern '^/booklink/(.*)' to uri '/index.php' (3) applying pattern '^/books?/(.+)' to uri '/index.php' (1) pass through /index.php 95
    95. 95. None of them matched (4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus .com' [NC] => not-matched (3) applying pattern 'wp-rss2.php' to uri '/index.php' (3) applying pattern '(journal/)?index.rdf' to uri '/index.php' (3) applying pattern '^/wordpress/wp-comments' to uri '/index.php' (3) applying pattern '^/perm/(.*)' to uri '/index.php' (3) applying pattern '^/articles?/(.*)' to uri '/index.php' (3) applying pattern '^/blog/(.*)' to uri '/index.php' (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/index.php' (3) applying pattern '^/book/cookbook' to uri '/index.php' (3) applying pattern '^/book/2.2' to uri '/index.php' (3) applying pattern '^/booklink/(.*)' to uri '/index.php' (3) applying pattern '^/books?/(.+)' to uri '/index.php' (1) pass through /index.php 96
    96. 96. And now • We can actually make some sense of what’s happening • Less inscrutable noise • Yes, it means something, but not to normal people 97
    97. 97. Examples (4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus .com' [NC] => not-matched • This was the result of RewriteCond %{HTTP_HOST} !^wooga.drbacchus.com [NC] 98
    98. 98. Examples (4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus .com' [NC] => not-matched • It shows what the input variable looked like RewriteCond %{HTTP_HOST} !^wooga.drbacchus.com [NC] 99
    99. 99. Examples (4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus .com' [NC] => not-matched • And what pattern was applied RewriteCond %{HTTP_HOST} !^wooga.drbacchus.com [NC] 100
    100. 100. Examples (4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus .com' [NC] => not-matched • As well as what happened RewriteCond %{HTTP_HOST} !^wooga.drbacchus.com [NC] 101
    101. 101. Another example (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/ index.php' • Was a result of RewriteRule ^/book/(mod)?_?rewrite http://www.amazon.com/exec/obidos/asin/ 1590595610/drbacchus/ [R,L] 102
    102. 102. Again ... (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/ index.php' • What was requested RewriteRule ^/book/(mod)?_?rewrite http://www.amazon.com/exec/obidos/asin/ 1590595610/drbacchus/ [R,L] 103
    103. 103. And ... (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/ index.php' • What it was compared against RewriteRule ^/book/(mod)?_?rewrite http://www.amazon.com/exec/obidos/asin/ 1590595610/drbacchus/ [R,L] 104
    104. 104. Matched? (3) applying pattern '^/book/(mod)?_?rewrite' to uri '/ index.php' • If it matched, the next line will be the action log RewriteRule ^/book/(mod)?_?rewrite http://www.amazon.com/exec/obidos/asin/ 1590595610/drbacchus/ [R,L] 105
    105. 105. The whole thing (3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite' (2) rewrite '/books/rewrite' -> 'http://www.amazon.com/exec/obidos/ asin/1590595610/drbacchus/' (2) explicitly forcing redirect with http://www.amazon.com/exec/ obidos/asin/1590595610/drbacchus/ (1) escaping http://www.amazon.com/exec/obidos/asin/1590595610/ drbacchus/ for redirect (1) redirect to http://www.amazon.com/exec/obidos/asin/1590595610/ drbacchus/ [REDIRECT/302] 106
    106. 106. The match: (3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite' (2) rewrite '/books/rewrite' -> 'http://www.amazon.com/exec/obidos/ asin/1590595610/drbacchus/' (2) explicitly forcing redirect with http://www.amazon.com/exec/ obidos/asin/1590595610/drbacchus/ (1) escaping http://www.amazon.com/exec/obidos/asin/1590595610/ drbacchus/ for redirect (1) redirect to http://www.amazon.com/exec/obidos/asin/1590595610/ drbacchus/ [REDIRECT/302] 107
    107. 107. Followed by (3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite' (2) rewrite '/books/rewrite' -> 'http://www.amazon.com/exec/obidos/ asin/1590595610/drbacchus/' (2) explicitly forcing redirect with http://www.amazon.com/exec/ obidos/asin/1590595610/drbacchus/ (1) escaping http://www.amazon.com/exec/obidos/asin/1590595610/ drbacchus/ for redirect (1) redirect to http://www.amazon.com/exec/obidos/asin/1590595610/ drbacchus/ [REDIRECT/302] 108
    108. 108. [R] (3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite' (2) rewrite '/books/rewrite' -> 'http://www.amazon.com/exec/obidos/ asin/1590595610/drbacchus/' (2) explicitly forcing redirect with http://www.amazon.com/exec/ obidos/asin/1590595610/drbacchus/ (1) escaping http://www.amazon.com/exec/obidos/asin/1590595610/ drbacchus/ for redirect (1) redirect to http://www.amazon.com/exec/obidos/asin/1590595610/ drbacchus/ [REDIRECT/302] 109
    109. 109. But it all runs together! • Look for: • (2) init rewrite engine with requested uri /atom/1 • ‘init rewrite engine’ shows where a new request started being rewritten 110
    110. 110. Load balancing
    111. 111. Fortunately ... Photo CC by Camo53 (flickr) • mod_proxy_balancer • Added in 2.1
    112. 112. Fairly simple to configure <Proxy balancer://mycluster> BalancerMember http://192.168.1.50:80 BalancerMember http://192.168.1.51:80 </Proxy> ProxyPass /test balancer://mycluster
    113. 113. Fairly simple to configure <Proxy balancer://mycluster> BalancerMember http://192.168.1.50:80 BalancerMember http://192.168.1.51:80 </Proxy> ProxyPass /test balancer://mycluster
    114. 114. Fairly simple to configure <Proxy balancer://mycluster> BalancerMember http://192.168.1.50:80 BalancerMember http://192.168.1.51:80 </Proxy> ProxyPass /test balancer://mycluster
    115. 115. Sticky Sessions Ensures that connections go to the same server they started with. ProxyPass / balancer://mycluster/ stickysession=PHPSESSIONID 118
    116. 116. Balancing measures ProxyPass / balancer://hotcluster/ <Proxy balancer://hotcluster> BalancerMember http://1.2.3.4:8009 loadfactor=1 BalancerMember http://1.2.3.5:8009 loadfactor=2 # The below is the hot standby BalancerMember http://1.2.3.6:8009 status=+H ProxySet lbmethod=bytraffic </Proxy> 119
    117. 117. 1.2.3.5 gets twice the traffic ProxyPass / balancer://hotcluster/ <Proxy balancer://hotcluster> BalancerMember http://1.2.3.4:8009 loadfactor=1 BalancerMember http://1.2.3.5:8009 loadfactor=2 # The below is the hot standby BalancerMember http://1.2.3.6:8009 status=+H ProxySet lbmethod=bytraffic </Proxy> 120
    118. 118. Hot spare ProxyPass / balancer://hotcluster/ <Proxy balancer://hotcluster> BalancerMember http://1.2.3.4:8009 loadfactor=1 BalancerMember http://1.2.3.5:8009 loadfactor=2 # The below is the hot standby BalancerMember http://1.2.3.6:8009 status=+H ProxySet lbmethod=bytraffic </Proxy> 121
    119. 119. bytraffic or byrequests ProxyPass / balancer://hotcluster/ <Proxy balancer://hotcluster> BalancerMember http://1.2.3.4:8009 loadfactor=1 BalancerMember http://1.2.3.5:8009 loadfactor=2 # The below is the hot standby BalancerMember http://1.2.3.6:8009 status=+H ProxySet lbmethod=bytraffic </Proxy> 122
    120. 120. BalancerManager <Location /balancer-manager> SetHandler balancer-manager Order Deny,Allow Deny from all Allow from .example.com </Location> 123
    121. 121. 124
    122. 122. Disable a particular host 125
    123. 123. Notes • The things on the other end don’t have to be Apache • This is a popular way to set up Ruby on Rails, with Mongrel 126
    124. 124. Bandwidth limiting Picture by Joachim S. Müller (Flickr) People always want their websites to run slower. Seems odd to me ...
    125. 125. In 2.4 ... • 2.4 adds two new modules for this purpose • mod_dialup • mod_ratelimit
    126. 126. mod_dialup Party like it’s 1999
    127. 127. v.92? Really? <Location /mysite> ModemStandard V.92 </Location> Also available: V.21 V.26bis V.32 V.92
    128. 128. mod_ratelimit <Location /downloads> SetHandler RATE_LIMIT SetEnv rate-limit 400 </Location> Speed is in kb/s
    129. 129. Prior to 2.4 A variety of other modules do bandwidth kind of things: mod_cband mod_bwshare mod_bw mod_evasive mod_limitipconn
    130. 130. Logging • mod_logio • mod_log_forensic
    131. 131. Obligatory ridiculous log photo Photo by Zevotron (Flickr)
    132. 132. Not enough • Your log files don’t tell you enough • Want more
    133. 133. Logging - mod_logio Complete INPUT and OUTPUT size 136
    134. 134. Logging - mod_logio Complete INPUT and OUTPUT size 137
    135. 135. Combined Log Format - Bytes transferred 138
    136. 136. Less than half the story • Only bytes transferred to the client • Doesn’t include headers • Doesn’t include data sent from the client to the server 139
    137. 137. mod_logio •Adds two additional variables •%I - Input bytes •%O - Output bytes •Includes headers, both directions 140
    138. 138. LogFormat 141
    139. 139. mod_log_forensic • Did it ever finish?
    140. 140. ForensicLog ForensicLog /var/log/httpd/forensic.log
    141. 141. ForensicLog ForensicLog /var/log/httpd/forensic.log • Post-process with the check-forensic script to tell you which URLs never exited
    142. 142. mod_speling CheckSpelling On CheckCaseOnly On
    143. 143. Image Theft SetEnvIf Referer ".example.com/" local_referal # Allow browsers that do not send Referer info SetEnvIf Referer "^$" local_referal <Directory /web/images> Order Deny,Allow Deny from all Allow from env=local_referal </Directory>
    144. 144. Or ... RewriteEngine on RewriteCond %{HTTP_REFERER} !="" RewriteCond %{HTTP_REFERER} !example.com [NC] RewriteRule .(jpe?g|gif|png)$ - [F,NC]
    145. 145. Or ... RewriteEngine on RewriteCond %{HTTP_REFERER} !="" RewriteCond %{HTTP_REFERER} !example.com [NC] # depending upon in which context # you use the RewriteRule, # you might need a condition to # exclude the go_away.png to prevent # an internal redirect looping. We don't use a RegEx here: RewriteCond %{REQUEST_URI} !=/images/go_away.png RewriteRule .(jpe?g|gif|png)$ /images/go_away.png [NC,L]
    146. 146. Or ... RewriteEngine on RewriteCond %{HTTP_REFERER} !="" RewriteCond %{HTTP_REFERER} !example.com [NC] # depending upon in which context # you use the RewriteRule, # you might need a condition to # exclude the go_away.png to prevent # an internal redirect looping. We don't use a RegEx here: RewriteCond %{REQUEST_URI} !=/images/go_away.png RewriteRule .(jpe?g|gif|png)$ http://other.example.com/images/go_away.png [R,NC,L]
    147. 147. Query Strings and Path Info
    148. 148. Query Strings monkeys.php?q=lemur&color=green&level=99.7&doc=info.txt • Query Strings are: • Ugly • Hard to type • Hard to remember • Potentially insecure
    149. 149. Query Strings monkeys.php?q=lemur&color=green&level=99.7&doc=info.txt • Query Strings are: • Ugly • Hard to type • Hard to remember • Potentially insecure
    150. 150. Path Info monkeys.php?q=lemur&color=green&level=99.7&doc=info.txt /monkeys/lemur/green/99.7/info
    151. 151. mod_rewrite monkeys.php?q=lemur&color=green&level=99.7&doc=info.txt /monkeys/lemur/green/99.7/info RewriteRule ^/monkeys/(.+)/(.+)/(.+)/(.+) /monkeys.php?q=$1&color=$2&level=$3&doc=$4.txt [PT,L,NE]
    152. 152. Engineering for failure • Why not do it right to begin with, and avoid the mess? • PHP makes this fairly easy
    153. 153. Step One: SetHandler /monkeys/lemur/green/99.7/info • We want monkeys to be a PHP script • We rename monkeys.php to monkeys, and then ... <Files monkeys> SetHandler application/x-httpd-php </Files> This goes in your server config, or in .htaccess
    154. 154. Step Two: explode() /monkeys/lemur/green/99.7/info • The rest of the solution is in your PHP: $args = explode( $_SERVER[‘PATH_INFO’] ); $type = $args[0]; $color = $args[1]; ...
    155. 155. While we’re at it • File extensions are *so* 1980s • All your files are php files, right? • Why give them a .php extension? RewriteCond %{REQUEST_URI} !. RewriteRule ^ - [H=application/x-httpd-php,PT]
    156. 156. Write a better FM • I’ll never get this far in the presentation, right?
    1. A particular slide catching your eye?

      Clipping is a handy way to collect important slides you want to go back to later.

    ×