TinyHttp学习

TinyHttp学习

纠结做项目中,太难的做不出来,太简单的也不想做(大废物了属于是)。然后找到了这个很经典的小项目,记录学习情况。因为之前基础很差,所以可能会很细,所以有很多比较蠢的知识也会记录下来。
原地址:(https://www.cnblogs.com/Paranoid5/p/16215852.html)

Day1

先看下需要用到的头文件吧。

#include <stdio.h>
#include <sys/socket.h>//提供socket函数以及数据结构
#include <sys/types.h>//数据类型定义
#include <netinet/in.h>//定义数据结构sockaddr_in
#include <arpa/inet.h>//提供IP地址转换函数
#include <unistd.h>//提供通用的文件、目录程序以及进程的函数
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>//多线程操作的函数
#include <sys/wait.h>//进程等待的函数
#include <stdlib.h>

sockaddr_in和sockaddr

中间提到了两个数据结构sockaddr_in和sockaddr。
这是用来处理网络通信地址的

struct sockaddr{
  _SOCKADDR_COMMON(sa_);//协议族
  char sa_data[14]; //地址+端口号
};

上面是sockaddr,那么sockaddr_in有什么区别呢?

typedef uint32_t in_addr_t;
struct in_addr{
  in_addr_t s_addr;//32位IPV4地址,当然能也有IPV6的
};
struct sockaddr_in{
  _SOCKADDR_COMMON(sin_);//协议族
  in_port_t sin_port;//端口号
  struct in_addr sin_addr;//IP地址
  //下面是用于填充的0字节(暂时没懂)
  unsigned char sin_zero[sizeof (struct sockaddr) -
                            __SOCKADDR_COMMON_SIZE -
                            sizeof (in_port_t) -
                           sizeof (struct in_addr)];
}

主要来说就是sockaddr_in将地址和端口分开了,这样地址和端口就没有混合在一起。

然后再看看函数内容:

#define ISspace(x) isspace((int)(x))
#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"//定义server名称

void accept_request(int);//处理从套接字上监听到的一个 HTTP 请求
void bad_request(int);//返回给客户端这是个错误请求,400响应码

void cat(int, FILE *);//读取服务器上某个文件写到 socket 套接字
void cannot_execute(int);//处理发生在执行 cgi 程序时出现的错误
void error_die(const char *);//把错误信息写到 perror 
void execute_cgi(int, const char *, const char *, const char *);//运行cgi脚本,这个非常重要,涉及动态解析
int get_line(int, char *, int);//读取一行HTTP报文
void headers(int, const char *);//返回HTTP响应头
void not_found(int);//返回找不到请求文件
void serve_file(int, const char *);//调用 cat 把服务器文件内容返回给浏览器。
int startup(u_short *);//开启http服务,包括绑定端口,监听,开启线程处理链接
void unimplemented(int);//返回给浏览器表明收到的 HTTP 请求所用的 method 不被支持

main函数:

int main(){
    int server_sock = -1;//服务端socket接口
    u_short port = 0;
    int client_sock = -1;//已连接socket描述符
    struct sockaddr_in = -1;
    int client_name_len = sizeof client_name;    
    pthread_t newthread;

    //start server socket
    server_sock = startup(&port);
    printf("http running on port %d\n",port);
    while(true){
        //accept client socket
	client_sock = accept( server_sock,
			      (struct sockaddr *)&client_name,
			      &client_name_len);
	if(client_sock == -1) error_die("accept");
	if(pthread_create(&newthread,NULL,accept_request,client_sock) != 0) perror("pthread_create");
     }
    //close server socket
    close(server_sock); 
    return 0;
}

这一部分复习一下http的连接,然后就是学一下C语言的网络编程的函数。

accept函数

函数声明:

int accept(int sockfd,
           struct sockaddr* addr,
           socklen_t *addrlen);

参数:

sockfd:socket描述字,在listen()后监听连接。
addr(缺省):指向缓冲区,其中接受为连接实体的地址。
addrlen(缺省):指向存有addr地址长度的整型数。

返回值:

成功:返回一个socket描述符,专门用于连接成功的客户端进行通信。
失败:返回-1

功能:

从sockfd等待队列中抽取第一个连接,创建一个与sockfd同类的新的socket。

pthread_create函数

函数声明

int pthread_create(pthread_t *tidp,
                   const pthread_attr_t *attr,
                   void *(*start_rtn)(void*),
                   void *arg);

返回值:

成功:返回0
失败:返回出错编号

参数:

pthread_t *tidp:指向线程标识符的指针
const pthread_attr_t *attr:设置线程属性
void *( * start_rtn)(void *):线程运行函数的起始地址
void *arg:运行函数的参数

功能:

创建线程

day2

实现startup

int startup(u_short *port){
	int httpd = 0;
	struct sockaddr_in name;
	//setting http socket
	httpd = socket(PF_INET,SOCK_STREAM,0);
	if(httpd == -1) error_die("socket");
	memset(&name,0,sizeof (name));
	name.sin_family = AF_INET;
	name.sin_port = htons(*port);
	name.sin_addr.s_addr = htonl(INADDR_ANY);
	//binding port
	if(bind(httpd,(struct sockaddr *)&name,sizeof(name)) < 0) error_die("bind");
        //动态随机分配一个端口
	if(*port == 0){
		int namelen = sizeof (name);
		if(getsockname(httpd,(struct sockaddr*)&name,&namelen) == -1) error_die("getsockname");
		*port = ntohs(name.sin_port);
	}
	//listen
	if(listen(httpd,5) < 0) error_die("listen");
	return httpd;
}

1、在服务端创建一个socket
2、将socket绑定到对应的端口上
3、如果当前指定的端口是0,则动态分配一个端口
4、监听请求

sokcet函数

函数声明

int socket(int domain, int type, int protocol);

参数:

domain:
协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。
AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合
在windows中AF_INET与PF_INET完全一样.
在Unix/Linux系统中,在不同的版本中这两者有微小差别.对于BSD,是AF,对于POSIX是PF.(没看懂)
type:
用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字,tcp)、SOCK_DGRAM(数据包套接字,udp)等。
protocol:
制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。

