Win32 API programming with C - HTTP Server API

Ciprian FusaCiprian Fusa
5 min read

In the realm of network programming on Windows, the Win32 HTTP Server API offers a robust, low-level foundation for handling HTTP requests directly within a C application. This API, part of the Windows HTTP Services (WinHTTP), provides developers with the tools needed to create HTTP servers that can manage requests, send responses, and handle web traffic without the need for third-party libraries. This article explores how to use the Win32 HTTP Server API with the C programming language to create a simple HTTP server.

Understanding the Win32 HTTP Server API

The Win32 HTTP Server API is designed to be a high-performance server-side Web protocol listener. It's suitable for applications that need to serve HTTP content directly, such as web services, or for scenarios where embedding a lightweight web server into an application is necessary.

The API provides a set of functions that allow applications to:

  • Create and manage HTTP server sessions.

  • Configure URL namespaces and listen for requests on specific URLs.

  • Receive requests and send responses, including headers and body data.

  • Handle SSL/TLS encryption for secure communication.

Setting Up Your Environment

Before diving into code, ensure your development environment is set up correctly. You will need:

  • A Windows development environment.

  • A C compiler, such as the one provided with Visual Studio.

  • The Windows SDK, which includes the headers and libraries for the Win32 HTTP Server API.

Creating a Simple HTTP Server

To illustrate how to use the Win32 HTTP Server API, let's create a simple HTTP server that listens for requests on a specific URL and responds with a basic message. The following steps outline the process:

1. Initialize the HTTP Service

First, initialize the HTTP service using the HttpInitialize function. This function prepares the application to use the HTTP Server API.

#include <windows.h>
#include <http.h>

#pragma comment(lib, "httpapi.lib")

ULONG result;
HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_1;

result = HttpInitialize(HttpApiVersion, HTTP_INITIALIZE_SERVER, NULL);
if (result != NO_ERROR) {
    return 1; // Initialization failed
}

2. Create an HTTP Server Session

Next, create a server session and configure it to listen for HTTP requests. Use HttpCreateHttpHandle to create a request queue handle.

HANDLE hReqQueue = NULL;
result = HttpCreateHttpHandle(&hReqQueue, 0);
if (result != NO_ERROR) {
    HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);
    return 1; // Failed to create request queue
}

3. Add a URL to the Request Queue

Before the server can receive requests, you must add at least one URL to the request queue. This is done using the HttpAddUrl function.

PCWSTR urlPrefix = L"http://localhost:8080/";
result = HttpAddUrl(hReqQueue, urlPrefix, NULL);
if (result != NO_ERROR) {
    HttpCloseRequestQueue(hReqQueue);
    HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);
    return 1; // Failed to add URL
}

4. Receive Requests and Send Responses

The server is now ready to receive HTTP requests. Use a loop to continuously call HttpReceiveHttpRequest, process each request, and then use HttpSendHttpResponse to send a response.

HTTP_REQUEST_ID reqId = 0;
HTTP_REQUEST* pRequest;
ULONG reqBufSize = sizeof(HTTP_REQUEST) + 2048; // Allocate a 2 KB buffer
pRequest = (HTTP_REQUEST*)malloc(reqBufSize);
DWORD bytesRead;

while (TRUE) {
    ZeroMemory(pRequest, reqBufSize);
    result = HttpReceiveHttpRequest(
        hReqQueue,    // Req Queue
        reqId,        // Req ID
        0,            // Flags
        pRequest,     // HTTP request buffer
        reqBufSize,   // req buffer length
        &bytesRead,   // bytes received
        NULL          // LPOVERLAPPED
    );

    if (result == NO_ERROR) {
        switch (pRequest->Verb)
        {
            case HttpVerbGET:
                wprintf(L"Got a GET request for %ws \n", pRequest->CookedUrl.pFullUrl);
                result = SendHttpResponse(hReqQueue, pRequest);
                break;
            case HttpVerbPOST:
                wprintf(L"Got a POST request for %ws \n", pRequest->CookedUrl.pFullUrl);
                break;
            default:
                break;
        }
    }
}

5. Clean Up

Finally, remember to clean up resources by removing URLs from the request queue, closing the queue, and terminating the HTTP service.

HttpRemoveUrl(hReqQueue, urlPrefix);
HttpCloseRequestQueue(hReqQueue);
HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);

Conclusion

The Win32 HTTP Server API provides a powerful set of functions for creating HTTP servers in C. While the example provided here is very basic, it illustrates the core steps needed to receive HTTP requests and respond to them. The API's flexibility allows developers to handle more complex scenarios, including serving files, handling POST requests, and integrating with existing web services. With its direct access to the HTTP protocol and integration with Windows security models, the Win32 HTTP Server API is a valuable tool for C developers looking to implement web server functionality in their applications.

