Internet if Things: How to create a simple Web server (part 2)

Last time I promised to show how to create a simple Web server on Galileo board. Since we have Windows on Galileo there can be several ways to create a simple web server but, finally, I found just one “stable” way to do it – sockets (WinSock 2.2 library). Additionally, I found one more way which doesn’t require development at all but first things first.

Since I am a .NET developer, I started my research with some libraries, which could help me to avoid sockets and not require using the Win32 API a lot. According to the samples presented by Microsoft, there is a way to use C++ Rest SDK and node.js. But both of them don’t have official support of Galileo boards. Windows image for Galileo contains subset of desktop Win32 API, so, there is no guarantee that these SDKs work fine or will work fine in future releases.

For example, if you are going to use C++ Rest SDK, you should download version 2.2 but current version 2.4 will not work. Version 2.2 uses WinHTTP API in some classes, which is not supported in Galileo image as well. That’s why I didn’t have a chance to use http_listener there.

The same situation with node.js. I did all recommended things with user32.dll but the latest version of node.js simply didn’t work. If I have some time I will try to find the solution of the problem there but I needed to find a simple way to launch Web server without spending much time.

That’s why I decided to use Winsock API which is a common part of all modern Windows images. But before I show my Web server, I want to discuss one more thing – httpsrv.exe.

If you had a chance to use the Galileo Watcher, you saw several menu items in context menu there and one of these items was “Web Browser Here”:

 

This menu allows you to launch a browser, which opens a web page on th Galileo board with several links there. It uses an IP address of the board and port 80 there. So, it’s easy to understand that Galileo contains a web server by default. If you launch Telnet and run tlist command you will find that there is httpsrv.exe process.

 

This process is a simple web server, which is part of Windows mini image. It’s not easy to find something about this server but I found that it maps c:\Windows\System32\Web folder as a Web server directory. In fact there is a simple page and several applications which are initiated by the page:

 

So, I simply changed a page and added a link to my own application, which contains just one line of code:

  #include <iostream>
 
 int _tmain(int argc, _TCHAR* argv[])
 {
 std::cout << "<body>Hello from Web <a href=https://microsoft.com>Microsoft</a></body>";
 return 0;
 }
 

Thanks to httpsrv I got a chance to run my own application and use output stream as a source for outgoing web page. This approach is very simple. In fact you can create two applications: the first one implements logic of your project and use some local files for data storage, the second one should read stored data and generate output stream for web page. Thanks to that you should not think about your own implementation of a web server at all. It was so easy that I lost all desire to use sockets but I had promised it last time. So, if you finally decided to implement your own server I will show the socket approach as well. Pay special attention that httpsrv uses port 80, so, if you want to use your own server using the port, you need to shutdown httpsvr or start httpsrv using other port.

In order to use Winsock API I decided to use existing code with some modifications. MSDN Samples allowed only one request and tried to make something with this request. In case of Web server, you need to allow many requests, so I used a simple while operator there. Additionally, I changed all printf to Log function.

 #include "arduino.h"
#undef UNICODE

#define WIN32_LEAN_AND_MEAN
 
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
 
#pragma comment (lib, "Ws2_32.lib")
 
#define DEFAULT_BUFLEN 1024
#define DEFAULT_PORT "8080"
 
int __cdecl main(void)
{
 WSADATA wsaData;
 int iResult;
 
 SOCKET ListenSocket = INVALID_SOCKET;
 SOCKET ClientSocket = INVALID_SOCKET;
 
 struct addrinfo *result = NULL;
 struct addrinfo hints;
 
 int iSendResult;
 char recvbuf[DEFAULT_BUFLEN];
 int recvbuflen = DEFAULT_BUFLEN;
 
 iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
 if (iResult != 0) {
 Log("WSAStartup failed with error: %d\n", iResult);
 return 1;
 }
 
 ZeroMemory(&hints, sizeof(hints));
 hints.ai_family = AF_INET;
 hints.ai_socktype = SOCK_STREAM;
 hints.ai_protocol = IPPROTO_TCP;
 hints.ai_flags = AI_PASSIVE;
 
 iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
 if (iResult != 0) {
 Log("getaddrinfo failed with error: %d\n", iResult);
 WSACleanup();
 return 1;
 }
 
 ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
 if (ListenSocket == INVALID_SOCKET) {
 Log("socket failed with error: %ld\n", WSAGetLastError());
 freeaddrinfo(result);
 WSACleanup();
 return 1;
 }
 
 iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
 if (iResult == SOCKET_ERROR) {
 Log("bind failed with error: %d\n", WSAGetLastError());
 freeaddrinfo(result);
 closesocket(ListenSocket);
 WSACleanup();
 return 1;
 }
 
 freeaddrinfo(result);
 
 while (true)
 {
 iResult = listen(ListenSocket, SOMAXCONN);
 if (iResult == SOCKET_ERROR) {
 Log("listen failed with error: %d\n", WSAGetLastError());
 closesocket(ListenSocket);
 }
 
 ClientSocket = accept(ListenSocket, NULL, NULL);
 if (ClientSocket == INVALID_SOCKET) {
 Log("accept failed with error: %d\n", WSAGetLastError());
 closesocket(ListenSocket);
 }
 
 iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
 if (iResult > 0)
 {
 Log("Bytes received: %d\n", iResult);
 
 char ret[] = "Hello World\0";
 iSendResult = send(ClientSocket, ret, sizeof(ret), 0);
 if (iSendResult == SOCKET_ERROR) 
 {
 Log("send failed with error: %d\n", WSAGetLastError());
 }
 Log("Bytes sent: %d\n", iSendResult);
 }
 else if (iResult == 0)
 Log("Connection closing...\n");
 else 
 {
 Log("recv failed with error: %d\n", WSAGetLastError());
 }
 
 iResult = shutdown(ClientSocket, SD_SEND);
 if (iResult == SOCKET_ERROR) {
 Log("shutdown failed with error: %d\n", WSAGetLastError());
 closesocket(ClientSocket);
 }
 
 closesocket(ClientSocket);
 }
 
 return 0;
}
 

This code is not ideal because it accepts all requests, doesn’t generate the right response (like HTTP/1.0 200 OK…..) and it doesn’t support multithreading. But it works, it doesn’t depend on any third-party library, doesn’t require dancing with a tambourine and you can use it as a starting point for your server.