返回值:

成功则返回一个socket,失败返回-1,错误原因存于errno 中。
ps:一个程序最多打开1024个socket。

功能:

创建一个新的socket,也就是向系统申请一个socket资源。

htons(), ntohl(), ntohs(),htons() 函数

这是字节的网络顺序和主机顺序的问题。
下面四种网络字节顺序(NBO)与本地字节顺序(HBO)之间的转换:
htons:"Host to Network Short"
ntohl:"Network to Host Long"
ntohs:"Network to Host Short"
htonl:"Host to Network Long"

bind函数

功能

服务端把用于通信的地址和端口绑定到socket上。

函数声明:

int bind(int sockfd,
 const struct sockaddr *addr,socklen_t addrlen);

参数:

sockfd:需要绑定的socket。
addr:存放了服务端用于通信的地址和端口。
addrlen:表示addr结构体的大小。

返回值:

成功则返回0,失败返回-1,错误原因存于errno 中。

如果绑定的地址错误,或端口已被占用,bind函数一定会报错,否则一般不会返回错误。

listen函数

功能

listen函数把主动连接socket变为被动连接的socket,使得这个socket可以接受其它socket的连接请求,从而成为一个服务端的socket。

函数声明

int listen(int sockfd, int backlog);

返回:

0-成功, -1-失败

参数

sockfd:
是已经被bind过的socket。socket函数返回的socket是一个主动连接的socket,在服务端的编程中,程序员希望这个socket可以接受外来的连接请求,也就是被动等待客户端来连接。由于系统默认时认为一个socket是主动连接的,所以需要通过某种方式来告诉系统,程序员通过调用listen函数来完成这件事。

