这里假设处理的 URL 是遵循常见的http
或https
协议格式的,例如:
http://example.com:8080/path/to/resource?param1=value1¶m2=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¶m2=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.解析协议:通过查找://
字符串来确定协议部分,如http
或https
,并将其提取出来存储在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¶m2=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 并输出,最后释放动态分配的内存。