Building a Simple HTTP Proxy Server in C on Linux

This article demonstrates how to implement a basic HTTP proxy server using C language socket programming and process management on Linux. The implementation supports the GET method of the HTTP 1.0 protocol.

The proxy listens on a user-specified port, which can be configured at startup by running ./proxy <port> in the terminal.

Architecture

The proxy server follows a straightforward request-response pattern:

  1. Create server socket and bind to specified port
  2. Accept incoming client connections and spawn child processes
  3. Receive HTTP GET requests from clients
  4. Forward requests to destination web servers
  5. Relay server responses back to clients
  6. Clean up resources and close connections

Implementation Details

Command-line Argument Validation

First, validate that the user provides a port number:

if (argc != 2) {
    fprintf(stderr, "Usage: %s <port>\n", argv[0]);
    return EXIT_FAILURE;
}

int listen_port = atoi(argv[1]);
if (listen_port < 1 || listen_port > 65535) {
    fprintf(stderr, "Error: Port must be between 1 and 65535.\n");
    return EXIT_FAILURE;
}

Server Socket Initialization

Set up the server socket with IPv4 and TCP protocol:

int server_fd = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(listen_port);
server_addr.sin_addr.s_addr = INADDR_ANY;

bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
listen(server_fd, BACKLOG);
printf("Proxy listening on port %d\n", listen_port);

Key points: INADDR_ANY allows connections from any network interface, htons() converts the port to network byte order, and listen() sets the maximum pending connection queue.

Connection Handling Loop

The server uses fork() to handle multiple concurrent clients:

while (1) {
    int client_fd = accept(server_fd, NULL, NULL);
    printf("New connection established\n");
    
    if (fork() == 0) {
        // Child process handles the request
        close(server_fd);
        handle_client(client_fd);
        exit(EXIT_SUCCESS);
    }
    
    close(client_fd);
}

Processing Client Requests

Parse incoming HTTP requests to extract the method:

char recv_buf[BUFFER_SIZE];
read(client_fd, recv_buf, sizeof(recv_buf));

char req_method[64], req_url[256], http_ver[32];
sscanf(recv_buf, "%s %s %s", req_method, req_url, http_ver);

if (strcasecmp(req_method, "GET") != 0) {
    const char *not_impl = "HTTP/1.0 501 Not Implemented\r\n\r\n";
    write(client_fd, not_impl, strlen(not_impl));
    close(client_fd);
    return;
}

The server only supports GET requests. Any other method returns a 501 status code.

Parsing URL and Resolving Hostname

Extract hostname and path from the URL:

int target_port = 80;
char hostname[128], resource_path[256], *port_sep;

sscanf(req_url, "http://%[^/]%s", hostname, resource_path);

port_sep = strchr(hostname, ':');
if (port_sep) {
    *port_sep = '\0';
    target_port = atoi(port_sep + 1);
}

printf("Target: %s:%d%s\n", hostname, target_port, resource_path);

struct hostent *host_info = gethostbyname(hostname);

Connecting to Target Server

Establish a connection to the destination web server:

int target_fd = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(target_port);
memcpy(&dest_addr.sin_addr, host_info->h_addr_list[0], host_info->h_length);

connect(target_fd, (struct sockaddr *)&dest_addr, sizeof(dest_addr));

Forwarding Requests

Send the reformatted request to the target server:

char http_request[BUFFER_SIZE];
snprintf(http_request, sizeof(http_request),
         "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n",
         resource_path, hostname);

write(target_fd, http_request, strlen(http_request));

Relaying Responses

Stream the server response back to the client:

while (1) {
    memset(recv_buf, 0, sizeof(recv_buf));
    ssize_t data_len = read(target_fd, recv_buf, sizeof(recv_buf) - 1);
    
    if (data_len <= 0) {
        break;
    }
    
    write(client_fd, recv_buf, data_len);
}

close(client_fd);
close(target_fd);

Complete Source Code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define BUFFER_SIZE 4096
#define BACKLOG 50

void process_request(int client_fd) {
    char recv_buf[BUFFER_SIZE];
    memset(recv_buf, 0, sizeof(recv_buf));
    
    if (read(client_fd, recv_buf, sizeof(recv_buf) - 1) <= 0) {
        close(client_fd);
        return;
    }
    
    char req_method[64], req_url[256], http_ver[32];
    sscanf(recv_buf, "%s %s %s", req_method, req_url, http_ver);
    
    if (strcasecmp(req_method, "GET") != 0) {
        const char *not_impl = "HTTP/1.0 501 Not Implemented\r\n\r\n";
        write(client_fd, not_impl, strlen(not_impl));
        close(client_fd);
        return;
    }
    
    int target_port = 80;
    char hostname[128], resource_path[256], *port_sep;
    sscanf(req_url, "http://%[^/]%s", hostname, resource_path);
    
    port_sep = strchr(hostname, ':');
    if (port_sep) {
        *port_sep = '\0';
        target_port = atoi(port_sep + 1);
    }
    
    printf("Connecting to %s:%d%s\n", hostname, target_port, resource_path);
    
    struct hostent *host_info = gethostbyname(hostname);
    if (!host_info) {
        close(client_fd);
        return;
    }
    
    int target_fd = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in dest_addr;
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(target_port);
    memcpy(&dest_addr.sin_addr, host_info->h_addr_list[0], host_info->h_length);
    
    if (connect(target_fd, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
        close(client_fd);
        close(target_fd);
        return;
    }
    
    char http_request[BUFFER_SIZE];
    snprintf(http_request, sizeof(http_request),
             "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n",
             resource_path, hostname);
    
    write(target_fd, http_request, strlen(http_request));
    
    while (1) {
        memset(recv_buf, 0, sizeof(recv_buf));
        ssize_t bytes_read = read(target_fd, recv_buf, sizeof(recv_buf) - 1);
        
        if (bytes_read <= 0) {
            break;
        }
        
        write(client_fd, recv_buf, bytes_read);
    }
    
    close(client_fd);
    close(target_fd);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <port>\n", argv[0]);
        return EXIT_FAILURE;
    }
    
    int listen_port = atoi(argv[1]);
    if (listen_port < 1 || listen_port > 65535) {
        fprintf(stderr, "Error: Port must be between 1 and 65535.\n");
        return EXIT_FAILURE;
    }
    
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(listen_port);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    
    bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    listen(server_fd, BACKLOG);
    
    printf("HTTP Proxy started on port %d\n", listen_port);
    
    while (1) {
        int client_fd = accept(server_fd, NULL, NULL);
        printf("Client connected\n");
        
        if (fork() == 0) {
            close(server_fd);
            process_request(client_fd);
            exit(EXIT_SUCCESS);
        }
        
        close(client_fd);
    }
    
    return EXIT_SUCCESS;
}

Usage

Compile the source code with GCC:

gcc -o proxy proxy.c -Wall

Launch the proxy server on port 8888:

./proxy 8888

Configure your web browser or system proxy settings to use localhost:8888 or 127.0.0.1:8888 as the HTTP proxy. Requests for websites should now be routed through your custom proxy.

Limitations

This implementation has several constraints: only HTTP GET requests are supported, HTTPS traffic cannot be handled since the proxy does not perform SSL/TLS tunneling, and each request is processed independently without connection keep-alive support.

Tags: C socket-programming HTTP proxy-server Linux

Posted on Tue, 12 May 2026 19:12:26 +0000 by markduce