⭐ If you would like to buy me a coffee, well thank you very much that is mega kind! : https://www.buymeacoffee.com/honeyvig
Hire a web Developer and Designer to upgrade and boost your online presence with cutting edge Technologies

Tuesday, December 6, 2022

Feeding the frontend – displaying data with D3.js

 

It’s been a while since I’ve posted any updates about Switchy McPortface. It works just fine in my dayjob and the data can be viewed by anyone wanting to see what is plugged into what, which has been helpful in a few situations.

However, aside from a few customised tables and functions, there’s not really a lot more that I’ve done to improve functionality. What I have done, however, is embark on making a nicer frontend with some interaction. Essentially what I am aiming to do is to display all of the computers on a single webpage for a given room, roughly corresponding to the positions that they actually exist in in each room. My first thought a few years ago was to use Unity3d or C++ and OpenGL but, since I’ve already used these things before, why not try something new and use Javascript to make a web app or something?

This guide is going to go through a nicer way to display data than just using a table generated from some basic HTML in php. There’s a few libraries out there, but I thought it would be nice to get stuck into something that has some widespread use and the name Data Driven Documents sums up nicely what I’m trying to do – generate something visual based on some underlying data. D3 gives a nice way to manipulate the DOM, bind data and provide some visualisation and I felt it fit somewhere between jQuery and (other) visualisation libraries for this project. So without further ado, here is a quick rundown of one way to use d3.js to generate something prettier than a few table rows and columns!

Starting out

Because this builds on my previous work (with the desktop client now uploaded to Github), I won’t go into exactly how you get this specific data generated, or a web server set up and running. The focus here is how to interpret data with a basic web page – so hopefully you’ll already have a way to serve and process some php files.

For this project overall, I used three files:

  • index.html – the main file you’ll call when you open your browser. It’ll also contain the page styles, instead of a separate .css file, and invoke the Javascript file
  • main.js – the file that will contain the scripts used that, in turn, call d3.js
  • query.php (this comes in part 2) – the file that handles the communication between your page and a database

The index and js files can sit at the web directory root of the server, or they can reside on your own machine. I’ve been using Brackets and testing this on my local machine, with php files running off a virtual machine and the concept is really simple:

  • Open index.html 
  • Load d3.js and the main.js files
  • Load in some data that represents computers
  • Create an SVG on the main page
  • Create a sub shape for that SVG for each computer

Index.html

The html landing page isn’t going to have an awful lot in it. Its primary purpose is to load the JS files, which in this case are d3.js v4 and jquery (used for simple AJAX calls later in part 2).

<html>
    <head>
        <title>Room test</title>
        <script src="https://d3js.org/d3.v4.js"></script>
        <script src="jquery-3.1.1.js"></script>       
    </head>
    <body>  
    </body>    
        <script src="main.js"></script>
</html>

Note that main.js is called after everything else; this is so that it loads after other JS libraries. You also don’t need to type out the entire <script type=”text/javascript” src=”main.js”></script> as of html5, since Javascript is the default script type now and the “type” attribute can be omitted.

main.js

This is where the main gubbins of this example resides. The gist of it is:

  • Create a blank SVG
  • Load in list of computers and a list of positions
  • Draw a new circle for each computer at its respective position
  • For fun, display the hostname for each of those computers when you hover over it with the mouse

Here’s each part broken down:

Creating a blank SVG

Once you’ve loaded the d3.js library in your index.html file, you can access d3 functions as in the example below. All it does is to create a blank SVG, appended to the body of the page, with an arbitrary width and height.

var w = 500;
var h = 450;
var svg = d3.select("body")
            .append("svg")
            .attr("width", w)
            .attr("height", h);

 

Load in computers and positions

So far so good? Next I’m just going to create two objects for a Computer and a Position, each taking some parameters to represent what they are. A computer is, for now, defined as simply a name – and this has a “place”. A position is a combination of a place number and its respective x and y coordinates. The reason these are separate is because at some point we might have different rooms or position layouts and I want to keep the computer and position data separate.

function Computer(place, hostname) {
    this.place = place;
    this.hostname = hostname;
}

function Position(place, posx, posy) {
    this.place = place;
    this.posx = posx;
    this.posy = posy;
}

var positions = [
    new Position('10', 0, 0),
    new Position('20', 80, 0),
    new Position('30', 160, 0),
    new Position('40', 240, 0),
    new Position('50', 0, 100),
    new Position('60', 80, 100),
    new Position('70', 160, 100),
    new Position('80', 240, 100)
];

var computers = [
    new Computer('10', "WS10562"),
    new Computer('20', "WS10239"),
    new Computer('50', "WS10555"),
    new Computer('60', "WS9111"),
    new Computer('70', "WS11032"),
    new Computer('40', "WS11031")
];

So here are two arrays of data for the computers and positions. The logic is that, whilst there may be any number of positions, they may not all be filled by a computer. Anyhow, this is all sort of irrelevant to D3 for now (and it could have been loaded in externally), so I’ll get on and demonstrate how you can now put something on screen.

Draw a new circle for each computer at its respective position

Very simply, I’m going through that array of computers and, for each one, I’ll add a circle at that position.

for (var x = 0; x < computers.length; x += 1) {
    
    var posIndex = positions.findIndex(y => y.place == computers[x].place);
                                        
    svg.append("svg")
        .append("circle")
        .attr("cx", positions[posIndex].posx + 30)
        .attr("cy", positions[posIndex].posy + 30)
        .attr("r", 20)
        .style("fill", "purple");

}

