Synthetic Monitoring

Simulate visitor interaction with your site to monitor the end user experience.

View Product Info

FEATURES

Simulate visitor interaction

Identify bottlenecks and speed up your website.

Learn More

Real User Monitoring

Enhance your site performance with data from actual site visitors

View Product Info

FEATURES

Real user insights in real time

Know how your site or web app is performing with real user insights

Learn More

Infrastructure Monitoring Powered by SolarWinds AppOptics

Instant visibility into servers, virtual hosts, and containerized environments

View Infrastructure Monitoring Info
Comprehensive set of turnkey infrastructure integrations

Including dozens of AWS and Azure services, container orchestrations like Docker and Kubernetes, and more 

Learn More

Application Performance Monitoring Powered by SolarWinds AppOptics

Comprehensive, full-stack visibility, and troubleshooting

View Application Performance Monitoring Info
Complete visibility into application issues

Pinpoint the root cause down to a poor-performing line of code

Learn More

Log Management and Analytics Powered by SolarWinds Loggly

Integrated, cost-effective, hosted, and scalable full-stack, multi-source log management

 View Log Management and Analytics Info
Collect, search, and analyze log data

Quickly jump into the relevant logs to accelerate troubleshooting

Learn More

Monitoring a RESTful API on a Headless CMS Using Pingdom

There are plenty of ways to gain insights on website availability and performance, from setting up complex monitoring agents to browsing through real-time logs. Few services are as straightforward and robust as SolarWinds® Pingdom®. Pingdom lets you set up checks for your website including uptime, page speed, and user interactions. It then collates the results in a dashboard for you to visualize. But aside from its point-and-click interface, Pingdom also has a powerful API, which you can use to automate the collection of all sorts of data.

In this post, we’ll explore some of the capabilities of the Pingdom API. We’ll build a quick headless CMS in Ruby, then automate some common tasks such as listing all the available routes, fetching their statuses, and testing all this logic before implementation.

Setting Up Your Environment

All the sample code for this project can be found on GitHub, if you’d like to simply clone that project to get started. Make sure you have a modern version of Ruby installed (greater than 2.5).

If you’re starting from scratch, create your Gemfile and paste the following lines into it:

# frozen_string_literal: true
source "https://rubygems.org"

gem 'httparty', '~> 0.18'
gem 'minitest', '~> 5.14'
gem 'rack-test', '~> 1.1'
gem 'sinatra', '~> 2.1'
gem 'webmock', '~> 3.12'

Create a new file called app.rb and paste these lines:

# frozen_string_literal: true
require 'sinatra'

get '/' do
  'Hello, world!'
end

Run bundle exec ruby app.rb. Navigate to localhost:4567. There,  you should see the bare-bones “Hello, world!” response.

Your final task will be to obtain an API token from Pingdom. Once you’ve done so through the Pingdom API, create the following test request to ensure you have access:

curl -X GET https://api.pingdom.com/api/3.1/checks -H 'Authorization:Bearer $PINGDOM_API_TOKEN

If you get back a JSON response without any errors, you’re all set.

Building the API

Our headless CMS will be very simple. All our content will be stored as a text file, and there will just be three routes:

  • GET /posts returns all of the posts
  • GET /post/:id returns a single post with the :id identifier
  • POST /post takes in a JSON payload and creates a new post

Our complete app might look something like this:

# frozen_string_literal: true
require 'sinatra'
require 'json'

get '/' do
  'Hello, world!'
end

get '/posts' do
  posts = Dir.entries('content').each_with_object([]) do |filename, arr|
    next if filename == '.' || filename == '..'

    arr << { id: File.basename(filename, '.md'), body: File.read("content/#{filename}") }
  end
  posts.to_json
end

get '/post/:id' do
  filename = params[:id]
  { id: filename, body: File.read("content/#{filename}.md") }.to_json
end

post '/posts' do
  request.body.rewind
  data = JSON.parse(request.body.read)
  File.open("content/#{data['id']}.md", 'w') { |f| f.write(data['body']) }
  redirect '/posts'
