Domain Mapping

Sun May 11 22:08:00 UTC 2008

I built MadPolitic to be an account-per-subdomain site. So when a user signs up for an account, they can choose a site name that gets prepended to the domain, e.g. mysite.madpolitic.com. Frequently, MadPolitic’s users will have registered their own domain name and want to map it to their MadPolitic site. MadPolitic is a publishing system for people or groups with a cause, so they might have domains like savethefarmersmarket.com or stopeatingthewhales.com. How do you map a domain name to a specific user site in your Rails app? Here’s how I did it.

Part I: Some Assembly Required

First of all, it’s worth noting that there is no way to fully automate this process. The user has to purchase their domain outside of your application through the registrar of their choice. They also have to use that registrar’s administration console to make some DNS configuration changes. Since I can’t do any of those things for them, I instead provide my users with detailed instructions on making the DNS changes for each of the more popular registrars. This is a bit of a hassle since these sites are beyond my control and can and do frequently tweak their menus or UI enough to render my instructions incorrect.

Buy a Domain

Let’s say we have a photosensitive user named Linda who is outraged at the city for installing street lights in her neighborhood. She created her community activism site at lindasmad.madpolitic.com but would rather people could find her at a more user-friendly URL like www.darkenmystreet.com.

Linda is also a big Danica Patrick fan, so she heads to GoDaddy and registers her new domain.

Configure DNS: the A record

To map darkenmystreet.com and www.darkenmystreet.com to her MadPolitic site, Linda has to make changes to some DNS records on her registrar’s name servers. A newly registered domain is typically set to use a pair of name servers under the control of the registrar. Most of the more popular registrars offer some version of a DNS control panel that lets users modify the DNS settings for their domain on those name servers. In the GoDaddy administration console, for instance, that feature is called Total DNS Control and MX Records.

First, she has to edit what is called an A record, also called an address record. The sole purpose of an address record is to map a domain name to an IP address. This is the function that most of us think of when we think of DNS servers. They translate domain names to IP addresses. When you register a new domain, an A record is typically created that points your domain to one of the registrars IP addresses.

Linda edits the A record and sets ‘Host’ to ‘darkenmystreet.com’ and ‘Points to’ to ‘72.249.74.216’, which is the MadPolitic IP address. In most DNS servers there is a shorthand for referring to the current domain: the ‘at’ sign (@). So for ‘Host’, Linda could also enter @.

Configure DNS: the CNAME record

Linda has one more change to make to the DNS records. She also wants to be sure that someone entering www.darkenmystreet.com winds up at her MadPolitic site. To do this, she needs to create a CNAME record, also called an alias. This record has two primary pieces of information: the alias name and the host it points to. Linda enters www for the alias name and @ for the ‘points to’ field. Sometimes this record is automatically created by the registrar and so nothing needs to be done.

Once all the DNS information for her new domain propagates out through the Internet, both darkenmystreet.com and www.darkenmystreet.com will resolve to the MadPolitic IP address.

Tell MadPolitic about the new domain

The final step for Linda is to log into her MadPolitic account and register her new domain name with my Rails app. I’ll use this to map requests coming from the darkenmystreet.com host to Linda’s MadPolitic site.

Part II: About That Rails App

That takes care of the first half of the equation: requests for the mapped domain are getting routed to my server. At this point, everything else is under my control. I have an apache virtual host set up to catch requests for port 80 from any host and proxy them to my Rails app. In the app, I have a before_filter in ApplicationController called set_site with this logic

...
if request.host.ends_with? 'madpolitic.com'
  # the request is for a site on the madpolitic domain with no domain mapping
  sitename = request.subdomains(tld_length = 1)[0]
  session[:site] = Site.find_by_sitename(sitename).id
else
  # the host is not *.madpolitic.com so see if the host maps to a madpolitic site
  # I actually cache a mapping of mapped_domain => sitename so I don' t hit the db each time; removed for this example
  session[:site] = Site.find_by_mapped_domain(request.host)
end
if !session[:site]
  logger.error "[#{Time.now.utc.strftime('%m-%d-%Y %H%:%M:%S')}]: Failed to locate site based on host of #{request.host}."
  redirect_to 'http://www.madpolitic.com/500.html', :status => 500
  return false
end
...

This method checks request.host to see if it ends in .madpolitic.com and handles it like a typical subdomain-based request and serves up the correct site accordingly. If the host does not end in .madpolitic.com then it might be a mapped domain and I try pulling a site from the database using the mapped domain.

Since Linda registered her mapped domain with my Rails app, the find is successful. If the find fails, then it’s possible someone followed all the directions, but failed to go to the MadPolitic administration console and enter the mapped domain. When I don’t find a site, I can serve an oops page and offer some helpful troubleshooting suggestions.

Epilogue: Follow That Request

Now let’s meet Robert. He lives on Linda’s street and has been infatuated with her for months. When he heard about her campaign to have the street lights removed, he became an impassioned supporter of her cause. He gets the URL from a flyer in the local coffee shop and logs on to sign the petition.

When Robert enters http://www.darkenmystreet.com into his browser and hits enter, here’s what happens (assuming he’s never hit that domain before).
  1. His browser sends a request out to one of the DNS servers of Robert’s ISP.
  2. That server (called the Resolver in this scheme) has never had a request for www.darkenmystreet.com before, so it doesn’t know the IP. It passes the request on to one of the 13 root name servers.
  3. That server replies to the Resolver saying “I don’t know about that domain, but this root name server should.”
  4. The Resolver then contacts that root name server with the request. That root server has a record mapping the domain to a name server and returns the address of that name server to the Resolver.
  5. Now the Resolver contacts that name server. This is the name server run by GoDaddy that Linda configured when she set the A record and CNAME record.
  6. Those record entries translate www.darkenmystreet.com to the MadPolitic IP address and return it to the Resolver.
  7. The Resolver caches the information for future requests and returns the IP to the browser.
  8. The browser sends its request directly to the MadPolitic IP.
  9. My Apache server is listening on that IP on port 80, and intercepts the request. The host in the request header matches up with a virtual server I have configured and Apache proxies the request on to Mongrel and my Rails app.
  10. Rails serves up the page Robert was looking for.
  11. Robert comments on one of Linda’s blog postings with an awkwardly-worded poem wherein he likens his love for Linda to a streetlight.

Tags: domain mapping madpolitic subdomains

Comments