URI详解

URI的组成 

URI官方文档

Scheme
Scheme指的就是方案,比如HTTP,HTTPS,FTP等,都是可以使用的,思想不要被这些常用的协议给局限了,我们还可以自定义协议,只要服务器支持即可。Scheme可以是由字母,数字,+,-,.,都是允许的。

注意:在Scheme之后,必须使用://把Scheme与后面的部分区分开来

Query
query就是查询参数,是一个可选的参数,如有有的话,那么必须要以?开头

fragment
fragment也是可选的,如果有的话,必须以#开头

authority
authority包含了用户名与密码(user infomation),还有主机名(host),以及端口号(port)

像用户名密码这东西,我们现在基本已经不使用这种方式了,因为在URI中明文传输账号密码,实在不安全

现在还在用的,基本上也就是经常使用ftp下载资源时我们才使用

所以我们通常只使用host:port,即主机名+端口号的形式

主机名是不可省略的,因为一但省略,我们就找不到对应的服务器

而端口号我们却可以省略,比如HTTP的默认端口号就是80端口,HTTPS的默认端口号就是443端口

path
主机名后面紧跟的就是我们的path

在URI中,path部分必须要以/开头,所以不要把path之前的/误以为是前面authority的结尾

path也分了很多种,分别是path-abempty、path-absolute、path-noscheme、path-rootless、path-empty

为什么要对URI进行编码?

在 URI 里只能使用 ASCII 码,但如果要在 URI 里使用英语以外的汉语、日语等其他语言该怎么办呢?还有,某些特殊的 URI,会在 path、query 里出现“@&?"等起界定符作用的字符,会导致 URI 解析错误,这时又该怎么办呢?所以,URI 引入了编码机制,对于 ASCII 码以外的字符集和特殊字符做一个特殊的操作,把它们转换成与 URI 语义不冲突的形式。这在 RFC 规范里称为“escape”和“unescape”,俗称“转义”。

URI 转义的规则有点“简单粗暴”,直接把非 ASCII 码或特殊字符转换成十六进制字节值,然后前面再加上一个“%”。例如,空格被转义成“%20”,“?”被转义成“%3F”。而中文、日文等则通常使用 UTF-8 编码后再转义,例如“银河”会被转义成“%E9%93%B6%E6%B2%B3”。

有了这个编码规则后,URI 就更加完美了,可以支持任意的字符集用任何语言来标记资源。

传递数据中,如果存在工作分隔符的保留字符怎么办?

对可能产生歧义的数据编码

不在ASCII 码范围内的字符
ASCII 码中不可显示的字符
URI中规定的保留字符
不安全字符(传输 环节中可能会被不正确处理) ,如空格、引号、尖括号等
示例:

https://www.baidu.com/s?wd=?#!

https://www.baidu.com/s?w=hello world

https://www.baidu.com/s?wd=hello ’ > 时间

URL

URL是URI的一个子集。它是Uniform Resource Locator的缩写,译为“统一资源定位符”。

通俗地说,URL是Internet上描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上。采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL是URI概念的一种实现方式。

URL的一般格式为(带方括号[]的为可选项):

protocol :// hostname[:port] / path / [;parameters][?query]#fragment

URL的格式由三部分组成: 

①第一部分是协议(或称为服务方式)。

②第二部分是存有该资源的主机IP地址(有时也包括端口号)。

③第三部分是主机资源的具体地址,如目录和文件名等。

URI和URL之间的区别

你可能觉得URI和URL可能是相同的概念,其实并不是,URI和URL都定义了资源是什么,但URL还定义了该如何访问资源URL是一种具体的URI,它是URI的一个子集,它不仅唯一标识资源,而且还提供了定位该资源的信息。URI 是一种语义上的抽象概念,可以是绝对的,也可以是相对的,而URL则必须提供足够的信息来定位,是绝对的。只要能唯一标识资源的就是URI,在URI的基础上给出其资源的访问方式的就是URL

