错误页面展示
void send_error(int cfd, int status, char *title, char *text)
{
char buf[4096] = {0};
sprintf(buf, "%s %d %s\r\n", "HTTP/1.1",status, title);
sprintf(buf+strlen(buf), "Content-Type: %s\r\n", "text/html");
sprintf(buf+strlen(buf), "Content-Length: %d\r\n", -1);
sprintf(buf+strlen(buf), "Connection: close\r\n");//首部
send(cfd, "\r\n", 2, 0);
memset(buf, 0, sizeof(buf));
sprintf(buf, "<html><head><title>%d %s</title></head>\n", status, title);
sprintf(buf+strlen(buf),"<body bgcolor = \"#cc99cc\"><h4 align = \"center\"%d %s></h4>\n", status, title);
sprintf(buf+strlen(buf), "%s\n", text);
sprintf(buf+strlen(buf), "<hr>\n</body>\n</html>\n");//hr和br不用成对出现,br是横线
send(cfd, buf, strlen(buf), 0);
return;
}
//需要报错即send error报错的地方调用函数即可,到浏览器显示页面也是需要封装首部的send_error(cfd, 404, "Not Found", "No such file or directly")
;
显示为👇
浏览器请求目录
先判断是目录还是文件,是普通文件就发送http首部+发送文件内容,如果是**目录文件*就发送http首部+目录内容;关于目录,要注意如果拿到的目录项还是个目录的话,就需要完成目录和目录的子目录项拼接
//递归遍历目录,获取文件内容
//快捷遍历目录scandir(),服务器端,可以使用文件操作时“递归遍历目录”的源码,实现遍历目录内文件名,回显给浏览器
//拼接
//http请求处理----->获取文件信息,判断是目录还是文件,分别封装发送消息报头,发送文件内容,发送目录内容的函数
void http_request(const char *request, int cfd)
{
//拆分http请求行
char method[12], path[1024], protocol[12];
sscanf(request, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
printf("method = %s, path = %s, protocol = %s\n", method, path, protocol);
//转码,将不能识别的中文乱码--->中文
//解码:%23 %34 %5f
decode_str(path, path);
char* file = path+1;//考虑目录项之间的/---->去掉path中的/获取访问文件名
//如果没有指定访问的资源,默认显示资源目录中的内容
if(strcmp(path, "/") == 0)
{
//file的值,资源目录的当前位置
file = "./";
}
//获取文件属性
struct stat st;
int ret = stat(file, &st);
if(ret == -1)
{
send_error(cfd, 404, "Not found", "No such file or directory");
return;
}
//判断是目录还是文件
if(S_ISDIR(st.st_mode))//是目录
{
send_respond_head(cfd, 200, "ok", get_file_type(".html"), -1);//发送头信息
send_dir(cfd, file);//发送目录信息
}else if(S_ISREG(st.st_mode))//是普通文件
{
send_respond_head(cfd, 200, "ok", get_file_type(file), st.st_size);
send_file(cfd, file);
}
}
//发送目录内容⭐❗
void send_dir(int cfd, const char* dirname)
{
int i, ret;
//拼一个html页面<table></table>
char buf[4049] = {0};
sprintf(buf, "<html><head><title>目录名:%s</title></head>", dirname);
sprintf(buf+strlen(buf), "<body><hl>当前目录:%s</hl><table>", dirname);
char enstr[1024] = {0};
char path[1024] = {0};
//目录项二级指针
struct dirent** ptr;//dirent不仅仅指向目录,还指向目录中的具体文件,
int num = scandir(dirname, &ptr, NULL, alphasort);
//遍历
for(i = 0; i<num; ++i)
{
char *name = ptr[i]->d_name;
//拼接文件的完整路径
sprintf(path, "%s/%s", dirname, name);
printf("path = %s =============================\n", path);
struct stat st;
stat(path, &st);
encode_str(enstr, sizeof(enstr), name);
//如果是文件
if(S_ISREG(st.st_mode))
{
sprintf(buf+strlen(buf),
"<tr><td><a href = \"%s\">%s</a></td><td>%ld</td></tr>",
enstr, name, (long)st.st_size);
}else if(S_ISDIR(st.st_mode))//如果是目录
{
sprintf(buf+strlen(buf),
"<tr><td><a herf = \"%s/\">%s/</a></td><td>%ld</td></tr>",
enstr, name, (long)st.st_size);
}
ret = send(cfd, buf, strlen(buf), 0);
if(ret == -1)
{
if(errno == EAGAIN)
{
perror("send error: ");
continue;
}else if(errno == EINTER)
{
perror("send error");
continue;
}else
{
perror("send error:");
exit(1);
}
}
memset(buf, 0, sizeof(buf));
}
sprintf(buf+strlen(buf), "</table></body></html>");
send(cfd, buf, strlen(buf),0);
#if 0
//打开目录
DIR* dir = opendir(dirname);
if(dir == NULL)
{
perror("opendir error");
exit(1);
}
//读目录
struct dirent *ptr = NULL;
while((ptr = readdir(dir)) != NULL)
{
char *name = ptr->d_name;
}
closedir(dir);
#endif
}
//啊好麻烦,一不小心就gg
通过文件名获取文件的类型
const char *get_file_type(const char *name)
{
char* dot;
//自右向左查找“.”字符,如不存在返回NULL
dot = strrchr(name, '.');//查找字符在指定字符串中从右面开始的第一次出现的位置,如果成功,返回该字符以及其后面的字符
if(dot == NULL)
return "text/plain; charset = utf-8";
if(strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
returm "text/html; charset = utf-8";
if(strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
returm "image/jpeg";
...
...
}
汉字编码和解码 unicode
访问带有汉字的文件时,将这个URL复制到新的浏览器地址中,可以看到它所对应的再浏览器中使用的字符编码
在访问带有汉字的文件时,应该在服务器回发数据给浏览器时进行“编码”操作,在浏览器请求资源目录的汉字文件时进行“解码”操作
服务器给浏览器发送–>编码
void encode_str(char *to, int tosize, const char* from)
{
int tolen;
for(tolen = 0; *from != '\0'&&tolen+4 < tosize; ++from)
{
if(isalnum(*from) || strchr("/_.-~",*from) != (char*)0)//isalnum判断字符变量c是否为字母或数字,若是则返回非零,否则返回零。
{
*to= *from;
++to;
++tolen;
}else{
sprintf(to, "%%%02x", (int)*from & 0xff);
to += 3;
tolen += 3;
}
}
*to = '\0';
}
借助telnet调试
可使用telnet命令,借助IP和port,模拟浏览器行为,在终端中对访问的服务器进行调试,方便查看服务器回发给浏览器的http协议数据
使用方法:telnet 127.0.0.1 9999
回车,手动写入http请求协议
,如GET /hello.c http/1.1 回车
此时,终端可查看到服务器回发给浏览器的http应答协议及数据内容,可根据该信息进行调试
跟ssh一样地位,一个是cs模型,一个是bs模型,ssh更安全更稳定