Full code below:

#ifndef UNICODE
#define UNICODE
#endif

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <windows.h>
#include <http.h>
#include <stdio.h>

#pragma comment(lib, "httpapi.lib")

ULONG result;
HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_1;

// function declarations
DWORD SendHttpResponse(HANDLE hReqQueue, HTTP_REQUEST* pRequest);

int main() {
    result = HttpInitialize(HttpApiVersion, HTTP_INITIALIZE_SERVER, NULL);
    if (result != NO_ERROR) {
        return 1; // Initialization failed
    }

    // Create an HTTP Server Session
    HANDLE hReqQueue = NULL;
    result = HttpCreateHttpHandle(&hReqQueue, 0);
    if (result != NO_ERROR) {
        HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);
        return 1; // Failed to create request queue
    }

    PCWSTR urlPrefix = L"http://localhost:8080/";
    result = HttpAddUrl(hReqQueue, urlPrefix, NULL);
    if (result != NO_ERROR) {
        HttpCloseRequestQueue(hReqQueue);
        HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);
        return 1; // Failed to add URL
    }

    wprintf(L"Listening for requests at: %s\n", urlPrefix);

    HTTP_REQUEST_ID reqId = 0;
    HTTP_REQUEST* pRequest;
    ULONG reqBufSize = sizeof(HTTP_REQUEST) + 2048; // Allocate a 2 KB buffer
    pRequest = (HTTP_REQUEST*)malloc(reqBufSize);
    DWORD bytesRead;

    while (TRUE) {
        ZeroMemory(pRequest, reqBufSize);
        result = HttpReceiveHttpRequest(
            hReqQueue,    // Req Queue
            reqId,        // Req ID
            0,            // Flags
            pRequest,     // HTTP request buffer
            reqBufSize,   // req buffer length
            &bytesRead,   // bytes received
            NULL          // LPOVERLAPPED
        );


        if (result == NO_ERROR) {
            switch (pRequest->Verb)
            {
                case HttpVerbGET:
                    wprintf(L"Got a GET request for %ws \n", pRequest->CookedUrl.pFullUrl);
                    result = SendHttpResponse(hReqQueue, pRequest);
                    break;
                case HttpVerbPOST:
                    wprintf(L"Got a POST request for %ws \n", pRequest->CookedUrl.pFullUrl);
                    break;
                default:
                    break;
            }
        }
    }

    // cleanup
    HttpRemoveUrl(hReqQueue, urlPrefix);
    HttpCloseRequestQueue(hReqQueue);
    HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);

    return 0;
}

DWORD SendHttpResponse(HANDLE hReqQueue, HTTP_REQUEST* pRequest)
{
    HTTP_RESPONSE response;
    HTTP_DATA_CHUNK dataChunk;
    DWORD result;
    DWORD bytesSent;

    // Prepare the response
    ZeroMemory(&response, sizeof(response));
    response.Headers.KnownHeaders[HttpHeaderContentType].pRawValue = "text/html";
    response.Headers.KnownHeaders[HttpHeaderContentType].RawValueLength = (USHORT)strlen("text/html");
    response.Version = (HTTP_VERSION)HTTP_VERSION_1_1;
    response.StatusCode = (USHORT)200;
    response.ReasonLength = (USHORT)strlen("OK");
    response.pReason = (PCSTR)"OK";
    response.EntityChunkCount = 1;
    dataChunk.DataChunkType = HttpDataChunkFromMemory;
    dataChunk.FromMemory.pBuffer = "Hello, World!";
    dataChunk.FromMemory.BufferLength = (ULONG)strlen("Hello, World!");
    response.pEntityChunks = &dataChunk;

    // Send the response
    result = HttpSendHttpResponse(
        hReqQueue,
        pRequest->RequestId,
        0,
        &response,
        NULL,
        &bytesSent,
        NULL,
        0,
        NULL,
        NULL
    );

    if (result != NO_ERROR)
    {
        wprintf(L"HttpSendHttpResponse failed with %lu \n", result);
    }

    return result;
}

More info about the Http Server API: https://learn.microsoft.com/en-us/windows/win32/http/http-api-start-page

0
Subscribe to my newsletter

Read articles from Ciprian Fusa directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ciprian Fusa
Ciprian Fusa

I am a .NET Developer and Consultant with over 15 years of experience, I specialize in crafting scalable, high-performance applications that drive business growth and innovation.