end

Given a directory called content, with a bunch of Markdown files in it, the GET /posts route will simply read all the files and return the information in a JSON format. The GET /post/:id route will look up a file based on its filename and return it in a JSON format. And the POST /posts will take a JSON request body and create a new file from it. In fact, you can test the POST route by creating a request similar to the following:

curl -X POST http://localhost:4567/posts -d '{"id":"new-post", "body":"This is a new post!"}'

At this point, our API is woefully incomplete. Among other things, we should have some authentication for creating and deleting posts (to ensure not everyone can simply change content), and we should have some error checking (to 404 in case the wrong :id is provided for a nonexistent file). But, for the purposes of our Pingdom sample, it’s good enough.

Testing the API

As with any modern application, adding a test suite guarantees your app’s behavior and functionality remains consistent through any future changes. For our demo, we’ll be relying on native Ruby test frameworks, like Minitest, to simulate user behavior and assert that the responses are what we’d expect:

ENV['RACK_ENV'] = 'test'

require_relative 'app.rb'
require 'minitest/autorun'
require 'rack/test'

class MiniTest::Spec
  include Rack::Test::Methods

  def app
    Sinatra::Application
  end
end

describe 'My App' do
  it 'should get posts' do
    get '/posts'
    assert last_response, :ok?
    response = JSON.parse(last_response.body)
    refute_predicate response.count, :zero?
  end

  it 'should get post' do
    get '/post/first-post'
    assert last_response, :ok?
    response = JSON.parse(last_response.body)
    assert_equal response['id'], 'first-post'
  end

  it 'should post a new post' do
    FileUtils.rm('content/a-new-post.md') if File.exist?('content/a-new-post.md')
    post '/posts', {
      id: 'a-new-post',
      body: 'Look at all this lovely content'
    }.to_json, { 'CONTENT_TYPE' => 'application/json' }
    follow_redirect!
    assert last_response, :ok?

    response = JSON.parse(last_response.body)
    assert(response.any? { |post| post['id'] == 'a-new-post' })
  end

  it 'should delete a post' do
    unless File.exist?('content/some-test-post.md')
      File.open('content/some-test-post.md', 'w') { |f| f.write('Words words.') }
    end

    delete '/post/some-test-post'
    follow_redirect!
    assert last_response, :ok?

    response = JSON.parse(last_response.body)
    refute(response.any? { |post| post['id'] == 'some-test-post' })
  end
end

Even if Ruby isn’t your strongest language, the test DSL should make it easy to understand what’s happening. For example, consider the following method:

it 'should get post' do
  get '/post/first-post'
  assert last_response, :ok?
  response = JSON.parse(last_response.body)
  assert_equal response['id'], 'first-post'
end

We’re testing the behavior of GET /post/:id here. We pass `first-post` as an ID, and assert that the last_response of that API call does in fact return the post we expect.

Automated Monitoring

So far, we’ve only completed half of our original goal. We created a headless CMS in Ruby which can list posts, as well as create and delete them. We also added a test suite to verify our app behaves as we expect it to. Now, suppose we’ve hosted this application on a platform like DigitalOcean at a domain called our-great-cms.app. We’ll now use some features of the Pingdom API to ensure our service is functional.

One quick API feature we can try is having a single probe check our app’s availability. Given a domain (and an optional path), a random Pingdom server from around the world will attempt to access your site. The HTTP request to make this call looks something like this:

curl "https://api.pingdom.com/api/3.1/single?type=http&host=our-great-cms.app" -H 'Authorization:Bearer $PINGDOM_API_TOKEN'

Now let’s do something a little more interesting. Follow this tutorial on setting up an uptime check through the Pingdom UI. With that established, verify the check was created with this API call:

curl "https://api.pingdom.com/api/3.1/checks" -H 'Authorization:Bearer $PINGDOM_API_TOKEN'

You should get back a checks array. Take note of the id of your newly created check, as we’ll be using it throughout our API calls.