使用Ragel编写一个uri解析器

uri.h

#ifndef __SYLAR_URI_H__
#define __SYLAR_URI_H__

#include <memory>
#include <string>
#include <stdint.h>
#include "address.h"

namespace sylar {

/*
     foo://user@sylar.com:8042/over/there?name=ferret#nose
       \_/   \______________/\_________/ \_________/ \__/
        |           |            |            |        |
     scheme     authority       path        query   fragment
*/

/**
 * @brief URI类
 */
class Uri {
public:
    /// 智能指针类型定义
    typedef std::shared_ptr<Uri> ptr;

    /**
     * @brief 创建Uri对象
     * @param uri uri字符串
     * @return 解析成功返回Uri对象否则返回nullptr
     */
    static Uri::ptr Create(const std::string& uri);

    /**
     * @brief 构造函数
     */
    Uri();

    /**
     * @brief 返回scheme
     */
    const std::string& getScheme() const { return m_scheme;}

    /**
     * @brief 返回用户信息
     */
    const std::string& getUserinfo() const { return m_userinfo;}

    /**
     * @brief 返回host
     */
    const std::string& getHost() const { return m_host;}

    /**
     * @brief 返回路径
     */
    const std::string& getPath() const;

    /**
     * @brief 返回查询条件
     */
    const std::string& getQuery() const { return m_query;}

    /**
     * @brief 返回fragment
     */
    const std::string& getFragment() const { return m_fragment;}

    /**
     * @brief 返回端口
     */
    int32_t getPort() const;

    /**
     * @brief 设置scheme
     * @param v scheme
     */
    void setScheme(const std::string& v) { m_scheme = v;}

    /**
     * @brief 设置用户信息
     * @param v 用户信息
     */
    void setUserinfo(const std::string& v) { m_userinfo = v;}

    /**
     * @brief 设置host信息
     * @param v host
     */
    void setHost(const std::string& v) { m_host = v;}

    /**
     * @brief 设置路径
     * @param v 路径
     */
    void setPath(const std::string& v) { m_path = v;}

    /**
     * @brief 设置查询条件
     * @param v
     */
    void setQuery(const std::string& v) { m_query = v;}

    /**
     * @brief 设置fragment
     * @param v fragment
     */
    void setFragment(const std::string& v) { m_fragment = v;}

    /**
     * @brief 设置端口号
     * @param v 端口
     */
    void setPort(int32_t v) { m_port = v;}

    /**
     * @brief 序列化到输出流
     * @param os 输出流
     * @return 输出流
     */
    std::ostream& dump(std::ostream& os) const;

    /**
     * @brief 转成字符串
     */
    std::string toString() const;

    /**
     * @brief 获取Address
     */
    Address::ptr createAddress() const;
private:

    /**
     * @brief 是否默认端口
     */
    bool isDefaultPort() const;
private:
    /// schema
    std::string m_scheme;
    /// 用户信息(authitity)
    std::string m_userinfo;
    /// host(authitity)
    std::string m_host;
    /// 路径
    std::string m_path;
    /// 查询参数
    std::string m_query;
    /// fragment
    std::string m_fragment;
    /// 端口(authitity)
    int32_t m_port;
};

}

#endif

 使用有限状态机解析,

由于官方已进给出uri的正则解析表达式了,我们就没必要自己去设计了,但是官方的解析器和Ragel有部分区别,接下来对那部分不一样的进行分析,大部分的内容还是得大家自行去看说明文档

官方的通配符比如"*“、”+"这些都是放在表达式的左边,而Ragel必须写在表达式右边
官方使用 1( h16 “:” ) 表示匹配一次 ( h16 “:” ),而Ragel的格式为 (h16 “:”){1}
官方的或运算符使用的是 “/”,而Ragel使用的是 “|”等等

#include "uri.h"
#include <sstream>