To match the data in the positions array up with the computers array, I need to first find the index of the correct item in positions array that corresponds with the “place” of the computer at the current index. In SQL, a left or an inner join would do what we need to do but in this example, I’m using two separate arrays of data that I need to match up.

Apparently, as of ES6, you can use findIndex. What this will do is return the index of positions where it finds a match by the function provided. Because I’m trying to match a property of an item in the array, the function needs to compare that “place” property for each item in the position array to the current computer’s “place” property. The => operator shortens the need to make that function by using y as the variable to represent the operative array item and it will return true when it is equal to the condition given.

For each of these computers, you can then append the SVG created earlier with a circle and give it the attributes for the radius, x and y coordinates (with an offset) and modify their style (which could be done in the index.html file but you can do it now too), which is just the fill colour here. This just adds some circles nested within the SVG tag that has been added to the page – nothing hugely complex, which is the great thing about D3. It is just a nice way to access the DOM and to add elements to a webpage dynamically.

It is important to note that this is not the best way to use D3. The way in which it should be done is to say you’ll add all of the elements of a given shape in one call then pass the data in – here, we’re going through the data first and then just adding one element on each iteration of the loop. There is no need to use a for loop in d3 and so what I’ve done is counter-intuitive to the way you’d normally learn it; but I’m simplifying the process of combining two arrays’ worth of data which I haven’t been able to find a nicer way to do when using D3. Besides, later it won’t be necessary; however, it was useful to figure out a nice way to make things work for now.

Display the hostname on mouse events

This is really easy to do. You first need to modify the previous code a little to look like this:

    svg.append("svg")
        .append("circle")
        .data(computers) //Add this!
        .attr("cx", positions[posIndex].posx + 30)
        .attr("cy", positions[posIndex].posy + 30)
        .attr("r", 20)
        .style("fill", "purple")
        .on("mouseover", fadein) //Mouse over event
        .on("mouseout", fadeout); //Mouse moved away event

Although it isn’t used as extensively as it will be later, the data function has been added in which is needed to provide the hostnames to each of the shapes when you mouse over them. For that to happen, two events need to be added with the on function – which are “mouseover” and “mouseout” (events that are triggered when the mouse is detected to have entered and exited the boundaries of the element in question). As much as I like the whole anonymous function thing in JS, I’m just going to call some named ones because I hate polluting what should be simple code with a bunch of ugly long functions that are more than 2 or 3 lines long.

And these are the functions you need:

var div = d3.select("body")
            .append("div")
            .attr("class", "tooltip")
            .style("opacity", 0)
            .text("");



function fadein(d, i) {
    div.transition().duration(200).style("opacity", 0.9);
    div.style("left", d3.mouse(this)[0])
	.style("top", d3.mouse(this)[1])
	.html(d.hostname);
    //console.log("fadeoin");
}

function fadeout() {
    div.transition().duration(400).style("opacity", 0);
    console.log("fadeout");
}

Here, the two functions can take two parameters passed into them by d3, which are the data and an index.The data is the element of the array used when each shape is created (although, in this case, that will always just be the first element, since only one circle is added at a time). The index will be which iteration that d3 element was created during.

I’ve also added a new div to the page up there too – that’s because, in order to have text pop up, I want to do it in a tiny floating “box”, which is really just an HTML element. As a result, changing the text is as simple as changing the html property of the object to whatever the hostname of that data object is. Note that, if you were to use a totally different dataset for this, “hostname” would have to be replaced by whatever else you would want to have displayed instead. I also have it appear at wherever the mouse is with a bit of a transition time, because that makes it look a bit swishier.

One last thing is that, to make it actually look nice, you might want to add a style just for the tooltip (hence div.tooltip) to the main html file within the <head> section somewhere:

<style>          
div.tooltip {
                position: absolute;
                text-align: center;
                width: 60px;
                height: 29px;
                padding: 2px;
                font: 12px sans-serif;
                background: green;
                border: 0px;
                border-radius: 9px;
                pointer-events: none;
		color: white;
            }
</style>

This centers the text and gives it a solid colour background with a bit of a rounded border and some padding.

Conclusion

If you’ve done it right, the result should look something like this (I’ve added both circles and rectangles here as a test and reduced the number of “computers” a bit for this example)

 

Carrying straight on from my previous post on the subject, I’m going to go through the alluded to third page to add, which is query.php and change main.js a little, too. The purpose is to swap out the hard coded arrays of data for an external data source, namely a web-based resource rather than any locally stored files using AJAX and JSON.

Query.php

What we’ve got so far are some blobs being drawn at various positions on the screen, based on the data stored in a couple of arrays. It isn’t all that exciting, but the main thing is that we have data in an array that can be visualised. The next step is then to load in that list of a computers from an external source. D3 can do things like load CSVs and JSON data in from a file, but since I’ve been using php to fetch data already from my database, I felt that it would be worth just adding a bit more code to what is already there to put that data into d3.

In another previous post I’d displayed the results of a query in a table, but what I want to do instead is to store them in JSON format. Its a data format that can be read and used by many different parsers on different platforms as well as being able to be directly read in by Javascript and interpreted as a list of objects.

Below is what you should be able to use to return and display a JSON string.