backlog:
这个参数涉及到一些网络的细节,比较麻烦,填5、10都行,一般不超过30。

当调用listen之后,服务端的socket就可以调用accept来接受客户端的连接请求。

返回值:

成功则返回0,失败返回-1,错误原因存于errno 中。

accept_request函数

void accpet_request(int client){
	char buf[1024];
	int numchars;
	char method[255];//存储请求方法
	char url[255];
	char path[512];
	size_t i,j;
	struct stat st;
	int cgi = 0;
	char *query_string = NULL;
	//得到请求行的第一行
	numchars = get_line(client,buf,sizeof(buf));
	i = 0,j = 0;
        //得到method数组
	while(!ISspace(buf[j]) && (i < sizeof(method) - 1)){
		method[i] = buf[i];
		i++,j++;
	}
	method[i] = '\0';
	//我们只处理post和get方法
	if(strcasecmp(method,"GET") && strcasecmp(method,"GET")){
		unimplemented(client);
		return ;
	}
        //post的时候开启cgi
	if(strcasecmp(method,"POST") == 0)
		cgi = 1;
        //读取url
	i = 0;
	while(ISspace(buf[j]) && (j < sizeof(buf))) j++;	
	while(ISspace(buf[j]) && (j < sizeof(buf)) && (i < sizeof (url)-1)){
		url[i] = buf[j];
		i++;j++;
	}
	url[i] = '\0';	
        //处理get方法
	if(strcasecmp(method,"GET") == 0){
		query_string = url;
		while((*query_string != '?') && (*query_string != '\0')) query_string++;
                //get方法中有'?',开启cgi
		if(*query_string == '?'){
			cgi = 1;
			*query_string = '\0';
			query_stirng++;
		}
	}
	sprintf(path,"htdocs%s",url);

	if(path[strlen(path)-1] == '/') strcat(path,"index.html");
	
	if(stat(path,&st) == -1) {
		//webpage not found 		
		while(numchars > 0 && strcmp("\n",buf)){
			numchars = get_line(client,buf,sizeof(buf));
		}	
		not_found(client);
	}else{
		if((st.st_mode & S_IFMT) == S_IFDIR) strcat(path,"/index.html");
		if((st.st_mode & S_IXUSR) ||
			(st.st_mode & S_IXGRP) ||
			(st.st_mode & S_IXOYH)){
			cgi = 0;
		}
		if(!cgi) serve_file(client,path);
		else execute_cgi(client,path,method,query_string);
	}
	close(client);
}

1、调用get_line()函数得到http请求的请求行
2、get_line完后,就是开始解析第一行,判断是GET方法还是POST方法,目前只支持这两种。
如果是POST,还是把cgi置1,表明要运行CGI程序;
如果是GET方法且附带以?开头的参数时,也认为是执行CGI程序
3、拼接获取要访问的url,可以是很常见的/,/index.html等等。
该程序默认为根目录是在htdocs下的,且默认文件是index.html。
另外还判断了给定文件是否有可执权限,如果有,则认为是CGI程序。最后根据变量cgi的值来进行相应选择:读取静态文件或者执行CGI程序返回结果。
4、返回文件内容(serve_file)或执行cgi程序(execute_cgi)

day3

http头部

HTTP的头域包括通用头,请求头,响应头和实体头四个部分。

通用头域(即通用头)

通用头域包含请求和响应消息都支持的头域,通用头域包含Cache-Control、 Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via。对通用头域的扩展要求通讯双方都支持此扩展,如果存在不支持的通用头域,一般将会作为实体头域处理。

请求消息(请求头)

请求消息的第一行为下面的格式:
Method(方法) Request-URI(请求的url) HTTP-Version(http版本)

响应消息(响应头)

