I participated in a company wide 24hr hackathon and here's how it went

I participated in a company wide 24hr hackathon and here's how it went

ยท

5 min read

Run through of the idea

I did a last minute registration, couldn't find anyone to join me, with a bit of hesitation ran solo ๐Ÿ˜‘

Most of the themes were focused towards company's product use cases. I ended up picking the General theme, because I was not interested in any of them.

There was an area which bugged me for quite sometime and it was around HTTP polling which we were doing in a lot of places in our cloud app. The first thought that came to my mind was to use SignalR to be able to switch to a more real-time data transfer from server to client. The only problem was SignalR exposes a bidirectional channel and for the issue in hand it seemed unnecessary. The problems I wanted to solve were around status updates, log streaming etc wherein after a client-server handshake is established the client would not be sending any data to the server.

While exploring for other real-time data transfer options and talking to few other colleagues I came across this little known HTTP feature called Server-Sent Events, and it was exactly what I was looking for ๐Ÿ‘Œ

  • It created a unidirectional channel
  • Lightweight with less bloat
  • Easy to setup

From Wikipedia

Server-Sent Events (SSE) is a server push technology enabling a client to receive automatic updates from a server via an HTTP connection, and describes how servers can initiate data transmission towards clients once an initial client connection has been established.

Overall design

Everything-HackathonDec2021.drawio (1).png

I am going to talk about a redacted and miniature version of what I built, but overall the same concepts would apply. By no means these are production ready ๐Ÿ˜‹

Building blocks

Step 1 - Expose an Azure function to push text as events

Inside your Azure function app, Navigate to Development Tools > Advanced Tools image.png

That should open up kudu service that runs your function app

Navigate to Debug Console > CMD image.png

Find wwwroot sub folder and add a SampleLog.txt there (put whatever content you would want to send as events)

With our log file in place, lets create a function to access it.

Head back to functions tab and create a function image.png Select a HTTP trigger template, since we need an API

image.png

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using System;
using System.Text;
using System.IO;
public static async Task Run(HttpRequest req, ILogger log, ExecutionContext context)
{
    var sampleLogPath = System.IO.Path.Combine(context.FunctionDirectory, "..\\SampleLog.txt");
    var allText = await File.ReadAllTextAsync(sampleLogPath);
    req.HttpContext.Response.Headers.Add("Content-Type", "text/event-stream");
    foreach (var line in allText.Split("\n"))
    {
        await Task.Delay(2000);
        byte[] bytes = Encoding.ASCII.GetBytes($"data:{line}\n\n");
        await req.HttpContext.Response.Body.WriteAsync(bytes);
        await req.HttpContext.Response.Body.FlushAsync();
    }
}

The response should start with data: and end with \n\n, as browsers look at this to figure that these are chunked events.

Step 2 - Expose an Azure function to send status updates at random intervals

Create another function, select the HTTP trigger template again

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using System.Text;
public static async Task Run(HttpRequest req, ILogger log)
{
    string[] states =
    {
        "Lorem", "Ipsum", "is", "simply", "dummy", "text", "of ", "the", "printing", "and", "typesetting", "industry"
    };
    req.HttpContext.Response.Headers.Add("Content-Type", "text/event-stream");
    Random random = new Random();           
    for (short i = 0; i < states.Length; i++)
    {
        await Task.Delay(random.Next(5, 15) * 1000);
        byte[] bytes = Encoding.ASCII.GetBytes($"data:{states[i]}\n\n");
        await req.HttpContext.Response.Body.WriteAsync(bytes);
        await req.HttpContext.Response.Body.FlushAsync();
    }
}

Our backend APIs are ready and serving data now.

Step 3 - Create a simple static page that has logic to consume both the events

Do make sure to replace the eventsource endpoints, I may delete my function app after a while

<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" integrity="sha512-Fo3rlrZj/k7ujTnHg4CGR2D7kSs0v4LLanw2qksYuRlEzO+tcaEPQogQ0KaoGN26/zrn20ImR1DfuLWnOo7aBA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>  
<i class="fas fa-file-alt fa-2x" onclick="streamlog()"></i>
<i class="fas fa-tasks fa-2x" onclick="streamevent()"></i>
<div id="cards" class="card-columns">
</div>
<script>

function streamlog() {
    var cards = document.getElementById("cards");
    var cardHtml = '<div class="card" style="background-color: #343a40!important; width:500px; height:300px; overflow-y: scroll;"><div class="card-header" style="background: #343a40; color:white">Log</div><div class="card-body" style="background: #343a40; color: yellow"><p id="log"></p></div></div>'
    cards.insertAdjacentHTML("beforeend", cardHtml);
    var logSource = new EventSource('https://kd-playground-functionapp.azurewebsites.net/api/StreamLogs?code=MuFPYxFQhc1q7CaxM2X8e8PLoJGoJWpo/8QUWABctC1g7WCObi9cOQ==');
    logSource.onmessage = function(event) 
    {
        var element = document.getElementById('log');
        if (element)
        {
            element.textContent += "<br>" + event.data;
        }
    };
}
function streamevent() {
    var cards = document.getElementById("cards");
    var cardHtml = '<div class="card" style="width:200px; height:300px;border:1px; background-color:#311b92;"><div class="card-header" style="color:white">Event Status</div><div class="card-body"><p id="status"></p></div></div>'
    cards.insertAdjacentHTML("beforeend", cardHtml);
    var logSource = new EventSource('https://kd-playground-functionapp.azurewebsites.net/api/HttpTrigger1?code=5C8YjKcM2D3Oj10iAq8F3BLHwnzkjNHMS24mac4tkG8pwxnP1ZGjzA==');
    logSource.onmessage = function(event) 
    {
        var element = document.getElementById('status');
        if (element)
        {
            element.textContent += "<br>" + event.data;
        }
    };
}
</script>
</body>
</html>

Step 4 - Create a repository in GitHub and push the html file

This should be pretty straight forward.

Step 5 - Serve html pages via DigitalOcean

You can use your preferred platform too

You should be able to create a DigitalOcean account using GitHub. They do give a 100$ free credit but in any case I think 3 static pages are free ๐Ÿ˜Ž

  • Create a new app, select source as GitHub image.png
  • Select your repository and branch image.png
  • Name your static site image.png
  • Select starter pack and launch image.png
  • Once the build completes we should have our page deployed at a public URL image.png
  • Dont forget to add a CORS entry in your Azure function app for this URL image.png

Final outcome

final_62026d026b89e200619ed6e2_632053.gif

  • By default, if the connection between the client and server closes, the connection is restarted, until it is explicitly closed by the client.
  • Multiple events can be transferred over the same channel.
  • Different events can be transferred over the same channel.

Break down of 24hrs

  • ~10% spent on wrapping my head around what I wanted to build, feasibility in my mind
  • ~20% spent on creating and testing azure functions
  • ~40% spent on front end code :( i hate css
  • ~20% spent on bringing it all together and e2e testing
  • ~10% on prep for demo
  • Also I did some basic research even before hackathon day ๐Ÿค“

Few takeaways

  • Make sure to ensure feasibility, you don't want to bang your head and end up pivoting, no harm in doing some research.
  • Keep accounts created beforehand with max max max access.
  • Jump straight into demo, do not build fancy presentations, there is no point if you cant showcase what you were able to build.
  • Keep some buffer for questions, more questions mean people actually are interested and find value, OR they just didn't get it, in any case you get to explain them again.

Resources

  1. Mozilla documentation on Server sent events
  2. Checkout DigitalOcean
  3. Checkout Azure functions