This makes a great interview question. :-)
The short answer is that it depends on how you set up your webserver. Different languages have different defaults, and of course you can override the defaults and set them up entirely differently.
A typical PHP installation runs as an Apache module. In this setup, the Apache server listens for HTTP requests and looks at their request path and virtual host. When it finds one that matches a PHP rule (defined in your Apache configuration), it starts the PHP interpreter, setting variables like $_GET and $_POST from the request data. On very old PHP installations, it then uses the path to locate the PHP script, parses it, and executes it. In newer (post-~2005) installations, the server caches the compiled PHP script in memory and executes it again on subsequent requests, without having to hit disk to read the file contents.
A Rails or Django deployment circa 2007 would use Nginx or Lighttpd as the webserver, and then communicate with a separate application server over FastCGI or SCGI. The latter are simple binary protocols that are designed solely to communicate between webserver and appserver; they basically include the information in the HTTP request, but in a parsed, compact format that's fast to decode. The application server would then decode the request and pass it to a web framework to execute, returning an HTML response that is forwarded on by the webserver.
Why split the webserver from the appserver? Because running your application's code is typically slow and memory-intensive; it's usually CPU-constrained. Serving static files and parsing HTTP, meanwhile, is typically fast, cheap, and bandwidth constrained. If you connect the app server directly to the Internet, your memory-hogging application will sit idle much of the time while pushing bytes out to a browser on dialup or a cell network. Splitting the servers lets you scale them independently; typically, you need just a few frontend load balancers to serve many app servers. It also gives you fault tolerance, since if an app server crashes, the load balancer can retry the request with a different one.
WSGI and Rack are HTTP interface specifications for Python and Ruby, respectively. Basically, all your Python/Ruby code needs to run inside a server somewhere, which talks some network protocol. A number of different web frameworks have cropped up to make programming webapps easier - things like Django, Pylons, and Flask for Python and Rails or Merb in Ruby - and these frameworks typically optimize for ease of programming. Similarly, a number of different appservers have cropped up - gunicorn and uwsgi for Python, unicorn and Mongrel and Thin for Ruby, Phusion Passenger for both - and these typically optimize for speed. A common gateway interface lets you mix and match between them. The reason why you need a different one for each language is that the app server and the webapp framework are typically hosted in the same process, communicating in-memory, and different languages have different memory representations. You don't always, however - uWSGI, for example, is written in C and can be used with Python, Perl, or Ruby.
Around 2010, people realized that HTTP was a perfectly valid transport protocol, and started to use it in place of FastCGI and SCGI. Now virtually all deployments use nginx talking http to one or more appservers that run the actual webapp code.
So to a first approximation, when your HTTP request hits a server, it makes a TCP connection to port 80 on an nginx instance somewhere. nginx parses the request (which looks something like "GET /myapp/index.php HTTP/1.1\n\n...headers..." - HTTP is just a text protocol), looks in its configuration file, and matches /myapp/*.php against the rule for some app (pretend it's actually a Python/Django app running on the same physical server for illustration). It then makes an HTTP request to localhost:3000 on the server to talk to the app server. On port 3000, you have uWSGI running, which again parses the request, populates a Python dictionary, and invokes the callable given in the uwsgi config file. That callable will typically be Django's entry point, where Django consults its root urlconf and routes the request to your application code.