DEV Community

catrina
catrina

Posted on • Originally published at catrina.me on

First Heroku App: Small API in PHP to Talk to .Net Core

I've recently been curious about switching to a time API for my time stamps and removing any dependency the app might have on the server for a timestamp. Upon Googling I found some paid services, some free and of the free ones, I noticed one was hosted on Heroku. I've heard of Heroku, but never had a reason to attempt to use it. This was the perfect chance.

How I Created a Small "GetTime" API

First, I created a free account on Heroku, nothing special. After verifying my email, I logged in to my Heroku Dashboard and up on the right hand corner, selected Create New App. I named it my company-api and out popped an app.

I decided on just plain, legacy PHP and a simple DateTime string passed thru JSON encode, just to get started. No authentication, no timezone, just a simple spit out if a request to the site came, like this:

    header('Content-type:application/json;charset=utf-8');

    $DateTime = new DateTime();
    $currentTime = $DateTime->format("Y-m-d H:i:s");
    echo json_encode($currentTime);
Enter fullscreen mode Exit fullscreen mode

I created a Git repo for this brand new file and pushed it out. Then, I went back to Heroku, Dashboard, My App and Deploy. I selected Github as my deploy "resource" and selected the new repo I just made along with correpsonding branch.

I hit manual deploy and Heroku runs off to my GitHub repo, grabs the code, compiles and publishes.

It failed.

Beginner Troubles

My first problem was that Heroku could not determine what language I had chosen for my app (you'd think the <?php would give it away ...). You need either one of two things: a composer.json file or an index.php file (for legacy, like mine). I renamed my file to index.php and all I needed now was a "builder pack".

To add a builder pack, I went back to Heroku, Dashboard, My App and Settings. Under builder pack, I added one for: "php". Save settings and done.

I went back to Deploy, Manual Deploy and had a successful output. Yay! First Heroku app!

Adding Some Security

I want to make sure this API is receiving and sending JSON, so there's a few IF's I make the request go through before I hit logic on my PHP page. I also want to (lightly) secure the requests made to this API and monitor our usage of it for metrics information (and future investment). Since this itty bitty API is just relying on 1 index.php file, I figure this can be a sort of "router" for future API's. So, this is what I added to the final PHP file:

  • Verify the request is in JSON
  • Require two variables passed:
    • a secret api "key"
    • an api "request"
  • Use a switch statement to forward to needed method


The final, simple 1 page PHP Heroku API:
<?php
header('Content-type:application/json;charset=utf-8');

//Make sure that it is a POST request.
if(strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') != 0){
    throw new Exception('Request method must be POST!');
}

//Make sure that the content type of the POST request has been set to application/json
$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : '';
$contentIsJson = strpos($contentType, "application/json");

if ($contentIsJson === false){
    throw new Exception('Content type must be: application/json');
}

//Receive the RAW post data.
$content = trim(file_get_contents("php://input"));

//Attempt to decode the incoming RAW post data from JSON.
$decoded = json_decode($content, true);

$app = strtoupper($decoded['API']);
$key = $decoded['APIKEY'];

//verify user key - simple MD5 generator: http://onlinemd5.com/.  will build user management for keys if ever needed
if ( $key == "BEAF1CB722A3F7758C7A7FA43F6BF2D1" )
{   

    switch ($app) {
        case "TIME":
            $jsonString = getTime();
            $arr = array('datetime' => $jsonString);
            break;
        default:
            $arr = array('error' => "Unknown Request On API");
            break;
    }  

    echo json_encode($arr);
}


//return the current time
function getTime ()
{
    $DateTime = new DateTime();

    //by default heroku returns time in UTC - can change in dashboard, config vars, only use as needed below
    //$DateTime->modify('-6 hours');
    $currentTime = $DateTime->format("Y-m-d H:i:s");

    $jsonString = $currentTime;

    return $jsonString;
}
?>

Enter fullscreen mode Exit fullscreen mode

 

Verifying the Response

I used Postman to send a raw JSON request to my Heroku app (used their default/free url). I wanted to make sure all my problems were resolved with this new toy, first, and then move on. Here's what the raw request and response look like on Postman:

Heroku + my PHP are responding nicely!

Handling the Request and Response in C#

So here's how I did the same request and received the response in C# (I use dotnet Core):

public async Task<string> OurHerokuAPI()
{
    string reqUrl = "https://mycompany-api.herokuapp.com";

    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders
            .Accept
            .Add(new MediaTypeWithQualityHeaderValue("application/json"));

        try
        {

            var query = new
            {
                APIKEY = "BEAF1CB722A3F7758C7A7FA43F6BF2D1",
                API = "time"
            };

            var asJson = JsonConvert.SerializeObject(query);
            HttpResponseMessage response = await client.PostAsync(reqUrl, new StringContent(asJson, Encoding.UTF8, "application/json"));

            if (response.IsSuccessStatusCode)
            {
                var definition = new { datetime = string.Empty };
                var json = JsonConvert.DeserializeAnonymousType(response.Content.ReadAsStringAsync().Result, definition);

                time = json.datetime;

                //monitors success across various "time" api's. in case this particular one fails, there can be various backups until success flag returns true.
                success = true;
            }
        }
        catch (OperationCanceledException)
        {
            //TODO: CREATE ERROR MESSAGE SEND BACK TO EMAIL/ERROR 
        }
    }
    return time;
}
Enter fullscreen mode Exit fullscreen mode

 

If you see notes, yes, I decided to have some fun and create maybe two other functions, just like the one above, where I query some "free" time api's until that "success" flag turns true (of course, times are controlled to same format). After x tries, I reluctantly call my last function, that gets the server timestamp. As said, I wanted my timestamp to be independent of server. So, if it hits this last method, I also send myself an error email that the time API is failing.

In the future, I could use environment variables (config vars) more wisely, instead of hardcoding. There's also so much clean up to do, but this was a very fun intro into Heroku!

Top comments (0)