<?php 
	
	$username = "user";
	$password = "password";
	$room = $_GET['room'];

	$serverDB = "mysql:host=localhost;dbname=inventory";
	$conn = new PDO($serverDB, $username, $password);

	if ($conn->connect_error) {
		die("Connection failed: " . $conn->connect_error);
	}
		
	$getHosts = "SELECT * from hosts WHERE Room = '". $room ."'";

	$hosts = array();
	
	if ($result = $conn->query($getHostsPlaces))
	{
	while ($row = $result->fetch(PDO::FETCH_ASSOC))
		{
			$hosts[] = $row;
		}
		
	echo json_encode($hosts);
	}
	
	$result->closeCursor();
	$conn = null;

The first thing to note here is that, in this example, the request for the page will take the form of http://url/query.php?room=A001 – where “A001” would be the room you are asking to have the computers returned from. This lets me create the select statement with the room specified and return only the computers in a certain room.

I then create an array that has a new element added for each row (leftover terminology from the previous example). The syntax isn’t obvious but assinging a value to an array without specifying a key seems to be the same as array.push() in Javascript. After that, I then call json_encode and pass it the array of hosts, which serialises the data to be read in by something else later. In this instance I simply echo the data, which should be an array of data.

One last thing is that, unlike any previous examples, I try to now use PDOs. Included with most php/mysql installations of Linux, its a technology-neutral way to access most databases (although the connection string does specify that it is a MYSQL database), so in lieu of any real reason not to, I have decided to go down that route. However, one important thing I found out was that, if you don’t properly close the connection and queries when you’re done, you get internal server errors. So, whilst you didn’t have to do this with mysql(i) connections, you absolutely do have to with PDOs, which is probably a good thing.

When I call this page from my browser, what is displayed is the JSON’d data, as expected:


With that done, I can now go back to my main.js file and make some changes so that this data is read in.

main.js

jQuery and d3.json

It is at this point that the reference to the jQuery library becomes relevant. To access the jQuery object, you just have to use a $. It turns out that this is actually a legitimate variable name – but thankfully nobody else would be crazy enough to use it on its own so jQuery can use it all on its own. What it can then be used to do is to manipulate the DOM, like D3, although in a different way. But it can also be used to perform an AJAX request – used to grab data from another page via an XML HTTP Request, but with a fraction of the code. To call our query.php page and interpret the returned data as JSON, you can use the following code below.

$.ajax({
	url: "query.php?room=w004",
	dataType: "JSON",
	success: makeComputers
});

The callback function is what is invoked (with the data passed in as a parameter) when the AJAX call is successful (you can create a similar callback for a failure, too). I’ve made a function called makeComputers, where I will put all of the previous code.

Update: I have since found that there is an even simpler way, when using d3.xhr or d3.json. For this example, you can reduce the above code down to a single line and cut jQuery out completely:

d3.json("query.php?room=w004", makeComputers);

In either case, the result will be the same; makeComputers will be called when the call has completed. This is an asynchronous call, though, so any code inside makeComputers will likely happen a few milliseconds after whatever follows either call.

I’ve modified the original code from last time to be as follows:

function Computer(place, hostname) {
    this.place = place;
    this.hostname = hostname;
}
function Position(place, posx, posy) {
    this.place = place;
    this.posx = posx;
    this.posy = posy;
}

var positions = [
    new Position('1', 0, 0),
    new Position('2', 80, 0),
    new Position('3', 160, 0),
    new Position('4', 240, 0),
    new Position('5', 320, 0),
    new Position('6', 400, 0),
    new Position('7', 0, 100),
    new Position('8', 80, 100),
    new Position('9', 160, 100),
    new Position('10', 240, 100),
    new Position('11', 320, 100),
    new Position('12', 400, 100),
    new Position('13', 0, 200),
    new Position('14', 80, 200),
    new Position('15', 160, 200),
    new Position('16', 240, 200),
    new Position('17', 320, 200),
    new Position('18', 400, 200),
    new Position('19', 0, 300),
    new Position('20', 80, 300),
    new Position('21', 160, 300),
    new Position('22', 240, 300),
    new Position('23', 320, 300),
    new Position('24', 400, 300),
];

var computers = [];

var w = 450;
var h = 450;
var svge = d3.select("body")
            .append("svg")
            .attr("width", w)
            .attr("height", h);
var div = d3.select("body")
            .append("div")
            .attr("class", "tooltip")
            .style("opacity", 0)
            .text("Tooltip");

function makeComputers(jsony){
	for(var k=0; k<jsony.length; k++){
		computers.push(new Computer(k+1,jsony[k]['hostname']));
	}
	var recties = svge.selectAll("rect")
			.data(computers)
			.enter()
			.append("svg")
			.attr("data-hello", function (d,i) {return d[i]; })
			.append("rect")
			.attr("width", 30)
			.attr("height", 30)
			.attr("x", function(d,i) {var xloc = positions.findIndex(y => y.place == computers[i].place); return positions[xloc].posx +15})
			.attr("rx", 6)
			.attr("ry", 6)
			.attr("y", function(d,i) {var xloc = positions.findIndex(y => y.place == computers[i].place); return positions[xloc].posy +15})
			.style("fill", "Lavender")
			.on("mouseover", fadein)
			.on("mouseout", fadeout)
			.on("mousemove", moviemouse);
}
function fadein(d, i) {
    div.transition().duration(200).style("opacity", 0.9);
    div.style("left", d3.mouse(this)[0])
	.style("top", d3.mouse(this)[1])
	.html(d.hostname);
}
function fadeout(e) {
    div.transition().duration(400).style("opacity", 0);
}
function moviemouse(e) {
    div.style("left", d3.mouse(this)[0])
        .style("top", d3.mouse(this)[1]);
}

