使用 C 解析 URL 的最佳方法?

Posted

技术标签:

【中文标题】使用 C 解析 URL 的最佳方法?【英文标题】:Best ways of parsing a URL using C? 【发布时间】:2009-04-07 14:47:18 【问题描述】:

我有一个这样的网址:

http://192.168.0.1:8080/servlet/rece

我想解析 URL 以获取值:

IP: 192.168.0.1
Port: 8080
page:  /servlet/rece

我该怎么做?

【问题讨论】:

对于 windows,使用 CoInternetParseUrl 【参考方案1】:

就我个人而言,我窃取了HTParse.c 模块from the W3C(例如,它用于lynx Web 浏览器)。然后,您可以执行以下操作:

 strncpy(hostname, HTParse(url, "", PARSE_HOST), size)

使用完善且经过调试的库的重要之处在于,您不会陷入典型的 URL 解析的陷阱(当主机是 IP 地址时,许多正则表达式会失败,例如,特别是 IPv6 的)。

【讨论】:

特别要注意,如果您尝试使用冒号分隔符,则使用 IPv6 时会出现模棱两可的情况。例如3ffe:0501::1:2,是端口 2,还是默认端口的完整地址。 URL 规范已经处理了这个问题,预写的库也是如此。 请注意没有真正的歧义。 URI 标准 RFC 3986 很明确,您的示例是非法的(您需要方括号)。 谢谢,这很令人欣慰。我的错误印象是,面向用户的代码(例如浏览器地址栏)接受不带方括号的地址。快速浏览一些流行的浏览器会发现情况并非如此。 HTParse.c 有许多依赖项,你有没有机会解释一下如何轻松地从项目中“窃取”它?也许早在 2009 年就没有了;)【参考方案2】:

我使用 sscanf 写了一个简单的代码,它可以解析非常基本的 URL。

#include <stdio.h>

int main(void)

    const char text[] = "http://192.168.0.2:8888/servlet/rece";
    char ip[100];
    int port = 80;
    char page[100];
    sscanf(text, "http://%99[^:]:%99d/%99[^\n]", ip, &port, page);
    printf("ip = \"%s\"\n", ip);
    printf("port = \"%d\"\n", port);
    printf("page = \"%s\"\n", page);
    return 0;


./urlparse
ip = "192.168.0.2"
port = "8888"
page = "servlet/rece"

【讨论】:

这是在什么平台上?我不知道你可以把像 [^:] 这样的正则表达式放在 sscanf 格式中。 我的平台是:uname -a Linux ubuntu 2.6.24-21-generic #1 SMP Tue Oct 21 23:43:45 UTC 2008 i686 GNU/Linux [^:] 在此上下文中不是正则表达式,它只是 sscanf() 的特殊格式说明符。这是标准的。例如,请参阅此手册页:linux.die.net/man/3/sscanf>. 在没有端口号的情况下解析有一些错误,不能正常工作。我该如何解决它。【参考方案3】:

可能会迟到,... 我使用的是 - http_parser_parse_url() 函数和从 Joyent/HTTP parser lib 中分离出来的所需宏 - 效果很好,~600LOC。

【讨论】:

是的。 node.js HTTP 解析器库非常棒,并且针对与 HTTP 请求/响应有关的任何内容都经过了很好的测试。【参考方案4】:

如果您想要简单的方法,请使用regular expression。否则使用FLEX/BISON。

您也可以使用URI parsing library

【讨论】:

确实,使用库似乎是唯一合理的做法,因为存在许多陷阱(http 与 https、显式端口、路径中的编码等)。 嗨,我为 url 写了一个 BNF,就像这样。 URL = "http://" IP PORT? 页?一个 flex 生成了一个解析 url 的文件。但是如何获取 IP、PORT 和 PAGE 等各个部分。从网址【参考方案5】:

Libcurl 现在有了curl_url_get() 函数,可以提取主机、路径等。

示例代码:https://curl.haxx.se/libcurl/c/parseurl.html

/* extract host name from the parsed URL */ 
uc = curl_url_get(h, CURLUPART_HOST, &host, 0);
if(!uc) 
  printf("Host name: %s\n", host);
  curl_free(host);

【讨论】:

【参考方案6】:

这个尺寸减小了,对我来说效果很好http://draft.scyphus.co.jp/lang/c/url_parser.html。只有两个文件(*.c、*.h)。 我不得不修改代码 [1]。

[1]将所有函数调用从http_parsed_url_free(purl)改为parsed_url_free(purl)

   //Rename the function called
   //http_parsed_url_free(purl);
   parsed_url_free(purl);

【讨论】:

@tremendows:很好的链接。它就像一个魅力。 遗憾的是,优秀的代码受版权保护“保留所有权利”,因此不应将其用于个人项目之外。【参考方案7】:

这个 C 要点可能很有用。它使用 sscanf 实现了纯 C 解决方案。

https://github.com/luismartingil/per.scripts/tree/master/c_parse_http_url

它使用

