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:
- Create server socket and bind to specified port
- Accept incoming client connections and spawn child processes
- Receive HTTP GET requests from clients
- Forward requests to destination web servers
- Relay server responses back to clients
- 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.