d3.json("query.php?room=w004", makeComputers);

Just to break this down in summary:

  • The makeComputers function starts off by adding all of the computers imported to the computers array based on their hostname
  • It then adds in rectangles, with the mouse listeners, using the better D3 method of adding SVGs to the page.
  • I’ve also used the findIndex function without needing a for loop, since D3 provides the index to be able to do this.
  • There are no longer any computers specified in the array of computers
  • There are now three functions associated with fading in/out and movement of the mouse (so the position of the tooltip box will change as the mouse moves)
  • Finally, this all happens when I call makeComputers at the end as the function that is executed once the request to the given URL has completed

The only last modification you may need to make, if your query.php file is stored on a different server to the one you are running your index.html and main.js from, is to add this to the top of your query.php file:

<?php 
 header("Access-Control-Allow-Origin: *");

This allows calls from other domains to be made, which is disabled by default for security reasons. However, if you’re confident that (for testing purposes at least) this won’t be an issue, then you can go ahead and enable to it, which lets you do things like run your index.html page and JS files from your local disk and make queries to the remote server.

This should have hopefully got you to a stage where you can make requests for JSON data to a web server and have it return some SQL results as readable data by JavaScript, but if anyone has any issues or encounters any oddities, do get in touch!

 

Ok so this is a bolt-on post to my previous post, where I am now essentially trying to figure out a nice way to map the data between the computers and their positions in a room. The way I’ve designed the system is that I have three tables with the relevant data:

  • Hosts: The main piece of data here is the hostname and other data – but it also contains the room that the host belongs to (and the “place”, which is just a notional number that I placed on a diagram)
  • Rooms: This is the list of rooms and their “layout type”. We could have ten different rooms each with the same layout, so this is a way to say which room has which layout type. Its sole purpose is to join the other two tables.
  • Places: The combination of a layout type and a position will give you an x and a y coordinate. This is a relative coordinate. It doesnt have to know which rooms are associated with it, its purely data on where – for a given layout type – a “position” would exist.

The idea for this is that you would fetch the respective coordinates for a host from the places table. The hosts table only has the place, so its necessary to fetch the correct layout type that matches the room, and then fetch the coordinates based on that data.

Complicated? A little. Especially for something that doesn’t seem too big. But I want it to be scalable and to separate out the data for positions from the hosts; a room may entirely change its layout but, so long as the room doesn’t change, the only thing I would need to change is the layout type and then the coordinates. However, I could have just placed all of this data into the hosts table – or removed the third table for places and just have mapped each room directly to sets of coordinates (and I still may!).

But I did find a solution, although it took a few iterations. I needed to refresh myself on inner and left joins a bit but my original plan was to do a left join between the hosts table and places table where the hosts’ room is a specific room. But then the places table works on layout type and noot a room number.

Ok so the first step would be something like this:

SELECT hostname, place, room, posx, posy
FROM hosts
INNER JOIN places ON places.position = hosts.place 

WHERE room = "W004"

Select the hostname from hosts, the coordinates from places, the room and the place from the inner join’d josts and places. The inner join will be done where the position field from places matches the place field from hosts. To limit it, I then just put “where room = w004”.

This would return a lot of results, since there’s many hosts potentially with the same place and many places with the same position. The “where” limitation narrows the hosts down to only the selection relevant to the one room, but it still leaves lots of entries potentially from the position, since there will likely be n times as many results as there are positions and layouts. That would then give duplicate host results, a duplicate for each recurring position that appears in the places table.

So the next step is to narrow this further, which was causing me a lot of headaches. I had an idea to expand the “WHERE” statement to include the room, but that isn’t a part of the dataset constructed by the joins. The solution was to do two joins – the second join being where the layout type matches the room specified in the room table and where the room matches in both the room and hosts table. This narrows both the hosts and rooms down to just one room, which means in turn there will only be one layout – and this further restricts the results from places down to what I need.

SELECT hostname, place, rooms.room, posx, posy 
FROM hosts 
INNER JOIN places ON places.position = hosts.place
INNER JOIN rooms ON rooms.room = hosts.room AND rooms.layoutType = places.layout 

WHERE rooms.room = "W004"

Note that I could have used left joins, which would preserve all the host data, but I am trying to narrow it down and there’s just no need for anything else. I should probably also tidy up some of the names a bit, especially since there is now ambiguity between the different “room”s, but not between place and position.

With that done, I can now replace my original SQL statement:

$getHosts = "SELECT * from hosts WHERE Room = '". $room ."'";

With the following:

$getHosts = "SELECT hostname, place, rooms.room, posx, posy FROM hosts INNER JOIN places ON hosts.place = places.position INNER JOIN rooms ON rooms.room = hosts.room AND rooms.layoutType = places.layout WHERE rooms.room = '". $room ."'";

A lot longer, but it returns a single dataset with all the position data we need for a given room!

Monday, December 5, 2022

Uploading data to a webserver – C/C++ and CURL

 this guide demonstrates how you can use a cURL library to upload data from an application written in C (or C++) to a webserver. Although at times I refer to previous examples, you can use this in any number of applications.

This will touch on a number of areas and require a little more prep work than previous examples, due to needing a few extra libraries and a server running PHP and SQL. In a nutshell, you have to cobble together a string with all your data in, send it along to your server with a request and then have that server do something with it. The steps covered are:

  1. (Setup) LAMP server and database
  2. (Client) Generate a URL string from variables in your program
  3. (Client) Send a web request to the URL generated in step 1
  4. (Server) Process the uploaded string to retrieve values from data
  5. (Server) Store the data as a new record in a database

