Windows网络编程

6、HTTP协议

1、课程目标

  1. 深入理解HTTP协议结构
  2. 掌握HTTP请求和响应格式
  3. 学会使用原始套接字实现HTTP
  4. 理解HTTP在安全工具中的应用

2、名词解释

术语解释
HTTPHyperText Transfer Protocol
Request Line请求行(方法 URI 版本)
Status Line状态行(版本 状态码 原因)
Header头部字段
Body消息体

3、技术原理

3.1、HTTP请求格式

┌─────────────────────────────────────────────────────────────┐
│                    HTTP请求结构                              │
│                                                             │
│  GET /path/resource HTTP/1.1\r\n     ← 请求行               │
│  Host: www.example.com\r\n           ← 头部字段             │
│  User-Agent: Mozilla/5.0\r\n                                │
│  Accept: text/html\r\n                                      │
│  Connection: keep-alive\r\n                                 │
│  \r\n                                ← 空行                 │
│  [Request Body]                      ← 请求体(POST)         │
│                                                             │
│  HTTP方法:GET POST PUT DELETE HEAD OPTIONS PATCH          │
└─────────────────────────────────────────────────────────────┘

4、代码实现

4.1、示例1:手动构造HTTP请求

// RawHttp.c - 原始HTTP实现
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

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

// HTTP GET请求
BOOL HttpGetRaw(const char* host, int port, const char* path, char** response, int* respLen) {
    SOCKET sock;
    struct sockaddr_in serverAddr;
    char request[2048], buffer[4096];
    int totalRecv = 0, recvLen;
    int capacity = 8192;
    
    *response = (char*)malloc(capacity);
    if (!*response) return FALSE;
    
    // 创建连接
    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    struct hostent* hostInfo = gethostbyname(host);
    if (!hostInfo) return FALSE;
    
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(port);
    memcpy(&serverAddr.sin_addr, hostInfo->h_addr, hostInfo->h_length);
    
    if (connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) {
        closesocket(sock);
        return FALSE;
    }
    
    // 构造HTTP请求
    sprintf_s(request, sizeof(request),
        "GET %s HTTP/1.1\r\n"
        "Host: %s\r\n"
        "User-Agent: CustomClient/1.0\r\n"
        "Accept: */*\r\n"
        "Connection: close\r\n"
        "\r\n",
        path, host);
    
    // 发送请求
    send(sock, request, strlen(request), 0);
    
    // 接收响应
    while ((recvLen = recv(sock, buffer, sizeof(buffer), 0)) > 0) {
        if (totalRecv + recvLen >= capacity) {
            capacity *= 2;
            *response = (char*)realloc(*response, capacity);
        }
        memcpy(*response + totalRecv, buffer, recvLen);
        totalRecv += recvLen;
    }
    
    (*response)[totalRecv] = '\0';
    *respLen = totalRecv;
    
    closesocket(sock);
    return TRUE;
}

// HTTP POST请求
BOOL HttpPostRaw(const char* host, int port, const char* path, 
                 const char* contentType, const char* body,
                 char** response, int* respLen) {
    SOCKET sock;
    struct sockaddr_in serverAddr;
    char request[4096];
    
    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    struct hostent* hostInfo = gethostbyname(host);
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(port);
    memcpy(&serverAddr.sin_addr, hostInfo->h_addr, hostInfo->h_length);
    
    connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    
    // 构造POST请求
    sprintf_s(request, sizeof(request),
        "POST %s HTTP/1.1\r\n"
        "Host: %s\r\n"
        "User-Agent: CustomClient/1.0\r\n"
        "Content-Type: %s\r\n"
        "Content-Length: %d\r\n"
        "Connection: close\r\n"
        "\r\n"
        "%s",
        path, host, contentType, strlen(body), body);
    
    send(sock, request, strlen(request), 0);
    
    // 接收响应
    int capacity = 8192, totalRecv = 0, recvLen;
    char buffer[4096];
    *response = (char*)malloc(capacity);
    
    while ((recvLen = recv(sock, buffer, sizeof(buffer), 0)) > 0) {
        if (totalRecv + recvLen >= capacity) {
            capacity *= 2;
            *response = (char*)realloc(*response, capacity);
        }
        memcpy(*response + totalRecv, buffer, recvLen);
        totalRecv += recvLen;
    }
    
    (*response)[totalRecv] = '\0';
    *respLen = totalRecv;
    
    closesocket(sock);
    return TRUE;
}