namespace sylar {
%%{
    # See RFC 3986: http://www.ietf.org/rfc/rfc3986.txt

    machine uri_parser;

    gen_delims = ":" | "/" | "?" | "#" | "[" | "]" | "@";
    sub_delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | "," | ";" | "=";
    reserved = gen_delims | sub_delims;
    unreserved = alpha | digit | "-" | "." | "_" | "~";
    pct_encoded = "%" xdigit xdigit;

    action marku { mark = fpc; }
    action markh { mark = fpc; }

    action save_scheme
    {
        uri->setScheme(std::string(mark, fpc - mark));
        mark = NULL;
    }

    scheme = (alpha (alpha | digit | "+" | "-" | ".")*) >marku %save_scheme;

    action save_port
    {
        if (fpc != mark) {
            uri->setPort(atoi(mark));
        }
        mark = NULL;
    }
    action save_userinfo
    {
        if(mark) {
            //std::cout << std::string(mark, fpc - mark) << std::endl;
            uri->setUserinfo(std::string(mark, fpc - mark));
        }
        mark = NULL;
    }
    action save_host
    {
        if (mark != NULL) {
            //std::cout << std::string(mark, fpc - mark) << std::endl;
            uri->setHost(std::string(mark, fpc - mark));
        }
    }

    userinfo = (unreserved | pct_encoded | sub_delims | ":")*;
    dec_octet = digit | [1-9] digit | "1" digit{2} | 2 [0-4] digit | "25" [0-5];
    IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet;
    h16 = xdigit{1,4};
    ls32 = (h16 ":" h16) | IPv4address;
    IPv6address = (                         (h16 ":"){6} ls32) |
                  (                    "::" (h16 ":"){5} ls32) |
                  ((             h16)? "::" (h16 ":"){4} ls32) |
                  (((h16 ":"){1} h16)? "::" (h16 ":"){3} ls32) |
                  (((h16 ":"){2} h16)? "::" (h16 ":"){2} ls32) |
                  (((h16 ":"){3} h16)? "::" (h16 ":"){1} ls32) |
                  (((h16 ":"){4} h16)? "::"              ls32) |
                  (((h16 ":"){5} h16)? "::"              h16 ) |
                  (((h16 ":"){6} h16)? "::"                  );
    IPvFuture = "v" xdigit+ "." (unreserved | sub_delims | ":")+;
    IP_literal = "[" (IPv6address | IPvFuture) "]";
    reg_name = (unreserved | pct_encoded | sub_delims)*;
    host = IP_literal | IPv4address | reg_name;
    port = digit*;

    authority = ( (userinfo %save_userinfo "@")? host >markh %save_host (":" port >markh %save_port)? ) >markh;

    action save_segment
    {
        mark = NULL;
    }

    action save_path
    {
            //std::cout << std::string(mark, fpc - mark) << std::endl;
        uri->setPath(std::string(mark, fpc - mark));
        mark = NULL;
    }


#    pchar = unreserved | pct_encoded | sub_delims | ":" | "@";
# add (any -- ascii) support chinese
    pchar         = ( (any -- ascii ) | unreserved | pct_encoded | sub_delims | ":" | "@" ) ;
    segment = pchar*;
    segment_nz = pchar+;
    segment_nz_nc = (pchar - ":")+;

    action clear_segments
    {
    }

    path_abempty = (("/" segment))? ("/" segment)*;
    path_absolute = ("/" (segment_nz ("/" segment)*)?);
    path_noscheme = segment_nz_nc ("/" segment)*;
    path_rootless = segment_nz ("/" segment)*;
    path_empty = "";
    path = (path_abempty | path_absolute | path_noscheme | path_rootless | path_empty);

    action save_query
    {
        //std::cout << std::string(mark, fpc - mark) << std::endl;
        uri->setQuery(std::string(mark, fpc - mark));
        mark = NULL;
    }
    action save_fragment
    {
        //std::cout << std::string(mark, fpc - mark) << std::endl;
        uri->setFragment(std::string(mark, fpc - mark));
        mark = NULL;
    }

    query = (pchar | "/" | "?")* >marku %save_query;
    fragment = (pchar | "/" | "?")* >marku %save_fragment;

    hier_part = ("//" authority path_abempty > markh %save_path) | path_absolute | path_rootless | path_empty;

    relative_part = ("//" authority path_abempty) | path_absolute | path_noscheme | path_empty;
    relative_ref = relative_part ( "?" query )? ( "#" fragment )?;

    absolute_URI = scheme ":" hier_part ( "?" query )? ;
    # Obsolete, but referenced from HTTP, so we translate
    relative_URI = relative_part ( "?" query )?;

    URI = scheme ":" hier_part ( "?" query )? ( "#" fragment )?;
    URI_reference = URI | relative_ref;
    main := URI_reference;
    write data;
}%%

Uri::ptr Uri::Create(const std::string& uristr) {
    Uri::ptr uri(new Uri);
    int cs = 0;
    const char* mark = 0;
    %% write init;
    const char *p = uristr.c_str();
    const char *pe = p + uristr.size();
    const char* eof = pe;
    %% write exec;
    if(cs == uri_parser_error) {
        return nullptr;
    } else if(cs >= uri_parser_first_final) {
        return uri;
    }
    return nullptr;
}

Uri::Uri()
    :m_port(0) {
}

bool Uri::isDefaultPort() const {
    if(m_port == 0) {
        return true;
    }
    if(m_scheme == "http"
            || m_scheme == "ws") {
        return m_port == 80;
    } else if(m_scheme == "https"
            || m_scheme == "wss") {
        return m_port == 443;
    }
    return false;
}

const std::string& Uri::getPath() const {
    static std::string s_default_path = "/";
    return m_path.empty() ? s_default_path : m_path;
}

int32_t Uri::getPort() const {
    if(m_port) {
        return m_port;
    }
    if(m_scheme == "http"
        || m_scheme == "ws") {
        return 80;
    } else if(m_scheme == "https"
            || m_scheme == "wss") {
        return 443;
    }
    return m_port;
}

std::ostream& Uri::dump(std::ostream& os) const {
    os << m_scheme << "://"
       << m_userinfo
       << (m_userinfo.empty() ? "" : "@")
       << m_host
       << (isDefaultPort() ? "" : ":" + std::to_string(m_port))
       << getPath()
       << (m_query.empty() ? "" : "?")
       << m_query
       << (m_fragment.empty() ? "" : "#")
       << m_fragment;
    return os;
}

std::string Uri::toString() const {
    std::stringstream ss;
    dump(ss);
    return ss.str();
}

Address::ptr Uri::createAddress() const {
    auto addr = Address::LookupAnyIPAddress(m_host);
    if(addr) {
        addr->setPort(getPort());
    }
    return addr;
}

}

main.cpp

#include "sylar/uri.h"
#include <iostream>

int main(int argc,char** argv){
    sylar::Uri::ptr uri = sylar::Uri::Create("http://
        admin@www.sylar.top/test/中文/uri?id=100&name=sylar&vv=中文#frg中文")
    std::cout<<uri->toString()<<std::endl;
    auto addr = uri ->createAddress();
    std::cout<<*addr<<std::endl;
    return0;
}

输出:

http://admin@www.sylar.top/test/中文/uri?id=100&name=sylar&vv=中文#frg中文
2023-10-08 15:35:43     11969   UNKNOW  0       [INFO]  [system]        sylar/address.cpp:109   2068341799
2023-10-08 15:35:43     11969   UNKNOW  0       [INFO]  [system]        sylar/address.cpp:109   2068341799
2023-10-08 15:35:43     11969   UNKNOW  0       [INFO]  [system]        sylar/address.cpp:109   2068341799
39.100.72.123:80(ip+端口)

参考:

URI和URL的区别比较与理解

sylar

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值