Building a Redirect Checker

Published: 04/11/2024

Author: Tech and Limon

What is a Redirect Checker?

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).

What technologies are we going to use?

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.

Lets get building!

1. The Redirect Service

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

2. The API

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.

3. The 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.

Screenshot of empty redirect checker

After we submit a URL, we will see the list of URLs and their status codes below our form.

Screenshot of redirect checker with results

Our new Redirect Checker Tool is ready for prime time! Check it out here: Redirect Checker

What did we learn?

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.

Join our list for special offers, news and inspiration.