Finally, there are some really quick and horrible things that I do here, with minimal catching of errors, but the purpose is to show the process from start to finish as briefly as possible!

Environment

First of all, its worth mentioning the set-up I am using for this system. Just to demonstrate, I have a LAMP server running on Ubuntu with a MySQL database. I won’t cover how to actually get as far as having those running because there’s plenty of articles that explain how to do that. The only other thing you may want to use is phpMyAdmin, as this makes database management via a web UI much easier than just command line interaction. It also helps hugely to understand basics of HTML/PHP – but you may well be able to stumble through this if you’ve never touched either before!

Setup

Database Table

The first thing to do is to create a new table to store the records in. This may as well just be a spreadsheet at this point, but later on I will be talking about queries and combining data from different tables. You can either create a new table with phpMyAdmin by selecting the appropriate settings from the code below or you can simply copy and paste the entire thing into an SQL query box and run it, which will create a new table for you.

CREATE TABLE inventory
(
hostName varchar(25) NOT NULL,
hostIP varchar(25),
hostMAC varchar(18),
hostCreateDate timestamp DEFAULT CURRENT_TIMESTAMP,
vlanID int(4),
switchPort varchar(32),
switchIP varchar(25),
switchName varchar(64),
switchPlatform varchar(64),
UNIQUE (hostName)
);

This single table consists of host information (name, mac, ip), switch information (name, platform, IP, port) and a date that the entry was created. Note that this also adds the constraint to the hostName to ensure that it is unique (and stipulates that it can’t be empty). There is a logical argument that, perhaps, it should be the MAC address that is unique; but I am quite happy with my domain and that it won’t give me hostname duplication for this demo. Plus, the focus is on getting data of different types in, rather than necessarily what the data is.

PHP page to process data

This is the part that is going to bring it all together later. When we want to place data into the database, we need something that will take data sent to the server and process that into the database. This file is going to store the database credentials, so its really important that it isn’t made available outside of your webserver. Since PHP files execute on the server, rather than the client, the file contents aren’t sent back when requested (unless the PHP service stops and breaks, in which case everything will be displayed in plaintext..). In fact, we really don’t have to – or necessarily want to – output anything with a PHP file; in this instance, it is simply used as a way to process data from clients to be stored in a database as follows:

The first thing to do is to specify some variables for the database details. These are used to connect to the database (on the same server, in this case):

<?php
	$servername = "localhost";
	$username = "user";
	$password = "Passw0rd";
	$dbname = "inventory";

//Rest of the code goes here

?>

 

The next thing to do is to create a connection object that handles the connection to a MySQL database, passing it the above variables as parameters:

$conn = new mysqli($servername, $username, $password, $dbname);

Before we can go any further, however, we need to actually have some data to process – or know what data we want to process. I’ll come back to this in a while, but first let’s generate the URL string.

Generate a URL string

At this point, I am making an assumption that you already have variables that you want to send. The key thing is to prepare that data to be sent to the server.

There are a number of ways in which you could send data with a web request; HTTP GET and POST being probably the most suited.

  • GET is easy; you can just construct a string consisting of the URL of the server to send the data too and then append all the extra data you want onto the end of it. However, you’re limited to 2048 characters, the URL is very “visible” (like any URL, the data included is going to pop up in search history, it can be cached) and so it is better used when trying to retrieve data (IE when you need to include some specific parameters as part of the request).
  • POST, on the other hand, hasn’t got the same limits on what – and how much – data you can include in the request. Possibly more crucially, however, is that the data isn’t sent along in the header of the request, so it isn’t as easily seen directly through the URL.

For good practice, I’m going to focus on using POST to upload the data. We’re using the request to send data, not simply retrieve it (which is where using GET would be appropriate). We can upload quite a bit of data this way, but it has to be formatted first. The string is pretty much the same for GET as it would be for POST; the difference is that the POST data will be sent separately to the header, in the body of the request, in the following format:

 

variable1=value&variable2=value&variable3=value

These are sets of variables and values that are accessible by the page requested; but the variable name must be used as it is on the page. If your local variable is myHostName, for example, but you send it as val1, then once it has reached the server, it has to be referred to by the name val1.

So how do you construct this string from many smaller strings?

There are a few ways to do this (str::append, for example). I’m using a mixture of C and C++ throughout these examples (which is probably a horrible thing to do in practice), but to break down how it works, I am going to manually stick the different strings together.

First of all, I’ve created a function called generatePOSTData() that will prepare the different parts of a string to be added together, consisting of 8 example variables. Note that each one has an equals operator next to it, which is used by the webserver:

