实现一个简单的Web(4)——(3)的补充:增加错误页面展示、浏览器请求目录的处理⛽、通过文件名获取文件的类型、汉字编码和解码、 借助telnet调试

错误页面展示

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更安全更稳定

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值