Monthly Archives: May 2018

.NET Rangers

I am proud to announce that the .NET Rangers community is back ! We have 3 MVP !

We have a blog : http://www.dotnetrangers.org and a Twitter account:

.NET Rangers @RangersNet

Members of the community:

  • Jean-Baptiste B. Sogeti
  • Cdric G. Sogeti
  • Jean-Nicolas B. Sogeti
  • Guilhem M. C-S
  • Cdric T. Sogeti
  • Keelan C. Sogeti
  • Eva D. Neos-SDI
  • Frederic S. Sydev
  • Michel F. Indep
  • Jean-Nol G. Indep
  • Christophe P. Neos-SDI

Article in Technical Magazine Programmez N° 219 – June 2018

May be in next June edition of french magazine Programmez, an article about creating a Windows Service. All the plumbing ! The second part will explain how to host a deamon inside and respond to external communication.

Creating the Ping feature for a middleware

For my clustering master/worker nodes, I have put a Ping thread:

To be production code, I will protect the vector _nodes. It’s a multi-thread appz so… it needs to be done.

void MyServer::ActivatePingThread()
{
	DWORD dwThreadId = 0;
	HANDLE hThread = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) &MyServer::PingThread, NULL, 0, &dwThreadId);
}

void MyServer::PingThread(LPVOID param)
{
	while (TRUE)
	{

		::Sleep(2000);

		std::shared_ptr<NodeAttributes> pObj = nullptr;

		for (auto itr = _nodes.begin(); itr != _nodes.end(); itr++)
		{
			pObj = *itr;

			TCHAR sz[255];
			_stprintf(sz, _T("http://%s:%s/MyServer/LMDB/"), pObj->_server.c_str(), pObj->_port.c_str());

			std::wstring address = sz;

			http_client client(address);

			std::wostringstream buf;
			buf << _T("?request=") << _T("ping");

			http_response response;

			try
			{
				wcout << _T("Ping testing ip:") << pObj->_server.c_str() << _T(" on port:") << pObj->_port.c_str() << endl;
				response = client.request(methods::GET, buf.str()).get();
				wcout << response.to_string() << endl;
			}
			catch (...)
			{
				// Something goes wrong !
				pObj->_isActive = false;
				*itr = pObj;
			}

			json::value jdata = json::value::array();
			jdata = response.extract_json().get();

			if (jdata.is_null())
			{
				std::wcout << _T("no JSON data...") << std::endl;

				// Something goes wrong !
				pObj->_isActive = false;
				*itr = pObj;
				continue;
			}

			PingData data = PingData::FromJSON(jdata.as_object());

			if (data.status == _T("OK"))
			{
				// Normal behaviour
			}
			else
			{
				// Something goes wrong !
				pObj->_isActive = false;
				*itr = pObj;
			}

			::Sleep(1000);
		}
	}
}

Creating a cluster of nodes in C++ REST

Creating a cluster of nodes…

I have a server appz that can be forked either to start nodes or the main server controller node.

When a node starts, its register with the node controller (master) giving its ip, its port and a name.

The node controller keeps that information in a std::vector<T>.

There is also an admin tool to send commands and see what’s in the master node.

start myserver

start myserver 192.168.1.33 7002 node node_1

start myserver 192.168.1.33 7003 node node_2

start myserver 192.168.1.33 7004 node node_3

start myclient 192.168.1.33 7001

The first myserver instance is the controller node (master). All others instances are worker nodes on a dedicated ip/port.

They all register with master. Master node will allow a node to be active when a client request for a node…

An admin tool allow to known when it happened:

F:DevGitHubLMDBLMDBWindowsx64Debug>admin 192.168.1.33 7001

Client http://192.168.1.33:7001/MyServer/LMDB/

Enter a command:?request=show-nodes

HTTP/1.1 200 OK

Content-Length: 0

Date: Sat, 19 May 2018 15:25:52 GMT

Server: Microsoft-HTTPAPI/2.0

Message GET /MyServer/LMDB/?request=show-nodes HTTP/1.1

Connection: Keep-Alive

Host: 192.168.1.33:7001

User-Agent: cpprestsdk/2.10.2

Relative URI /?request=show-nodes

Query request show-nodes

Request show-nodes

show-nodes…

Active:0 Server:192.168.1.33 Port:7002 Name:node_1

Active:0 Server:192.168.1.33 Port:7003 Name:node_2

Active:0 Server:192.168.1.33 Port:7004 Name:node_3

As we can see, there are registered nodes but no active one yet.

If I start a client asking for a worker node: myclient 192.168.1.33 7001

The server reacts:

Relative URI /?request=get-node&name=cache_v1

Query name cache_v1

Query request get-node

Request get-node

name cache_v1

get-node…

{"ip":"192.168.1.33","name":"cache_v1","port":"7002"}

If I ask my admin tool to show nodes:

Relative URI /?request=show-nodes

Query request show-nodes

Request show-nodes

show-nodes…

Active:1 Server:192.168.1.33 Port:7002 Name:cache_v1