void generatePOSTData()
{
	char *prefixhost = "host=";
	char *prefixip = "ip=";
	char *prefixmac = "mac=";
	char *prefixvlan = "vlan=";
	char *prefixswPort = "swPort=";
	char *prefixswName = "swName=";
	char *prefixswIP = "swIP=";
	char *prefixswMAC = "swMAC=";

	
	int slhost = strlen(prefixhost);
	int slip = strlen(prefixip);
	int slvlan = strlen(prefixvlan);
	int slmac = strlen(prefixmac);
	int slswPort = strlen(prefixswPort);
	int slswName = strlen(prefixswName);
	int slswIP = strlen(prefixswIP);
	int slswMAC = strlen(prefixswMAC);
...

Note that this also calculates the length of each of the strings we’ve just created. This is important when adding the strings together, later.

Next, we can take the strings and add them together, storing them in another variable. The function, addStrings, is something I have discussed in a previous post.

	addStrings(&host, prefixhost, systemhostname, slhost, slsystemhostname);
	addStrings(&ip, prefixip, systemip, slip, slsystemip);
	addStrings(&vlan, prefixvlan, systemvlan, slvlan, slsystemvlan);
	addStrings(&mac, prefixmac, systemmac, slmac, slsystemmac);

	addStrings(&swPort, prefixswPort, systemswitchport, slswPort, slsystemswitchport);
	addStrings(&swIP, prefixswIP, systemswIP, slswIP, slsystemswIP);
	addStrings(&swName, prefixswName, systemswName, slswName, slsystemswName);
	addStrings(&swMAC, prefixswMAC, systemswMAC, slswMAC, slsystemswMAC);
}

void addStrings(char ** result, const char * prefix, const char * body, int &prefixlength, int &bodylength)
{
	*result = (char *)malloc((prefixlength + bodylength + 1) * sizeof(char));
	memcpy(*result, prefix, prefixlength);
	memcpy(*result + prefixlength, body, bodylength);
	(*result)[prefixlength + bodylength] = '\0'; //Must be set like this as array notation takes precendence over a dereferencing
	prefixlength = strlen(*result);
}

Finally, we can start to construct a single string of all of these together.

requestString = (char *)malloc((slhost + slip + slswPort + slvlan + slmac + slswName + slswIP + slswMAC + 7 + 1) * sizeof(char));

This string is going to be long enough for each of the lengths of the above variables, plus one extra character for the ampersands to connect them. Plus a terminating character.

Now it is time to go through each string (I could use a recursive function here, but I’m keeping it long and simple here.. optimisation will come later!) and, bit by bit, copy the next piece of data over from individual variables over to requestString with an ampersand added after each set of values:

memcpy(requestString, host, slhost);
	requestString[slhost] = '&'; 

	memcpy(requestString+ slhost + 1,
		ip, slip);
	requestString[slhost + slip + 1] = '&';

	memcpy(requestString+ slhost + slip + 2,
		swPort, slswPort);
	requestString[slhost + slip + slswPort + 2] = '&';

	memcpy(requestString+ slhost + slip + slswPort + 3,
		vlan, slvlan);
	requestString[slhost + slip + slswPort + slvlan + 3] = '&';

	memcpy(requestString+ slhost + slip + slswPort + slvlan + 4,
		mac, slmac);
	requestString[slhost + slip + slswPort + slvlan + slmac + 4] = '&';

	memcpy(requestString+ slhost + slip + slswPort + slvlan + slmac + 5,
		swName, slswName);
	requestString[slhost + slip + slswPort + slvlan + slmac + slswName + 5] = '&';

	memcpy(requestString+ slhost + slip + slswPort + slvlan + slmac + slswName + 6,
		swIP, slswIP);
	requestString[slhost + slip + slswPort + slvlan + slmac + slswName + slswIP + 6] = '&';

	memcpy(requestString+ slhost + slip + slswPort + slvlan + slmac + slswName + slswIP + 7,
		swMAC, slswMAC);
	requestString[slhost + slip + slswPort + slvlan + slmac + slswName + slswIP + slswMAC + 7] = '\0';

This is kind of ridiculous, admittedly. I could just use the addStrings function from above, or as I said before, just functions in C++ to do this. However, it shows you how you really have to add strings together at a lower level than simply doing something at a higher level such as string1 += string2.

Send a web request with cURL to the webserver

Now that we have the post string, we can prepare it for sending with cURL; and it isn’t very hard. The first thing to do is to create a new curl object and then initialise it. If that works, you can use curl_setopt to set the URL on the object to your webpage (so http://www. your-site.com/ sendData.php) and the post string to send that was constructed above. Finally, you can initiate the request with curl_easy_perform and then do cleanup to remove the cURL object.

//Library available from https://curl.haxx.se/libcurl/
#define CURL_STATICLIB //You may or may not need this!
#include "curl/curl.h"
#pragma comment ( lib, "libcurl.lib" )

//...
//...

	CURL * curl;
	curl_global_init(CURL_GLOBAL_ALL);
	CURLcode res;
	curl = curl_easy_init();
	if (curl)
	{
		curl_easy_setopt(curl, CURLOPT_URL, address);
		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, requestString);			
		res = curl_easy_perform(curl);
		if (res != CURLE_OK)
		{
			fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
		}	
		curl_easy_cleanup(curl);
	}
	curl_global_cleanup();

Outside the scope of this document would be handling the response from the server, but it is important to provision for this later as this will allow you to know whether or not things worked on the client side!

Process the uploaded string

Now it is time to go back to where we were with the PHP page. We know what data is going to be sent now, so we know what will be received by the page.

Loading in the data

Although the string was all wrapped up with ampersands and formatted with a header, when the page receives the data, it is available as an associative array called $_POST. This means that you can access a value by using the name of it in the index (for example, $_POST[“host”] will return you the value of the hostname). The other thing about $_POST is that it is accessible from anywhere on your page, as it is a superglobal type. However, in this example, we only access it once to load in the values and process them into variables used throughout the page.

Where you declare the other variables in your code, so around $conn or the database details, add the following:

