C语言实现URL地址的解析和打包

这里假设处理的 URL 是遵循常见的httphttps协议格式的,例如:

http://example.com:8080/path/to/resource?param1=value1&param2=value2#fragment

这种形式。

一、URL 解析示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// 结构体用于存储解析后的URL各部分
typedef struct URLComponents {
    char *protocol;
    char *host;
    int port;
    char *path;
    char *query;
    char *fragment;
} URLComponents;

// 函数用于解析URL
URLComponents parseURL(const char *url) {
    URLComponents components = {NULL, NULL, 0, NULL, NULL, NULL};

    // 先查找协议部分(以://结束)
    char *protocol_end = strstr(url, "://");
    if (protocol_end!= NULL) {
        int protocol_len = protocol_end - url;
        components.protocol = (char *)malloc(protocol_len + 1);
        strncpy(components.protocol, url, protocol_len);
        components.protocol[protocol_len] = '\0';

        // 移动指针越过://
        url = protocol_end + 3;
    }

    // 查找主机部分(到端口、路径、查询参数或片段标识符之前)
    char *path_start = strchr(url, '/');
    char *port_start = strchr(url, ':');
    char *query_start = strchr(url, '?');
    char *fragment_start = strchr(url, '#');

    if (port_start && (!path_start || port_start < path_start) && (!query_start || port_start < query_start) && (!fragment_start || port_start < fragment_start)) {
        int host_len = port_start - url;
        components.host = (char *)malloc(host_len + 1);
        strncpy(components.host, url, host_len);
        components.host[host_len] = '\0';

        // 解析端口号
        components.port = atoi(port_start + 1);

        url = port_start + 1;
    } else if (path_start && (!query_start || path_start < query_start) && (!fragment_start || path_start < fragment_start)) {
        int host_len = path_start - url;
        components.host = (char *)malloc(host_len + 1);
        strncpy(components.host, url, host_len);
        components.host[host_len] = '\0';

        url = path_start;
    } else if (query_start && (!fragment_start || query_start < fragment_start)) {
        int host_len = query_start - url;
        components.host = (char *)malloc(host_len + 1);
        strncpy(components.host, url, host_len);
        components.host[host_len] = '\0';

        url = query_start;
    } else {
        int host_len = strlen(url);
        components.host = (char *)malloc(host_len + 1);
        strcpy(components.host, url);
    }

    // 解析路径部分(从第一个/开始到查询参数或片段标识符之前)
    if (path_start) {
        char *next = query_start? query_start : fragment_start;
        if (next) {
            int path_len = next - path_start;
            components.path = (char *)malloc(path_len + 1);
            strncpy(components.path, path_start, path_len);
            components.path[path_len] = '\0';

            url = next;
        } else {
            components.path = (char *)malloc(strlen(path_start) + 1);
            strcpy(components.path, path_start);
        }
    }

    // 解析查询参数部分(从?开始到#之前)
    if (query_start) {
        char *next = fragment_start;
        if (next) {
            int query_len = next - query_start;
            components.query = (char *)malloc(query_len + 1);
            strncpy(components.query, query_start, query_len);
            components.query[query_len] = '\0';

            url = next;
        } else {
            components.query = (char *)malloc(strlen(query_start) + 1);
            strcpy(components.query, query_start);
        }
    }

    // 解析片段标识符部分(从#开始到结尾)
    if (fragment_start) {
        int fragment_len = strlen(fragment_start);
        components.fragment = (char *)malloc(fragment_len + 1);
        strncpy(components.fragment, fragment_start, fragment_len);
        components.fragment[fragment_len] = '\0';
    }

    return components;
}

// 释放结构体中各字符串内存的函数
void freeURLComponents(URLComponents components) {
    free(components.protocol);
    free(components.host);
    free(components.path);
    free(components.query);
    free(components.fragment);
}

int main() {
    const char *url = "http://example.com:8080/path/to/resource?param1=value1&param2=value2#fragment";
    URLComponents components = parseURL(url);

    printf("Protocol: %s\n", components.protocol? components.protocol : "");
    printf("Host: %s\n", components.host? components.host : "");
    printf("Port: %d\n", components.port);
    printf("Path: %s\n", components.path? components.path : "");
    printf("Query: %s\n", components.query? components.query : "");
    printf("Fragment: %s\n", components.fragment? components.fragment : "");

    freeURLComponents(components);

    return 0;
}

主要过程: 

1.解析协议:通过查找://字符串来确定协议部分,如httphttps,并将其提取出来存储在URLComponents结构体的protocol成员中。

2.解析主机:根据:(端口号前导符)、/(路径前导符)、?(查询参数前导符)和#(片段标识符前导符)的位置关系来确定主机部分,提取并存储在host成员中。

3.解析端口:如果主机后面紧跟着:,则提取后面的数字作为端口号存储在port成员中,若没有明确指定端口,则根据协议默认端口(例如http默认 80,https默认 443 等,这里代码未做默认处理补充,可自行添加逻辑完善)。

4.解析路径:从第一个/开始到查询参数或片段标识符之前的部分作为路径提取到path成员中。

5.解析查询参数:从?开始到#之前的部分作为查询参数提取到query成员中。

6.解析片段标识符:从#开始到结尾的部分作为片段标识符提取到fragment成员中。

最后在main函数中调用parseURL函数进行解析,并输出各部分内容,然后通过freeURLComponents函数释放之前动态分配的内存。

二、URL 打包示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// 根据给定的URL各部分组装成完整的URL
char *packURL(const char *protocol, const char *host, int port, const char *path, const char *query, const char *fragment) {
    int len = strlen(protocol) + strlen(host) + 3 + (port > 0? 5 : 0) + (path? strlen(path) : 0) + (query? strlen(query) : 0) + (fragment? strlen(fragment) : 0);
    char *url = (char *)malloc(len + 1);
    sprintf(url, "%s://%s", protocol, host);
    if (port > 0) {
        sprintf(url + strlen(url), ":%d", port);
    }
    if (path) {
        sprintf(url + strlen(url), "%s", path);
    }
    if (query) {
        sprintf(url + strlen(url), "?%s", query);
    }
    if (fragment) {
        sprintf(url + strlen(url), "#%s", fragment);
    }
    return url;
}

int main() {
    const char *protocol = "http";
    const char *host = "example.com";
    int port = 8080;
    const char *path = "/path/to/resource";
    const char *query = "param1=value1&param2=value2";
    const char *fragment = "fragment";

    char *new_url = packURL(protocol, host, port, path, query, fragment);
    printf("Packed URL: %s\n", new_url);
    free(new_url);

    return 0;
}

通过sprintf函数逐步将各部分按照正确的顺序拼接起来,先拼接协议和主机,然后根据情况添加端口号、路径、查询参数和片段标识符,最终返回组装好的完整 URL 字符串。在main函数中进行调用演示,组装出一个 URL 并输出,最后释放动态分配的内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值