With this check created, we can now perform a variety of actions through the API, like getting the status results of this check:

curl "https://api.pingdom.com/api/3.1/results/$check_id" -H 'Authorization:Bearer $PINGDOM_API_TOKEN'

Or, we can get a summary of the average uptime:

curl "https://api.pingdom.com/api/3.1/summary.average/$check_id" -H 'Authorization:Bearer $PINGDOM_API_TOKEN'

We can then integrate all this capability straight into our application itself. Add the following lines of code to the end of app.rb:

require 'httparty'

get '/status/:password' do
  return 404 if params[:password].nil? || params[:password] != 'supersecret'

  url = 'https://api.pingdom.com/api/3.1/results/$check_id'
  headers = {
    Authorization: 'Bearer $PINGDOM_API_TOKEN'
  }

  response = HTTParty.get(url, headers: headers)
  JSON.parse(response.body)
end

Essentially, what we’re trying to do here is create some sort of admin status page. If a user navigates to /status, they’ll be denied entry if they haven’t provided the right password (in this case, it’s supersecret). If the password was given, then the page will make a request to the Pingdom API and return the response back.

It cannot be stressed enough: in a real application, DO NOT paste your credentials directly into the file! Instead, following the Twelve-Factor App recommendations, you should store your sensitive information in environment config variables.

With this route newly established, we can test it as well:

require 'webmock/minitest'

it 'should require password for status' do
  get '/status/notreal'
  assert last_response.status, 404
end

it 'should make a call out to pingdom ' do
  stub_request(:get, "https://api.pingdom.com/api/3.1/results/$check_id")
                .with(
                  headers: {
                  'Authorization'=>'Bearer $PINGDOM_API_TOKEN',
                  })
                .to_return(status: 200, body: '{
                  "activeprobes":[257],
                  "results":[
                    {"probeid":261,
                      "time":1617657276,
                      "status":"up",
                      "responsetime":1186,
                      "statusdesc":"OK",
                      "statusdesclong":"OK"
                    }]
                }')

  get '/status/supersecret'
  assert last_response.status, 200
end

Here, the ever-useful webmock gem simulates a response to the Pingdom API. We don’t actually make a call; however, in our test, we tell webmock what we expect the request to look like, and when we navigate to get ‘/status/supersecret’, webmock asserts that the request is actually being made. We’re also asserting that a user without the right password gets a 404 error response.

Conclusion

We’ve only scratched the surface of what can be done with the Pingdom API. For example, you could also set up maintenance windows in the event of some serious downtime. Or, you could simulate user behavior using TMS checks. All these can be integrated in places where HTTP requests can be made, whether it’s the command line, a Slack bot, or even in an app itself.

The full source code for this demo can be found on GitHub. For more tutorials, check out the Pingdom guided tour.

Be sure to check out the 30-day free trial of Pingdom and experience how it helps you gain insights on website availability and performance.

Introduction to Observability

These days, systems and applications evolve at a rapid pace. This makes analyzi [...]

Webpages Are Getting Larger Every Year, and Here’s Why it Matters

Last updated: February 29, 2024 Average size of a webpage matters because it [...]

A Beginner’s Guide to Using CDNs

Last updated: February 28, 2024 Websites have become larger and more complex [...]

The Five Most Common HTTP Errors According to Google

Last updated: February 28, 2024 Sometimes when you try to visit a web page, [...]

Page Load Time vs. Response Time – What Is the Difference?

Last updated: February 28, 2024 Page load time and response time are key met [...]

Monitor your website’s uptime and performance

With Pingdom's website monitoring you are always the first to know when your site is in trouble, and as a result you are making the Internet faster and more reliable. Nice, huh?

START YOUR FREE 30-DAY TRIAL

MONITOR YOUR WEB APPLICATION PERFORMANCE

Gain availability and performance insights with Pingdom – a comprehensive web application performance and digital experience monitoring tool.

START YOUR FREE 30-DAY TRIAL
Start monitoring for free