A redirect checker is a tool that accepts a URL, makes a request to it and then follows any potential redirects returned from the server, revealing the final location that will load if you put it in your browser. For example, you can use a tool like Bitly to shorten URLs and make them easier to share and publish. A URL that looks like this https://techandlimon.com/blog/building-a-redirect-checker can turn into this http://bit.ly/43TNxmW. Great! It's easier share that short URL or publish it on your social media.
But, what happens if http://bit.ly/43TNxmW were to send you to a sketchy place? For example, a paypal look-alike phishing site? Or even a well known *hub site that you probably shouldn't be browsing at work? Well, it's possible. You have no idea where that short URL is going to take you.
Enter the Redirect Checker tool. This tool helps alleviate some of that worry by running the redirect on a server somewhere (not your browser), and then revealing the details of the redirect to you, along with where you'll ultimately end up. Now you can decide if you really want to load URL at work.
There are already a ton of redirect checker tools out there, likehttps://www.redirect-checker.org, and https://wheregoes.com just to name a few. They work well, and we actually used to use them all the time. But building our own was an opportunity we didn't want to miss.
To focus on the redirect checker, we're going to gloss over some details about how servers and redirects works. That's a topic for another blog post (I'll make sure to add a link once that one is published). For now, we'll assume an understanding of how URLs work, what an HTTP Response Code is, and the differences between a server redirect and a Javascript redirect. This redirect checker is going to focus on server redirects. Javascript redirects are a whole different beast (I sense another blog post opportunity here).
Since we are going to focus on server redirects, we are going to build our solution on a backend server stack. I've chosen Rails as my framework. I know Rails is not the most popular framework right now, but I like it for it's ease of use, and the speed at which you can get an application up and running. The process of building this on a different back end language is going to be fairly similar.
Now, our server side code is going to be in Rails, but that doesn't mean our front end has to be there too. One of the critizisms of Rails is the front end speed. Althought it is faster now than it used to be, it's still a little slow. It also doesn't play nice with React by default, so I don't use the Rails front end much. We're going to build our front end in React and use Next to generate a static page. React has become the standard for building dynamic webpages, and Next is a framework that makes it really easy to build and publish a performant React website.
First we are going to create a service in our existing Rails application to handle fetching a URL, checking the response code and then making additional requests if the response is a redirect.
Lets create a file at /lib/services/RedirectChecker.rb
with the following
require 'net/http'
module Services
class RedirectChecker
...
end
end
Then, lets add an initilize method that accepts a URL string, creates an empty array (list) variable and calls a build_response
method with the URL.
def initialize(url)
@redirects = []
build_response url
end
Now, lets write the build_response
method. This is where the bulk of the work is done. First, we create a URI object with the URL that is accepted by our service. This is important, because a URI object is smart, it knows what our protocal is, the host we are going to request and the path. We are going to use the Net::HTTP
object to make our network request and get a result. Finally, we'll get the time before and after our network call, so we can time how long the request took. At this point, our build_response
method looks like this:
def build_response(url)
uri = URI url
start_time = Time.now
result = Net::HTTP.get_response uri
end_time = Time.now
request_time = (end_time - start_time) * 1000.0
....
end
Ok, we have a response from our first call, now what? Well, we need to check the code of the response in order to determine what to do. Remember, if it's a successful or error response we are done, but, if it's a redirect response we have more work to do. So we'll add an if/else statement that checks the response code. In each condition we'll add the URL, code and request time to our list of URLs. But, if we notice a redirect, we're going to do something else. It looks something like this:
if result.code == '200'
@redirects.append({ url:, code: result.code, request_time: })
puts 'Successful check'
@redirects
elsif result.code == '301' || result.code == '302' && result['Location']
@redirects.append({ url:, code: result.code, request_time: })
puts 'Redirecting to ' + result['Location']
build_response(result['Location'])
else
@redirects.append({ url:, code: result.code, request_time: })
puts 'Some other response'
@redirects
end
Now, all of those look fairly similar, but pay close attention to the code in the middle block. If we see a response code of 301
or 302
AND we also have a 'Location'
header, we call our build_response
method again, this time with the URL in the Location header. This is a handy coding method called "recursion". It allows us to call a function or method from within itself, repeatedly, until a particular condition is met. This is what enables our method to continously make networks request that keep redirecting, until it finally gets a response that is not a 301 or 302. Recursion is cool!
Finally, we'll add a rescue
block at the end of our method to catch any errors, in case our service gets called with an invalid URL, or if there is some other unexpected issue. Our final service will look like this:
require 'net/http'
module Services
class RedirectChecker
def initialize(url)
@redirects = []
build_response(url)
end
def build_response(url)
uri = URI url
start_time = Time.now
result = Net::HTTP.get_response uri
end_time = Time.now
request_time = (end_time - start_time) * 1000.0
if result.code == '200'
@redirects.append({ url:, code: result.code, request_time: })
puts 'Successful check'
@redirects
elsif result.code == '301' || result.code == '302' && result['Location']
@redirects.append({ url:, code: result.code, request_time: })
puts 'Redirecting to ' + result['Location']
build_response(result['Location'])
else
@redirects.append({ url:, code: result.code, request_time: })
puts 'Some other response'
@redirects
end
rescue => e
end_time = Time.now
request_time = (end_time - start_time) * 1000.0
puts "ERROR: An unexpected error occurred. #{e}"
@redirects.append({ url:, code: nil, request_time:, error: "#{e}" })
end
def get_result
@redirects
end
end
end
What is an API? API stands for "Application Programming Interface". It's just a fancy term for a way or method of exposing your code or program and enabling it to be used by another person or program. We created a Redirect Service in our Rails app, but how will anyone use it? We'll, we need to create an API. In our case this will consist of a route and a controller with an action.
In our config/routes.rb
we'll add an entry for our redirect checker
Rails.application.routes.draw do
...
post "redirect_checkers" => "redirect_checkers#create"
...
end
Then, we need to create a RedirectCheckers controller with a create action that calls our RedirectChecker service.
class RedirectCheckersController < ApplicationController
...
def create
input = redirect_checker_params[:input]
redirect_service = Services::RedirectChecker.new(input)
output = redirect_service.get_result
@redirect_checker = RedirectChecker.new({
input:,
output:,
})
respond_to do |format|
if @redirect_checker.save
format.json { render status: :created, json: { urls: output } }
else
format.json { render json: @redirect_checker.errors, status: :unprocessable_entity }
end
end
end
private
def redirect_checker_params
params.require(:redirect_checker).permit(:input)
end
end
That's it! This route and controller will handle accepting a request to the /redirect_checkers.json
route, running our ResourceChecker service and responding with a list of URLs.
At this stage it's useful to test using an application like Postman. This is a handy tool that most developers use as they build out API's. Once we test and verify our API works through Postman we can begin building out our front end.
We are going to build out our front end using React and Next. I already have a Next application created that serves up pages from the pages directory. So I will create a new page at pages/tools/redirect-checker.tsx
export default function RedirectChecker() {
...
return <section id="redirect-checker">
<div className="container">
<h1>URL Redirect Checker</h1>
<form onSubmit={handleSubmit}>
<div className="row gtr-uniform">
<div className="col-12">
<input type="url" placeholder="https://...." />
</div>
<div className="col-12">
<ul className="actions">
<li>{loading ? <span>Loading...</span> : <button type="submit" className="button primary">Check URL</button>}</li>
</ul>
</div>
</div>
</form>
</div>
</section>;
}
This page has a form with an input that will accept a URL. It also has a onSubmit function that will be called when the form is submitted and a loading state for the button.
Let's take a look at the handleSubmit
function.
const handleSubmit = async (e) => {
e.preventDefault();
const input: HTMLInputElement|null = document.querySelector('input[type="url"]');
if (input?.value) {
setLoading(true);
const response = await fetch(url + "/redirect_checkers.json", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
redirect_checker: {
input: input?.value
}
}),
});
setLoading(false);
if (response.status === 201) {
const json = await response.json()
setUrls(json.urls)
} else {
setError(true)
}
}
}
We are getting the value from our input field and then making a request to our /redirect_checker.json
API. If we get a successful 201 network response, we add the list of URLs from our response to our state. If we don't get a successful response, we set an error state.
Now, we'll add some code to display our list of URLs once we have the response form our API.
{urls.length > 0 &&
<div>
<h2>Check Result</h2>
<div className="table-wrapper">
<table>
<tr>
<th>Step</th>
<th>Code</th>
<th>URL</th>
<th>Secure?</th>
</tr>
{urls.map((item:{code:string, url:string}, index) => {
let codeClass = '';
switch (item.code) {
case '200':
codeClass = 'success';
break;
case '301':
case '302':
codeClass = 'warning'
break;
default:
codeClass = 'error'
}
return <tr>
<td>{index + 1}</td>
<td className={codeClass}><span className="inner">{item.code || 'Error'}</span></td>
<td>{item.url}</td>
<td>{item.url.slice(4, 5) === 's' ? 'Yes':'No'}</td>
</tr>
}
)}
</table>
</div>
</div>}
Here is what the whole page looks like:
import { useState } from 'react';
export default function RedirectChecker() {
const [urls, setUrls] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false)
const url = process.env.NEXT_PUBLIC_APP_URL;
const handleSubmit = async (e) => {
e.preventDefault();
const input: HTMLInputElement|null = document.querySelector('input[type="url"]');
if (input?.value) {
setLoading(true);
const response = await fetch(url + "/redirect_checkers.json", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
redirect_checker: {
input: input?.value
}
}),
});
setLoading(false);
if (response.status === 201) {
const json = await response.json()
setUrls(json.urls)
} else {
setError(true)
}
}
}
return <section id="redirect-checker">
<div className="container">
<h1>URL Redirect Checker</h1>
<form onSubmit={handleSubmit}>
<div className="row gtr-uniform">
<div className="col-12">
<input type="url" placeholder="https://...." />
</div>
<div className="col-12">
<ul className="actions">
<li>{loading ? <span>Loading...</span> : <button type="submit" className="button primary">Check URL</button>}</li>
</ul>
</div>
</div>
</form>
{urls.length > 0 &&
<div>
<h2>Check Result</h2>
<div className="table-wrapper">
<table>
<tr>
<th>Step</th>
<th>Code</th>
<th>URL</th>
<th>Secure?</th>
</tr>
{urls.map((item:{code:string, url:string}, index) => {
let codeClass = '';
switch (item.code) {
case '200':
codeClass = 'success';
break;
case '301':
case '302':
codeClass = 'warning'
break;
default:
codeClass = 'error'
}
return <tr>
<td>{index + 1}</td>
<td className={codeClass}><span className="inner">{item.code || 'Error'}</span></td>
<td>{item.url}</td>
<td>{item.url.slice(4, 5) === 's' ? 'Yes':'No'}</td>
</tr>
}
)}
</table>
</div>
</div>}
</div>
</section>;
}
That's it! Now, when we load up our page, we will see a form with an input and a button to call our API.
After we submit a URL, we will see the list of URLs and their status codes below our form.
Our new Redirect Checker Tool is ready for prime time! Check it out here: Redirect Checker
Building out this Redirect Checker was pretty quick and painless. It's motivated us to build more tools that people might find useful.
Do you have any feedback or suggestions? Feel free to send us a message.