$host = mysql_escape_string($_POST['host']);
$ip = mysql_escape_string($_POST['ip']);
$swPort = mysql_escape_string($_POST['swPort']);
$swIP = mysql_escape_string($_POST['swIP']);
$swName = mysql_escape_string($_POST['swName']);
$swMAC = mysql_escape_string($_POST['swMAC']);
$vlan = mysql_escape_string($_POST['vlan']);
$mac = mysql_escape_string($_POST['mac']);

What this will do is quite important; it (hopefully) will prevent against SQL injection attacks by escaping the string. In other words, it stops code from being added to text that could unexpectedly end the string and roll over into executing the code that was submitted. However, see this post for a bit more information – and why you might not want to use it in the real world.

Additionally, we now can refer to the variables by their name prefixed with a dollar symbol, rather than having to use $POST[‘variableName’] for everything. (Interestingly there are some differences between single and double quotes in php.).

Validate the data

The next test we want to do on the incoming data is to make sure it is, in fact, populated and not simply. This can be done by simply checking through each variable and seeing if it is null or not. Note that anything echo’d will be returned to the requestor. If this was a browser, you would see the response on screen, but this application doesn’t have a way to handle this, yet.

if ($host == NULL)
	{
		echo "Error, host is empty! ";	
	}

There is also one more thing that I want to add in; a test to see if the host is valid or not. If there is junk data and there are blank entries, for fields that must not be empty, this can create problems for us. If we specify an invalidation flag, that can be set if one of the variables is empty, then we can stop any database processing from happening.

$testInValid = 0;

if ($host == NULL)
	{
		echo "Error, host is empty! ";
		$testInValid = 1;
	}

if ($ip == NULL)
	{
		echo "Error, ip is empty! ";		
		$testInValid = 1;
	}

if ($swPort == NULL)
	{
		echo "Error, swPort is empty! ";
		$testInValid = 1;
	}

if ($vlan == NULL)
	{
		echo "Error, vlan is empty! ";		
		$testInValid = 1;
	}

if ($mac == NULL)
	{
		echo "Error, mac is empty! ";		
		$testInValid = 1;
	}

if ($swName == NULL)
	{
		echo "Error, swName is empty! ";		
		//$testInValid = 1;
	}

if ($swMAC == NULL)
	{
		echo "Error, swMAC is empty! ";		
		//$testInValid = 1;

	}

if ($swIP == NULL)
	{
		echo "Error, swIP is empty! ";
		//$testInValid = 1;
	}

if (testInValid == 0)
{

We begin by declaring a variable called testInValid which, by default, will remain as 0 so long as all the key pieces of data exist. At the bottom, we continue the program so long as testInValid remains as 0. It is a quick and dirty trick, but it ensures that we don’t execute code if it could break anything in the database.

Store the data as a new record in a database

With the data validated and in the form, it is time to add it to the database. Be sure that you have you added the following line to create the connection object:

$conn = new mysqli($servername, $username, $password, $dbname);

The first thing to do is to see if we can connect to it. This will also catch the error if it can’t, but you don’t have to populate it yet:

if ($conn->connect_error)
{
}

Instead, we can now create an SQL Select statement, which will be run as a query by the object.

$sqlSelect = "SELECT * from hosts WHERE hostName = '$host'";

What this will do, is to return records for any hosts that exist with the hostname that we have specified in the request. Note that, although the query is in double quotation marks, the hostname is within single quotes. This allows us to use the $host variable from the page, expanded to its actual value. Another way to do that would be to use a full stop to join two strings together, with the second string being $host and the first string being the SQL statement up until the end of the equals sign, as so:

$sqlSelect = "SELECT * from hosts WHERE hostName = " . $host;

Back to the statements: we need another one. This one will be the SQL Insert statement, used to add an entry into the database.

$sqlInsert = "INSERT INTO hosts (hostName, hostIP, switchPort, switchIP, switchName, switchPlatform, vlanID, hostMac)		

				VALUES ('$host', '$ip', '$swPort', '$swIP','$swName','$swMAC','$vlan','$mac')";

 

Finally, we run the queries. First we check to see if there is already a record with that hostname which – if there is – means that we do nothing other than report that to be the case. However, if there are no results for the query, this means that there are no hosts with that name and we can now run the second query. If that returns true – in other words, there were no errors – then we can assume it worked and output that to be the case.

$result = $conn->query($sqlSelect);

if ($result->num_rows > 0)
{
 echo "Host already exists!"
}

else
if ($conn->query($sqlInsert) === TRUE) 
{
	echo "Success!";
}

Viewing the results

You can now check this by making a new page with the same database connection details that fetches and outputs the data to a webpage. Below is a commented example of something that you can use to view the results into a table. Note that anything echo’d will be output as HTML – you can view how the code is generated in the browser by right clicking the screen (in most browsers) and clicking “View Source” when you run this:

<?php

//Connection strings
	$servername = "myserver";
	$username = "user1";
	$password = "p@ssw0rd";
	$dbname = "database1";
	$tableName = "inventory";

//Connection object
	$conn = new mysqli($servername, $username, $password, $dbname);

//Check to see if the connection succeeds
	if ($conn->connect_error) {
		die("Connection failed: " . $conn->connect_error);
	}


//SQL statement to select various fields from the table. Note that you could say, instead, "SELECT * FROM" . $tableName;	
	$sql = "SELECT hostName, hostIP, hostMac, hostCreateDate, vlanID, switchPort, switchIP FROM ". $tableName ;

//Run the query and store the results in an array
	$result = $conn->query($sql);

	if ($result->num_rows > 0) 
	{	
//This will spit out a table with some headers on the first row
		echo "<table><tr><th>Hostname</th><th>IP Address</th><th>MAC</th><th>Switch IP</th><th>Switchport ID</th><th>VLAN ID</th><th>Created On</th></tr>";

//Here we just create a new table row with the results displayed in each column
		while($row = $result->fetch_assoc()) 
		{
			  echo "<tr><td>".
			  $row["hostName"]."</td><td>". 
			  $row["hostIP"]."</td><td>". 
			  $row["hostMac"]."</td><td>". 
			  $row["switchIP"]."</td><td>" .
			  $row["switchPort"]."</td><td>" .
			  $row["vlanID"]."</td><td>".
			  $row["hostCreateDate"]."</tr>"; 
		}
	}

//Just in case we have nothing!
	else 
	{
		echo "0 results";
	}

$conn->close();

?>

 

That concludes a very hacky – but functional – guide to how to get data from an application written in C/C++ into a database and displayed on a webpage using cURL. Please do post comments, issues and questions in reply!

 

I demonstrated an example of uploading data to a webserver with cURL. The thing that bugged me was, however, that this used external libraries which can be annoying to deploy with. One option is to statically link the cURL libraries, which will make it huge, but I decided to go native in Windows and give Winsock a go.

Working from the example in Part 1, one of the first things I decided to do was to remove any reference to cURL. Actually, the only place you need to really do this in the previous example would be in the function where you actually upload the string (and, of course, any library or header reference). With that out of the way, we now need to make a socket (which, on Windows, you should only need to use winsock.h and Ws2_32.lib to achieve).

WSADATA wsaData; 
if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0)
{
	logfileOutput("WSAStartup failed.");
	exit(1);
}