// 解析HTTP响应
void ParseHttpResponse(const char* response, int* statusCode, char** headers, char** body) {
    // 找到状态码
    const char* p = strstr(response, "HTTP/1.");
    if (p) {
        p = strchr(p, ' ');
        if (p) *statusCode = atoi(p + 1);
    }
    
    // 找到头部结束位置
    const char* headerEnd = strstr(response, "\r\n\r\n");
    if (headerEnd) {
        int headerLen = headerEnd - response;
        *headers = (char*)malloc(headerLen + 1);
        strncpy_s(*headers, headerLen + 1, response, headerLen);
        
        *body = _strdup(headerEnd + 4);
    }
}

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    
    char* response;
    int respLen;
    
    if (HttpGetRaw("httpbin.org", 80, "/get", &response, &respLen)) {
        printf("Response (%d bytes):\n%s\n", respLen, response);
        free(response);
    }
    
    WSACleanup();
    return 0;
}

4.2、示例2:HTTP头部解析

// HttpParser.c - HTTP解析器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct _HTTP_HEADER {
    char* name;
    char* value;
} HTTP_HEADER;

typedef struct _HTTP_REQUEST {
    char method[16];
    char path[1024];
    char version[16];
    HTTP_HEADER headers[50];
    int headerCount;
    char* body;
    int bodyLength;
} HTTP_REQUEST;

// 解析HTTP请求
BOOL ParseHttpRequest(const char* data, HTTP_REQUEST* request) {
    char* line, *saveptr;
    char* dataCopy = _strdup(data);
    
    memset(request, 0, sizeof(HTTP_REQUEST));
    
    // 解析请求行
    line = strtok_s(dataCopy, "\r\n", &saveptr);
    if (!line) return FALSE;
    
    sscanf_s(line, "%s %s %s", request->method, 16, request->path, 1024, request->version, 16);
    
    // 解析头部
    while ((line = strtok_s(NULL, "\r\n", &saveptr)) != NULL) {
        if (strlen(line) == 0) break;  // 空行,头部结束
        
        char* colon = strchr(line, ':');
        if (colon && request->headerCount < 50) {
            *colon = '\0';
            request->headers[request->headerCount].name = _strdup(line);
            
            char* value = colon + 1;
            while (*value == ' ') value++;
            request->headers[request->headerCount].value = _strdup(value);
            
            request->headerCount++;
        }
    }
    
    // 获取Body
    const char* bodyStart = strstr(data, "\r\n\r\n");
    if (bodyStart) {
        request->body = _strdup(bodyStart + 4);
        request->bodyLength = strlen(request->body);
    }
    
    free(dataCopy);
    return TRUE;
}

// 获取指定头部
const char* GetHeader(HTTP_REQUEST* request, const char* name) {
    for (int i = 0; i < request->headerCount; i++) {
        if (_stricmp(request->headers[i].name, name) == 0) {
            return request->headers[i].value;
        }
    }
    return NULL;
}

// 构建HTTP响应
void BuildHttpResponse(int statusCode, const char* contentType, 
                       const char* body, char* response, int maxLen) {
    const char* statusText;
    switch (statusCode) {
        case 200: statusText = "OK"; break;
        case 404: statusText = "Not Found"; break;
        case 500: statusText = "Internal Server Error"; break;
        default: statusText = "Unknown"; break;
    }
    
    sprintf_s(response, maxLen,
        "HTTP/1.1 %d %s\r\n"
        "Content-Type: %s\r\n"
        "Content-Length: %d\r\n"
        "Connection: close\r\n"
        "\r\n"
        "%s",
        statusCode, statusText, contentType, strlen(body), body);
}

5、课后作业

  1. 实现HTTP分块传输解析
  2. 添加Cookie处理
  3. 实现HTTP代理
  4. 编写HTTP流量分析工具

6、扩展阅读

  • RFC 2616 HTTP/1.1
  • HTTP/2协议
  • HTTPS和TLS