响应消息的第一行为下面的格式:
HTTP-Version(http版本) Status-Code(响应代码,例如100、200) Reason-Phrase(对于响应代码的描述)

实体消息(实体头和实体)

请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成。
实体头域包含关于实体的原信息。

GET和POST方法

GET请求,请求的数据会附加在URL之后,以?分割URL和传输数据,多个参数用&连接。URL的编码格式采用的是ASCII编码,而不是uniclde,即是说所有的非ASCII字符都要编码之后再传输。

POST请求:POST请求会把请求的数据放置在HTTP请求包的包体中。

cgi

一个web服务器主机提供信息服务的标准接口。这是一种根据请求信息动态产生回应内容的技术。服务器可以通过cgi将不同请求启动不同的外部程序,并在程序结束后返回结果给客户端。

数据结构stat和函数stat

struct stat{
  dev_t     st_dev;     /* 文件所在设备的标识  */
  ino_t     st_ino;     /* 文件结点号  */
  mode_t    st_mode;    /* 文件保护模式  */
  nlink_t   st_nlink;   /* 硬连接数  */
  uid_t     st_uid;     /* 文件用户标识  */
  gid_t     st_gid;     /* 文件用户组标识  */
  dev_t     st_rdev;    /* 文件所表示的特殊设备文件的设备标识  */
  off_t     st_size;    /* 总大小,字节为单位  */
  blksize_t st_blksize; /* 文件系统的块大小  */
  blkcnt_t  st_blocks;  /* 分配给文件的块的数量,512字节为单元  */
  time_t    st_atime;   /* 最后访问时间  */
  time_t    st_mtime;   /* 最后修改时间  */
  time_t    st_ctime;   /* 最后状态改变时间  */
};

对于数据类型st_mode我们需要一些其他的宏定义来配合。

文件类型标志包括:
S_IFBLK:文件是一个特殊的块设备
S_IFDIR:文件是一个目录
S_IFCHR:文件是一个特殊的字符设备
S_IFIFO:文件是一个FIFO设备
S_IFREG:文件是一个普通文件
S_IFLNK:文件是一个符号链接

其他模式标志包括:
S_ISUID:文件设置了SUID位
S_ISGID:文件设置了SGID位
S_ISVTX:文件设置了sticky位

用于解释st_mode标志的掩码包括:
S_IFMT:文件类型
S_IRWXU:属主的读/写/执行权限,可以分成S_IXUSR, S_IRUSR, S_IWUSR
S_IRWXG:属组的读/写/执行权限,可以分成S_IXGRP, S_IRGRP, S_IWGRP
S_IRWXO:其他用户的读/写/执行权限,可以分为S_IXOTH, S_IROTH, S_IWOTH

还有一些用于帮助确定文件类型的宏定义,这些和上面的宏不一样,这些是带有参数的宏,类似与函数的使用方法:

S_ISBLK:测试是否是特殊的块设备文件
S_ISCHR:测试是否是特殊的字符设备文件
S_ISDIR:测试是否是目录
S_ISFIFO:测试是否是FIFO设备
S_ISREG:测试是否是普通文件
S_ISLNK:测试是否是符号链接
S_ISSOCK:测试是否是socket

stat()函数:获取文件状态
定义函数

int stat(const char * file_name, struct stat *buf);

函数说明:stat()用来将参数file_name 所指的文件状态, 复制到参数buf 所指的结构中。

day 4

execut_cgi函数