This first snippet is needed to initialise Winsock (you might want to also add a way to capture the exit code, too). Once that’s done, I’ve gone and replaced the old cURL code with some Winsock code:

struct addrinfo hints;
	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_INET;          // IPv4
	hints.ai_protocol = IPPROTO_TCP;    // TCP
	hints.ai_socktype = SOCK_STREAM;    

	struct addrinfo* targetAdressInfo = NULL;
	DWORD getAddrRes = getaddrinfo(address, NULL, &hints, &targetAdressInfo);

	if (getAddrRes != 0 || targetAdressInfo == NULL)
	{
		logfileOutput("Could not resolve the hostname.");
	}

	SOCKADDR_IN sockAddr;
	sockAddr.sin_addr = ((struct sockaddr_in*) targetAdressInfo->ai_addr)->sin_addr;    
	sockAddr.sin_family = AF_INET;  // IPv4
	sockAddr.sin_port = htons(80);  // HTTP Port: 80
								
	freeaddrinfo(targetAdressInfo);

	SOCKET webSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (webSocket == INVALID_SOCKET)
	{
		logfileOutput("Creation of the socket failed!");
	}

	printf("\nConnecting... ");
	if (connect(webSocket, (SOCKADDR*)&sockAddr, sizeof(sockAddr)) != 0)
	{
		logfileOutput("Could not connect");
		closesocket(webSocket);
	}
	printf("Connected");


	// Here's where we send the data
	const char* httpRequest = postdata;
	int sentBytes = send(webSocket, httpRequest, strlen(httpRequest), 0);
	if (sentBytes < strlen(httpRequest) || sentBytes == SOCKET_ERROR)
	{
		logfileOutput("Could not send the request to the server");
		closesocket(webSocket);
	}

 

Because it is an incredibly quick and dirty example, I won’t explain it too much. I would actually prefer using something like cURL, which has already been done, but the basic idea is that you set up a socket, connect to the webserver and then send the data. Notice that there is no provision for receiving or interpreting responses, so this is really unhelpful for debugging. But the point is to illustrate that you can send data using what is already provided by Microsoft in the Winsock library.

The two piece of data – httprequest (and data) and address – represent the webserver URL (e.g. http://10.0.0.1/) and the request data itself. This data is not simply the string that we had previously; we need to make another string that wraps around the string of values to send. To do that, I’ve made the following code, which creates a string in the format that is expected for the data to be received in:

	sprintf_s(postdata,
		"POST %s HTTP/1.1\r\n"
		"Host: %s\r\n"
		"Content-Type: application/x-www-form-urlencoded\r\n"
		"Content-Length: %i\r\n\r\n"
		"%s\r\n", settingsPage, settingsServer, strlen(requestString), requestString);

To break it down a little:

  • The first part of this, POST %s, tells the page defined by settingsPage that this is POST data. The page should be the one that you created earlier; so this could be senddata.php or something.
  • It is important for the content-type field to report that it is application/x-www-form-urlencoded and not text/html as I originally had tried to do. It just won’t send the data in the body as you want it to, otherwise!
  • Content-length has to be correct – but that is simply the length of the requestString.
  • Finally, after two carriage returns (and I think that this has to be the case), you include the data you want to include (in other words, the payload of requestString) that was generated in the previous example.

You can test what it comes out as with a simple printf of postdata, which should show you what the server will see. This is then sent to the server in the socket code above.

Again, this is a very quick and dirty way to get a socket to send the same data as in the last example and is really just to show that you can do it. There is very little error catching, format checking and it doesn’t capture a response. But with a bit of work, it can eliminate a lot of un-necessary code that more cumbersome (cURL) libraries might include if all you’re looking to do is to create the bare minimum for a small footprint binary executable.