Win32 API programming with C - HTTP Server API
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
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.