前面对request报文请求行和请求头进行了解析,现在来对uri进行解析。
uri存在于请求行中,Uniform Resource Identifiers统一资源标识符,用于在互联网上标志一个资源,完整的URI主要由以下三种形式:
<scheme>:scehme-specific-part#<fragment>
<scheme>://<authority><path>?<query>#<fragment>
<scheme>://<host:port><path>?<query>#<fragment>
例如:http://www.java2s.com:8080/yourpath/fileName.htm?stove=10&path=32&id=4#harvic
- 基本形式
<scheme>:scehme-specific-part#<fragment>scheme
表示协议,比如http
,ftp
等等。authority
,用://
来和scheme
区分。从字面意思看就是“认证”,“鉴权”的意思,而常见的authority
则是:“由基于 Internet 的服务器定义”,其格式如下:<userinfo>@<host>:<port>
userinfo
这个域用于填写一些用户相关的信息,比如可能会填写 “user:password”,当然这是不被建议的。抛开这个不讲,后面的<host>:<port>
则是被熟知的服务器地址了,host
可以是域名,也可以是对应的 IP 地址,port
表示端口,这是一个可选项,如果不填写,会使用默认端口(也是和协议相关,比如http
协议默认端口是 80)。path
,在scheme
和authority
确定下来的情况下标识资源,path
由几个段组成,每个段用/
来分隔。注意,path
不等同于文件系统定义的路径。
fragment是片段标识符,用于指代一个资源从属于另一个资源,主资源由URI标识,片段标识符指向从属资源。 - 进一步划分
<scheme>://<authority><path>?<query>#<fragment>- path可以有多个,每个用/连接,例如
scheme://authority/path1/path2/path3?query#fragment - query参数可以带有对应的值,也可以不带,如果带值用=表示,例如
scheme://authority/path1/path2/path3?id = 1#fragment,这里有一个参数id,它的值是1 - query参数可以有多个,每个用&连接
scheme://authority/path1/path2/path3?id = 1&name = mingming&old#fragment
- path可以有多个,每个用/连接,例如
- 终极划分
<scheme>://<host:port><path>?<query>#<fragment>
例如,http://www.java2s.com:8080/yourpath/fileName.htm?stove=10&path=32&id=4#harvic
协议使用的是http协议。
host是www.java2s.com,port是8080,8080端口是被用于WWW代理服务的,可以实现网页浏览,经常在访问某个网站或使用代理服务器的时候,会加上“:8080”端口号。
path是/yourpath/fileName.htm,'.'界定文件类型。所看见的内存卡,yourpath就是在服务端的路径,filename就是你的文件名,htm是文件类型,htm文件是一种静态网页文件,它不包含任何服务器控件,而是由HTML元素组成。
query是stove=10&path=32&id=4是立刻拿出来刷空间
fragment
在例如:https://www.ibm.com/developerworks/cn/xml/x-urlni.html#artrelatedtopics
具体的URI解析对应的函数如下:
// 解析URI,获取文件名
parse_uri(request->uri_start, request->uri_end - request->uri_start, filename, NULL);
static void parse_uri(char *uri_start, int uri_length, char *filename, char *query){
uri_start[uri_length] = '\0';
// 找到'?'位置界定非参部分
char *delim_pos = strchr(uri_start, '?');
int filename_length = (delim_pos != NULL) ? ((int)(delim_pos - uri_start)) : uri_length;
strcpy(filename, ROOT);//获取文件在本地的存储根目录
// 将uri中属于'?'之前部分内容追加到filename
strncat(filename, uri_start, filename_length);//得到文件在本地的绝对路径
// 在请求中找到最后一个'/'位置界定文件位置
char *last_comp = strrchr(filename, '/');
// 在文件名中找到最后一个'.'界定文件类型
char *last_dot = strrchr(last_comp, '.');
// 请求文件时末尾加'/'
if((last_dot == NULL) && (filename[strlen(filename) - 1] != '/')){
strcat(filename, "/");
}
// 默认请求index.html,它是网站的默认主页
if(filename[strlen(filename) - 1] == '/'){
strcat(filename, "index.html");
}
return;
}
对于资源的请求处理,存在以下几种错误处理
// 处理相应错误
if(error_proess(&sbuf, filename, fd))
continue;
int error_proess(struct stat* sbufptr, char *filename, int fd){
// 处理文件找不到错误
if(stat(filename, sbufptr) < 0){//找不到资源404
do_error(fd, filename, "404", "Not Found", "TKeed can't find the file");
return 1;
}
// 处理权限错误,是否是一个常规文件,常规文件才能被访问,以及是否能被USR读取
if(!(S_ISREG((*sbufptr).st_mode)) || !(S_IRUSR & (*sbufptr).st_mode)){
do_error(fd, filename, "403", "Forbidden", "TKeed can't read the file");
return 1;
}
return 0;
}
里面用到了一个结构体struct stat和函数stat。下面学习下这个函数。
在了解这两个知识点之前,还应了解inode,文件存储在磁盘时,一部分专门存储文件内容,一部分专门存储文件信息,文件的信息包括:
* 文件的字节数
* 文件拥有者的User ID
* 文件的Group ID
* 文件的读、写、执行权限
* 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。
* 链接数,即有多少文件名指向这个inode
* 文件数据block的位置
每一个inode都有一个号码,操作系统用inode号码来识别不同文件,也就是说linux系统内部使用inode号码来识别文件,文件名只是方便用户使用的文件绰号。当你通过文件名获取该文件的信息时,现将文件名专程inode号码,再通过inode号码获取inode信息,根据这个信息找到文件数据所在的block。查看更多信息。
- stat函数:取得指定文件的文件属性,文件属性存储在结构体stat里。
#include <sys/stat.h> int stat(const char *pathname, struct stat *statbuf); //用来获取pathname的文件信息,并保存在statbuf所指的结构体中 //执行成功返回0,失败返回-1,错误代码存于errno中 //使用示范 #include <sys/stat.h> #include <unistd.h> #include <stdio.h> int main() { struct stat buf; stat("/etc/hosts", &buf); printf("/etc/hosts file size = %d\n", buf.st_size); }
- struct stat结构体
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* Inode number */ mode_t st_mode; /* File type and mode */ nlink_t st_nlink; /* Number of hard links */ uid_t st_uid; /* User ID of owner */ gid_t st_gid; /* Group ID of owner */ dev_t st_rdev; /* Device ID (if special file) */ off_t st_size; /* Total size, in bytes */ blksize_t st_blksize; /* Block size for filesystem I/O */ blkcnt_t st_blocks; /* Number of 512B blocks allocated */ /* Since Linux 2.6, the kernel supports nanosecond precision for the following timestamp fields. For the details before Linux 2.6, see NOTES. */ struct timespec st_atim; /* Time of last access */ struct timespec st_mtim; /* Time of last modification */ struct timespec st_ctim; /* Time of last status change */ #define st_atime st_atim.tv_sec /* Backward compatibility */ #define st_mtime st_mtim.tv_sec #define st_ctime st_ctim.tv_sec };
对于这个结构体,使用最多的属性是st_mode,通过该属性可以对文件进行判断,可以使用以下几个宏来判断。
S_ISLNK(st_mode):是否是一个连接.
S_ISREG(st_mode):是否是一个常规文件.
S_ISDIR(st_mode):是否是一个目录
S_ISCHR(st_mode):是否是一个字符设备.
S_ISBLK(st_mode):是否是一个块设备
S_ISFIFO(st_mode):是否是一个FIFO文件.
S_ISSOCK(st_mode):是否是一个SOCKET文件.
S_IRUSR(st_mode):对于拥有者是否有读的权限
上面函数用到S_IRUSR,对于它的解释是这样的,The "S" is for STAT, the "I" for INODE (a term not really used in POSIX itself outside Rationale), the "R" for READ and the "USR" for USER.