Active:0 Server:192.168.1.33 Port:7003 Name:node_2

Active:0 Server:192.168.1.33 Port:7004 Name:node_3

There is one active node. The name is submitted by the client for an LMDB database names cache_v1.

.NET Ranger – The community is reborn !

I am proud to announce that the .Net Rangers are back !

The web site www.dotnetrangers.org is live.

We are a community of Windows Experts in development and infrastructure. Stay tuned !

Give the extra class for HTTP Server !

Here is the code :

 

#pragma once

class LMDBData
{
public:
	LMDBData();
	virtual ~LMDBData();

public:
	MDB_env * m_env;
	MDB_dbi m_dbi;
	MDB_txn * m_txn;
	MDB_val m_key;
	MDB_val m_data;
	MDB_stat m_mst;
};

#include "StdAfx.h"
#include "LMDBData.h"

LMDBData::LMDBData()
{
}

LMDBData::~LMDBData()
{
}

Give me the main of the HTTP Server

Here is the code :

#include "stdafx.h"
#include "MyServer.h"

using namespace web;
using namespace http;
using namespace utility;
using namespace http::experimental::listener;

std::unique_ptr<MyServer> g_http;


std::wstring GetIP()
{
	USES_CONVERSION;

	// Init WinSock
	WSADATA wsa_Data;
	int wsa_ReturnCode = WSAStartup(0x101, &wsa_Data);

	// Get the local hostname
	char szHostName[255];
	gethostname(szHostName, 255);
	struct hostent *host_entry;
	host_entry = gethostbyname(szHostName);
	char * szLocalIP;
	szLocalIP = inet_ntoa(*(struct in_addr *)*host_entry->h_addr_list);
	WSACleanup();

	std::wstring ip = A2W(szLocalIP);
	return ip;
}

int wmain(int argc, wchar_t *argv[])
{
    utility::string_t port = U("7001");
	std::wstring defaultAddress = _T("localhost");
	if(argc == 2)
    {
        port = argv[1];
    }
	if (argc == 3)
	{
		defaultAddress = argv[1];
		port = argv[2];
	}

	std::wstring ip = GetIP();
	std::wcout << L"IP : " << ip << std::endl;

	std::wstring address = _T("http://");
	address.append(ip); // defaultAddress); //ip
	address.append(_T(":"));
	address.append(port);
	address.append(_T("/MyServer/LMDB/"));
	http::uri uri = http::uri(address);
	auto addr = uri.to_string();

	//
	// Create the server instance
	//

	std::wcout << L"Server " << addr << std::endl;

	g_http = std::unique_ptr<MyServer>(new MyServer(addr));
	g_http->Init_LMDB();
	g_http->open().wait();

	ucout << utility::string_t(U("Listening for requests at: ")) << addr << std::endl;
	std::cout << "Press ENTER to exit." << std::endl;
    std::string line;
    std::getline(std::cin, line);

	g_http->close().wait();
	return 0;
}

Give me the body of the HTTP Server !

Look at the code:

#include "stdafx.h"
#include "MyServer\\messagetypes.h"
#include "MyServer.h"

using namespace std;
using namespace web; 
using namespace utility;
using namespace http;
using namespace web::http::experimental::listener;

LMDBData MyServer::m_lmdb;

MyServer::MyServer(utility::string_t url) : m_listener(url)
{
	std::function<void(http_request)> fnGet = &MyServer::handle_get;
	m_listener.support(methods::GET, fnGet);

	std::function<void(http_request)> fnPut = &MyServer::handle_put;
	m_listener.support(methods::PUT, fnPut);

	std::function<void(http_request)> fnPost = &MyServer::handle_post;
    m_listener.support(methods::POST, fnPost);

	std::function<void(http_request)> fnDel = &MyServer::handle_delete;
    m_listener.support(methods::DEL, fnDel);
   
}

void MyServer::Init_LMDB()
{
	mdb_env_create(&m_lmdb.m_env);
	mdb_env_set_maxreaders(m_lmdb.m_env, 1);
	mdb_env_set_mapsize(m_lmdb.m_env, 10485760);
	mdb_env_open(m_lmdb.m_env, "c:\\temp", MDB_CREATE/*|MDB_NOSYNC*/, 0664);
}

void MyServer::handle_post(http_request message)
{
    ucout <<  message.to_string() << endl;
	message.reply(status_codes::OK);
};

void MyServer::handle_delete(http_request message)
{
	ucout << message.to_string() << endl;
	message.reply(status_codes::OK);
}

void MyServer::handle_put(http_request message)
{
	ucout << message.to_string() << endl;
	message.reply(status_codes::OK);
};

