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+端口)