Custom 404 Error Pages with Sinatra.rb
In the process of porting my blog to Octopress/Jekyll, I wanted to implement a custom 404 error page. Blogs around the internet suggested that I drop a 404.html into my source directory.
Here’s what I use:
---
layout: page
title: Error 404
footer: false
menu: false
---
<p>The page you have requested cannot be found. Why not read some of my latest entries instead?</p>
<div id="blog-archives" class="missing">
<article>
<span class="post-meta">Feb 10, 2021</span>
<h2>
<a class="post-link" href="/blog/2021/02/10/ipv6-docker-docker-compose-and-shorewall6-ip6tables/">IPv6, Docker(-compose), and Shorewall6/ip6tables</a>
</h2>
</article>
<article>
<span class="post-meta">Dec 27, 2015</span>
<h2>
<a class="post-link" href="/blog/2015/12/27/jekyll-3-post-excerpts/">Jekyll 3 Post Excerpts</a>
</h2>
</article>
<article>
<span class="post-meta">Dec 24, 2015</span>
<h2>
<a class="post-link" href="/blog/2015/12/24/why-i-gym-religiously/">Why I Gym Religiously</a>
</h2>
</article>
<article>
<span class="post-meta">Dec 23, 2015</span>
<h2>
<a class="post-link" href="/blog/2015/12/23/s3-custom-redirection-rules/">S3 Custom Redirection Rules</a>
</h2>
</article>
<article>
<span class="post-meta">Dec 9, 2015</span>
<h2>
<a class="post-link" href="/blog/2015/12/09/getting-f-lux-onto-your-ios-device-with-xcode/">Getting f.lux onto your iOS Device with Xcode</a>
</h2>
</article>
<article>
<span class="post-meta">Sep 10, 2015</span>
<h2>
<a class="post-link" href="/blog/2015/09/10/hey-siri-give-us-a-hint/">Hey Siri, Give Us a Hint</a>
</h2>
</article>
<article>
<span class="post-meta">Jun 28, 2015</span>
<h2>
<a class="post-link" href="/blog/2015/06/28/investment-portfolio-tracking/">Investment Portfolio Tracking</a>
</h2>
</article>
<article>
<span class="post-meta">Jun 28, 2015</span>
<h2>
<a class="post-link" href="/blog/2015/06/28/love-wins/">Love Wins</a>
</h2>
</article>
<article>
<span class="post-meta">Jun 11, 2015</span>
<h2>
<a class="post-link" href="/blog/2015/06/11/the-jekyll-reboot/">The Jekyll Reboot</a>
</h2>
</article>
<article>
<span class="post-meta">Sep 17, 2013</span>
<h2>
<a class="post-link" href="/blog/2013/09/17/forward-thinking/">Forward-Thinking</a>
</h2>
</article>
</div>
However, I noticed another problem. Sinatra was not returning HTTP status error 404 for this. A regular status 200 (OK) was sent, leading Google to start indexing these error pages.
But first, let me show you the default config.ru
Rack-adaptor that ships with Octopress:
require 'bundler/setup'
require 'sinatra/base'
# The project root directory
$root = ::File.dirname(__FILE__)
class SinatraStaticServer < Sinatra::Base
get(/.+/) do
send_sinatra_file(request.path) {404}
end
not_found do
send_sinatra_file('404.html') {"Sorry, I cannot find #{request.path}"}
end
def send_sinatra_file(path, &missing_file_block)
file_path = File.join(File.dirname(__FILE__), 'public', path)
file_path = File.join(file_path, 'index.html') unless file_path =~ /\.[a-z]+$/i
File.exist?(file_path) ? send_file(file_path) : missing_file_block.call
end
end
run SinatraStaticServer
To ensure that the 404.html is served with a status 404, I had to add a :status
option to send_file
. Here’s how my file now looks like:
require 'bundler/setup'
require 'sinatra/base'
# The project root directory
$root = ::File.dirname(__FILE__)
class SinatraStaticServer < Sinatra::Base
configure do
enable :static_cache_control
end
get(/.+/) do
send_sinatra_file(request.path)
end
not_found do
send_file(File.join(File.dirname(__FILE__), 'public', '404.html'), {:status => 404})
end
def send_sinatra_file(path)
file_path = File.join(File.dirname(__FILE__), 'public', path)
file_path = File.join(file_path, 'index.html') unless file_path =~ /\.[a-z]+$/i and !File.directory?(file_path)
File.exist?(file_path) ? send_file(file_path) : not_found
end
end
run SinatraStaticServer
I’ve rewritten the not_found
method and also added another condition !File.directory?(file_path)
within the send_sinatra_file
method. This is to teach some web servers to serve the index.html
when the path is a directory.
However, that’s not quite it. Support for sending status codes within send_file
was only recently added and is only available in version 1.4.0 of Sinatra.
Not to worry, we will make use of trusty Bundler to help us fetch the latest version of Sinatra from source. Update the following in your Gemfile
:
gem 'sinatra', "~> 1.4.0"
While at it, the gems defined in the Gemfile are ancient. I’ve cleaned up my Gemfile and here’s how it looks like:
source "https://rubygems.org"
group :development do
gem 'rake'
gem 'rack'
gem 'jekyll'
gem 'rdiscount'
gem 'pygments.rb'
gem 'RedCloth'
gem 'haml', '>= 3.1'
gem 'compass', '>= 0.11'
gem 'rubypants'
gem 'rb-fsevent'
gem 'stringex'
gem 'liquid'
end
gem 'sinatra', "~> 1.4.0"
gem 'thin'
Once you have updated the Gemfile, run the following commands:
bundle update
bundle install
With that, Octopress (and its Rack-compatible web server) will now serve the custom error 404 file not found page with the correct HTTP status code.