void MyServer::handle_get(http_request message)
{
	ucout << U("Message") << U(" ") << message.to_string() << endl;
	ucout << U("Relative URI") << U(" ") << message.relative_uri().to_string() << endl;

	auto paths = uri::split_path(uri::decode(message.relative_uri().path()));
	for (auto it1 = paths.begin(); it1 != paths.end(); it1++)
	{
		ucout << U("Path") << U(" ") << *it1 << endl;
	}

	auto query = uri::split_query(uri::decode(message.relative_uri().query()));
	for (auto it2 = query.begin(); it2 != query.end(); it2++)
	{
		ucout << U("Query") << U(" ") << it2->first << U(" ") << it2->second << endl;
	}

	auto queryItr = query.find(U("request"));
	utility::string_t request = queryItr->second;
	ucout << U("Request") << U(" ") << request << endl;

	auto keyItr = query.find(U("key"));
	utility::string_t key; 
	if (keyItr != query.end())
	{
		key = keyItr->second;
		ucout << U("key") << U(" ") << key << endl;
	}

	auto valueItr = query.find(U("value"));
	utility::string_t value;
	if (valueItr != query.end())
	{
		value = valueItr->second;
		ucout << U("value") << U(" ") << value << endl;
	}

	if (request == U("get-data"))
	{
		TCHAR szKey[255];
		TCHAR szValue[255];

		MDB_val VKey;
		MDB_val VData;

		_tcscpy(szKey, key.c_str());

		VKey.mv_size = sizeof(szKey);
		VKey.mv_data = szKey;
		VData.mv_size = sizeof(szValue);
		VData.mv_data = szValue;

		mdb_txn_begin(m_lmdb.m_env, NULL, 0, &m_lmdb.m_txn);
		mdb_dbi_open(m_lmdb.m_txn, NULL, 0, &m_lmdb.m_dbi); 
		int err = mdb_get(m_lmdb.m_txn, m_lmdb.m_dbi, &VKey, &VData);
		mdb_txn_commit(m_lmdb.m_txn);
		mdb_env_stat(m_lmdb.m_env, &m_lmdb.m_mst);

		if (err == MDB_NOTFOUND)
		{
			Data data;
			data.key = szKey;
			data.value = U("");

			utility::string_t response = data.AsJSON().serialize();
			ucout << response << endl;

			message.reply(status_codes::OK, data.AsJSON());
		}
		else
		{
			Data data;
			data.key = szKey;
			data.value = (TCHAR *)VData.mv_data;

			utility::string_t response = data.AsJSON().serialize();
			ucout << response << endl;

			message.reply(status_codes::OK, data.AsJSON());
		}
		return;
	}

	if (request == U("set-data"))
	{
		TCHAR szKey[255];
		TCHAR szValue[255];

		MDB_val VKey;
		MDB_val VData;

		_tcscpy(szKey, key.c_str());
		_tcscpy(szValue, value.c_str());

		VKey.mv_size = sizeof(szKey);
		VKey.mv_data = szKey;
  		VData.mv_size = sizeof(szValue);
		VData.mv_data = szValue;
		_tprintf(_T("Add Key:%s Data:%s\n"), szKey, szValue);
		mdb_txn_begin(m_lmdb.m_env, NULL, 0, &m_lmdb.m_txn);
		mdb_dbi_open(m_lmdb.m_txn, NULL, 0, &m_lmdb.m_dbi);
		mdb_put(m_lmdb.m_txn, m_lmdb.m_dbi, &VKey, &VData, MDB_NOOVERWRITE);
		mdb_txn_commit(m_lmdb.m_txn);
		mdb_env_stat(m_lmdb.m_env, &m_lmdb.m_mst);

		Data data;
		data.key = szKey;
		data.value = szValue;

		utility::string_t response = data.AsJSON().serialize();
		ucout << response << endl;

		message.reply(status_codes::OK, data.AsJSON());
		return;
	}

	message.reply(status_codes::OK);
};

Write a simple HTTP server using C++ REST SDK

Here is the code: who said C++ is difficult ?

#pragma once

using namespace web;
using namespace http;
using namespace utility;
using namespace http::experimental::listener;

#include "LMDBData.h"

class MyServer
{
public:
	MyServer() {}
	MyServer(utility::string_t url);

	void Init_LMDB();

	pplx::task<void> open() { return m_listener.open(); }
	pplx::task<void> close() { return m_listener.close(); }

private:

	static void handle_get(http_request message);
	static void handle_put(http_request message);
	static void handle_post(http_request message);
	static void handle_delete(http_request message);

	http_listener m_listener;
	static LMDBData m_lmdb;
};

 

MVP Totem

Here is the totem for my MVP award. It was a lot of work. It’s also a lot of work to continue. Stay tuned.

Create a Windows Service with C++

It’s written, done. There are about 300 lines of code but it’s technical stuff. I hope that the Chief Editor let me write the second part. It’s about how to communicate between a Service and another source code using XML-RPC.

Next Article for Programmez Magazine

The next article I prepare for the June edition of Programmez Magazine will be about Windows Service:

  • How to create a service and register it in the registry.
  • How to associate an event log entry with a service
  • How to log DEBUG or INFO informations with log4cpp
  • How to debug a service
  • Tips & tricks

MVP

Today, I received the Microsoft MVP Award. I am proud. A lot of work. Stay tuned. :)