void execute_cgi(int client,const char *path,const char *method,const char *query_string){
	char buf[1024];
	int cgi_output[2];//0,1分别表示读取和写入,下同
	int cgi_input[2];	
	pid_t pid;
	int status;
	int i;
	char c;
	int numchars = 1;
	int content_length = -1;
	buf[0] = 'A';buf[1] = '\0';
        //判断是post还是get协议
	if(strcasecmp(method,"GET") == 0){//GET
		while((numchars > 0) && strcmp("\n",buf)){
			numchars = get_line(client,buf,sizeof(buf));		
		}
	}else{//POST,读取请求长度
		numchars = getline(client,buf,sizeof(buf));
		while((numchars>0 && strcmp("\n",buf))){
			buf[15] = '\0';
			if(strcasecmp(buf,"Content-Length:") == 0){
				content_length = atoi(&(buf[16]));
			}
			numchars = get_line(client,buf,sizeof(buf));
		}
		if(content_length == -1){
			bad_request(client);
			return ;
		}
	}
	// return 200 OK 
	sprintf(buf,"http/1.0 200 OK\r\n");
	send(client,buf,strlen(buf),0);
        //建立管道
	if(pipe(cgi_output) < 0){
		cannot_execute(client);
		return ;
	}
	if(pipe(cgi_input) < 0){
		cannot_execute(client);
		return ;
	}
	if((pid = fork()) < 0){
		cannot_execute(client);
		return ;
	}
	//creat a child thread to run cgi
	if(pid == 0){
		char meth_env[255];
		char query_env[255];
  		char length_env[255];

		dup2(cgi_output[1],1);
		dup2(cgi_input[0],0);

		close(cgi_output[0]);
		close(cgi_input[1]);
	
		sprintf(meth_env,"REQUEST_METHOD=%s",method);
		putenv(meth_env);
		if(strcasecmp(method,"GET") == 0){
                        // 设置query_string的环境变量
			sprintf(query_env,"QUERY_STRING=%s",query_string);
			putenv(query_env);
		}else{
                        // 设置content_length的环境变量
			sprintf(length_env,"CONTENT_LENGTH=%d",content_length);
			putenv(length_env);
		}
		execl(path,path,NULL);
		exit(0);
	}else{
                
		close(cgi_output[1]);
		close(cgi_input[0]);
		//子进程所调用的脚本可以从标准输入获得post数据
                if(strcasecmp(method,"POST") == 0){
			for(i = 0 ;i < content_length;i++){
				recv(client,&c,1,0);
				write(cgi_input[1],&c,1);			
			}
			
		}
		while(read(cgi_output[0],&c,1)>0){
			send(client,&c,1,0);		
		}
		close(cgi_output[0]);
		close(cgi_input[1]);
		waitpid(pid,&status,0);
	}
}

send函数

功能

向一个已经连接的socket发送数据

函数声明:

int send( SOCKET s, const char FAR *buf, int len, int flags );

参数:

s:指定发送端socket描述符
buf:指明一个存放应用程序要发送数据的缓冲区
len:指明实际要发送的数据的字节数
flags:0

返回值

成功:发送数据的总数
失败:SOCKET_ERROR

pipe函数

功能

创建一个管道,实现进程之间的通信

函数声明:

int pipe(int fd[2]);

参数

fd[0]存放管道的读端,fd[1]存放管道的写端。

返回值

若成功,则返回0
若失败,则返回-1,并设置errno

pid_t类型

一个short类型变量,实际上是内核进程表的索引。

fork函数

功能

创建进程

  1. fork()函数产生了一个和当前进程完全一样的新进程 ,并和当前进程一样从fork()函数里返回。
  2. 原来一条执行通路(父进程),现在变成两条(父进程 + 子进程)。

返回值

fork()回返回两次:父进程中返回一次,子进程中返回一次,而且,fork()在父进程中返回的值和在子进程中返回的值是不同的。
子进程的fork()返回值是0;
父进程的fork()返回值是新建立的子进程的ID,因为全局变量g_mygbltest的值发生改变,导致主,子进程内存被单独的分开,所以每个的g_mygbltest值也不同;
失败则直接返回-1

dup/dup2函数

函数声明

int dup(int oldfd);
int dup2(int oldfd, int newfd);

当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。
dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项。

putenv函数

定义函数:

int putenv(const char * string);