// Parsing the tmp_source char*
if (sscanf(tmp_source, "http://%99[^:]:%i/%199[^\n]", ip, &port, page) == 3)  succ_parsing = 1;
else if (sscanf(tmp_source, "http://%99[^/]/%199[^\n]", ip, page) == 2)  succ_parsing = 1;
else if (sscanf(tmp_source, "http://%99[^:]:%i[^\n]", ip, &port) == 2)  succ_parsing = 1;
else if (sscanf(tmp_source, "http://%99[^\n]", ip) == 1)  succ_parsing = 1;
(...)

【讨论】:

第三个 if 语句永远不会被测试,因为第二个具有相同的含义,所以这可能会导致端口/页面出现问题【参考方案8】:

这是我写的

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
typedef struct

    const char* protocol = 0;
    const char* site = 0;
    const char* port = 0;
    const char* path = 0;
 URL_INFO;
URL_INFO* split_url(URL_INFO* info, const char* url)

    if (!info || !url)
        return NULL;
    info->protocol = strtok(strcpy((char*)malloc(strlen(url)+1), url), "://");
    info->site = strstr(url, "://");
    if (info->site)
    
        info->site += 3;
        char* site_port_path = strcpy((char*)calloc(1, strlen(info->site) + 1), info->site);
        info->site = strtok(site_port_path, ":");
        info->site = strtok(site_port_path, "/");
    
    else
    
        char* site_port_path = strcpy((char*)calloc(1, strlen(url) + 1), url);
        info->site = strtok(site_port_path, ":");
        info->site = strtok(site_port_path, "/");
    
    char* URL = strcpy((char*)malloc(strlen(url) + 1), url);
    info->port = strstr(URL + 6, ":");
    char* port_path = 0;
    char* port_path_copy = 0;
    if (info->port && isdigit(*(port_path = (char*)info->port + 1)))
    
        port_path_copy = strcpy((char*)malloc(strlen(port_path) + 1), port_path);
        char * r = strtok(port_path, "/");
        if (r)
            info->port = r;
        else
            info->port = port_path;
    
    else
        info->port = "80";
    if (port_path_copy)
        info->path = port_path_copy + strlen(info->port ? info->port : "");
    else 
    
        char* path = strstr(URL + 8, "/");
        info->path = path ? path : "/";
    
    int r = strcmp(info->protocol, info->site) == 0;
    if (r && info->port == "80")
        info->protocol = "http";
    else if (r)
        info->protocol = "tcp";
    return info;

测试

int main()

    URL_INFO info;
    split_url(&info, "ftp://192.168.0.1:8080/servlet/rece");
    printf("Protocol: %s\nSite: %s\nPort: %s\nPath: %s\n", info.protocol, info.site, info.port, info.path);
    return 0;

出局

Protocol: ftp
Site: 192.168.0.1
Port: 8080
Path: /servlet/rece

【讨论】:

【参考方案9】:

纯基于sscanf() 的解决方案:

//Code
#include <stdio.h>

int
main (int argc, char *argv[])

    char *uri = "http://192.168.0.1:8080/servlet/rece"; 
    char ip_addr[12], path[100];
    int port;
    
    int uri_scan_status = sscanf(uri, "%*[^:]%*[:/]%[^:]:%d%s", ip_addr, &port, path);
    
    printf("[info] URI scan status : %d\n", uri_scan_status);
    if( uri_scan_status == 3 )
       
        printf("[info] IP Address : '%s'\n", ip_addr);
        printf("[info] Port: '%d'\n", port);
        printf("[info] Path : '%s'\n", path);
    
    
    return 0;


但是,请记住,此解决方案是为 [protocol_name]://[ip_address]:[port][/path] 类型的 URI 量身定制的。要了解有关 URI 语法中存在的组件的更多信息,您可以前往RFC 3986。

现在让我们分解我们定制的格式字符串:"%*[^:]%*[:/]%[^:]:%d%s"

%*[^:] 有助于忽略协议/方案(例如 http、https、ftp 等)

它基本上从头开始捕获字符串,直到它第一次遇到: 字符。由于我们在% 字符之后使用了*,因此捕获的字符串将被忽略。

%*[:/] 有助于忽略协议和 IP 地址之间的分隔符,即://

%[^:] 有助于捕获分隔符后出现的字符串,直到遇到:。而这个捕获的字符串只不过是 IP 地址。

:%d 有助于捕获号码。坐在: 字符之后(在捕获IP 地址期间遇到的字符)。没有。这里捕获的基本上是你的端口号。

%s 如您所知,它将帮助您捕获剩余的字符串,这不过是您正在寻找的资源的路径。

【讨论】:

【参考方案10】:

编写自定义解析器或使用字符串替换函数之一替换分隔符“:”,然后使用sscanf()

【讨论】:

有很多陷阱需要注意,所以自定义解析器在我看来是个坏主意。 @bortzmeye:这不会使建议无效。这是模糊的推理。此外,自定义解析器是最强大/最有效/无依赖的。 sscanf 更容易出错。 “编写一些代码来满足您的需求”如何成为公认的答案?

以上是关于使用 C 解析 URL 的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章

C# - 解析网页的最佳方法?

解析连续 JSON 文件的最佳方法?

将解析的 html 存储为 XML 输出的最佳方法

c++ 高效解析url算法

检查网站每个链接的最佳方法是什么?

在 C# 中解析 html 的最佳方法是啥? [关闭]