函数说明:putenv()用来改变或增加环境变量的内容. 参数string 的格式为name=value, 如果该环境变量原先存在, 则变量内容会依参数string 改变, 否则此参数内容会成为新的环境变量.
返回值:执行成功则返回0, 有错误发生则返回-1.

waitpid(等待子进程中断或结束):

定义函数

pid_t waitpid(pid_t pid,int * status,int options);

函数说明

waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用waitpid()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数status可以设成NULL。参数pid为欲等待的子进程识别码,其他数值意义如下:
pid<-1 等待进程组识别码为pid绝对值的任何子进程。
pid=-1 等待任何子进程,相当于wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为pid的子进程。
返回值 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno。

execl

函数定义

int execl(const char *path, const char *arg, ...)

函数说明 execl()用来执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。

返回值 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。

(这个说实话没有太看懂)

recv函数

用来接收远程主机通过套接字sockfd发送来的数据,并把这些数据保存到数组buf中。

函数声明:

int recv(int sockfd, void *buf, int len, int flags)

参数

sockfd:连接的套接字
buf:接收到的数据保存在数组中
len:数组的长度
flags:设置为0

返回值

0:表示执行成功,返回实际接受的字符个数
= 0:另外一端关闭连接
< 0:执行失败

write函数

获取打开文件的指针位置

int write(int handle,void *buf,int len);

参数:

handle :获取文件指针的文件句柄
*buf :要写入的内容
len :要写入的长度

返回值

返回实际写入文件内容的长度

read函数

获取打开文件的指针位置

int write(int fd,void *buf,int len);

参数:

fd :获取文件指针的文件句柄
*buf :要读取的内容
len :要读取的长度

返回值

返回实际读取文件内容的长度

execute_cgi()函数总结(未完成)

在Get请求下:
读出剩余报文请求头内容并丢弃,直到遇到两个换行符,后面再读就是请求数据。
在Post请求下:
读报文请求头部中的content-length字段,判断是否又有效内容,该字段的值为post请求的数据长度。

day 5

错误请求

返回400状态码

void bad_request(int client){
	char buf[1024];
	sprintf(buf,"HTTP/1.0 400 BAD REQUEST\r\n");
	send(client,buf,sizeof(buf),0);
	sprintf(buf,"Content-type: text/html\r\n");
	send(client,buf,sizeof(buf),0);
	sprintf(buf,"\r\n");
	send(client,buf,sizeof(buf),0);
	sprintf(buf,"<p>Your browser sent a bad request, ");
	send(client,buf,sizeof(buf),0);
	sprintf(buf,"such as a POST without a Content-Length.\r\n");
	send(client,buf,sizeof(buf),0);
}

cat函数

得到文件内容,发送

void cat(int client,FILE *resource){//read file 
	char buf[1024];
	fgets(buf,sizeof(buf),resource);
	while(!feof(resource)){
		send(client,sizeof(buf),strlen(buf),0);
		fgets();
	}
}

feof函数

检查流上的文件结束符,结束返回非0值,否则返回0

get_line函数

得到一行数据,只要发现c为\n,就认为是一行结束,如果读到\r,再用MSG_PEEK的方式读入一个字符,如果是\n,从socket用读出
如果是下个字符则不处理,将c置为\n,结束。如果读到的数据为0中断,或者小于0,也视为结束,c置为\n

int get_line(int sock,char *buf,int size){
	int i = 0;
	char c = '\0';
	int n;
	while((i < size-1) && (c!='\n')){
		n = recv(sock,&c,1,0);
		if(n > 0){
			if(c == '\r'){
				n = recv(sock,&c,1,MSG_PEEK);
				if((n > 0) && (c == '\n')) recv(sock,&c,1,0);
				else c = '\n';
			}	
			buf[i] = c;
			i++;
		}else c = '\n';
	}
	buf[i] = '\0';
	return i;
}

serve_file函数

不是CGI,直接读取文件

void serve_file(int client,const char *filename){//将请求的文件发送回浏览器客户端
	FILE *resource = NULL;
	int numchars = 1;
	char buf[1024];

        //默认字符?这个没太懂
	buf[0] = 'A';buf[1] = '\0';
	while((numbers > 0) && strcmp("\n",buf)){
		numchars = get_line(client,buf,sizeof(buf));
	}
	resource = fopen(filename,"r");
	if(resource == NULL)not_found(client);
	else {
		//http head
		headers(client,filename);
		//send file content
		cat(client,resource);
	}
	fclose(resource);
}

unimplmented函数

//方法没有实现,就返回此信息
void unimplemented(int client){
	char buf[1024];
        //返回501状态码
	sprintf(buf,"HTTP/1.0 501 Method Implemented\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,SERVER_STRING);
	send(client,buf,strlen(buf),0);
	sprintf(buf,"Content-Type: text/html\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"<HTML><HEAD><TITLE>Method Not Implemented\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"</TITLE></HEAD>\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"<BODY><P>HTTP request method not supported.\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"</BODY></HTML>\r\n");
	send(client,buf,strlen(buf),0);
}

not_found函数

返回404状态码

void not_found(int client){//client not found 
	char buf[1024];
 //返回404
 	sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
 	send(client, buf, strlen(buf), 0);
 	sprintf(buf, SERVER_STRING);
 	send(client, buf, strlen(buf), 0);
 	sprintf(buf, "Content-Type: text/html\r\n");
 	send(client, buf, strlen(buf), 0);
 	sprintf(buf, "\r\n");
 	send(client, buf, strlen(buf), 0);
 	sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
 	send(client, buf, strlen(buf), 0);
 	sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
 	send(client, buf, strlen(buf), 0);
 	sprintf(buf, "your request because the resource specified\r\n");
 	send(client, buf, strlen(buf), 0);
 	sprintf(buf, "is unavailable or nonexistent.\r\n");
 	send(client, buf, strlen(buf), 0);
 	sprintf(buf, "</BODY></HTML>\r\n");
 	send(client, buf, strlen(buf), 0);
}

headers函数

返回200状态码

void headers(int client ,const char *filename){
	char buf[1024];
	(void)filename;
	strcpy(buf,"HTTP/1.0 200 OK\r\n");
	send(client,buf,strlen(buf),0);
	strcpy(buf,SERVER_STRING);
	send(client,buf,strlen(buf),0);
	strcpy(buf,"Content-Type: text/html\r\n");
	send(client,buf,strlen(buf),0);
	strcpy(buf,"\r\n");
	send(client,buf,strlen(buf),0);
}

cannot_execute函数

void cannot_execute(int client){
	//发送500
 	sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
 	send(client, buf, strlen(buf), 0);
 	sprintf(buf, "Content-type: text/html\r\n");
 	send(client, buf, strlen(buf), 0);
 	sprintf(buf, "\r\n");
 	send(client, buf, strlen(buf), 0);
 	sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
 	send(client, buf, strlen(buf), 0);
}
void error_die(const char *sc){
	perror(sc);
	exit(1);
}

bad_request函数

void bad_request(int client){
	char buf[1024];
	sprintf(buf,"HTTP/1.0 400 BAD REQUEST\r\n");
	send(client,buf,sizeof(buf),0);
	sprintf(buf,"Content-type: text/html\r\n");
	send(client,buf,sizeof(buf),0);
	sprintf(buf,"\r\n");
	send(client,buf,sizeof(buf),0);
	sprintf(buf,"<p>Your browser sent a bad request, ");
	send(client,buf,sizeof(buf),0);
	sprintf(buf,"such as a POST without a Content-Length.\r\n");
	send(client,buf,sizeof(buf),0);
}
posted @ 2022-05-02 15:02  Paranoid5  阅读(188)  评论(0编辑  收藏  举报