自己动手写HTTP服务--myhttpd


/** sol12.5.c
 ** ------------------------------------------------------------
	A version of webserv that puts some typical CGI
	variables into the environment before calling
	exec is
	sol12.5.c.
	Three kinds of variables are demonstrated in this solution: 
	      variables about the server,
	      variables sent as part of the http header,
	  and variables about the client.

 ** ------------------------------------------------------------
 **
 **
 *   Version of webserv.c that includes some environment variables
 *   when running CGI programs.
 *
 *   The variables included for cgi programs are:
 *      SERVER_SOFTWARE
 *      SERVER_NAME
 *      REMOTE_PORT
 *      REMOTE_ADDR
 *      REQUEST_URI
 *      REQUEST_METHOD
 *      HTTP_USER_AGENT
 *
 *   This program uses the varlib.c system from the smsh.c
 *   program in the shell Chapter.   That system does most
 *   of the filing and export work for us.
 *
 *      usage: ws portnumber
 *   features: supports the GET command only
 *             runs in the current directory
 *             forks a new child to handle each request
 *             has MAJOR security holes, for demo purposes only
 *             has many other weaknesses, but is a good start
 *
 *      build: cc sol12.5.c socklib.c varlib.c -o sol12.5
 *
 *      By showing how easily we can use varlib.c to manage the
 *      variables for the web server, we see in concrete terms
 *      how much a web browser work a shell and OS.

 */
#include	<stdio.h>
#include	<stdlib.h>
#include	<unistd.h>
#include	<fcntl.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<string.h>
#include	<sys/socket.h>
#include	<netinet/in.h>
#include	<arpa/inet.h>
#include	<errno.h>
#include	<time.h>
#include	<errno.h>

#include	"varlib.h"
#include	"socklib.h"

#define	oops(s,x) { perror(s); exit(x); }

void	setupvars();
void	process_request(int , struct sockaddr_in *);
void	process_header(FILE *);
void	act_on_request(char *, char *, int );
void	header( FILE *, char *);
void	cannot_do(int );
void	do_404(char *, int );
int	isadir(char *);
int	not_exist(char *);
void	do_ls(char *, int );
char	* file_type(char *);
int	ends_in_cgi(char *);
void	do_exec( char *, int );
void	do_cat(char *, int );

int main(int ac, char *av[])
{
	int 		   sock, fd;
	int		   len;
	struct sockaddr_in clnt_addr;

	if ( ac == 1 ){
		fprintf(stderr,"usage: ws portnum\n");
		exit(1);
	}
	sock = make_server_socket( atoi(av[1]) );
	if ( sock == -1 ) 
		oops("socket", 2);

	setupvars();

	/* main loop here */

	while(1){
		/*
		 * wait for a call
		 */
		len = sizeof(struct sockaddr);
		fd = accept(sock, (struct sockaddr *)&clnt_addr, &len);

		/* 
		 * take a call and buffer it
		 */ 
		if ( fd >= 0 ){
			process_request(fd, &clnt_addr);
			close(fd);
		}
		else
			perror("accept");
	}
	return 0;
}

/*
 * load the environment table from environ and
 * set some fixed values that describe this host and server
 */

void setupvars()
{
	char	hostname[512];

	VLstore("SERVER_SOFTWARE", "Simple-WebServer 0.2");
	VLexport("SERVER_SOFTWARE");
	gethostname(hostname, 512);
	VLstore("SERVER_NAME", hostname);
	VLexport("SERVER_NAME");
	/*
	   add here other fixed values that can be 
	   computed at startup
	*/
}

/*
 * A more general process_request than the earlier version
 * This one has to read the header and put some parts into
 * the environment list.
 */
void process_request(int fd, struct sockaddr_in *caller)
{
	FILE	*fpin;
	char	request[BUFSIZ];
	char	portnumstr[10];
	char	cmd[BUFSIZ], arg[BUFSIZ];

	/* process the request in a child process */

	if ( fork() != 0 )
		return;

	/* jot down the caller's adress and port */

	VLstore("REMOTE_ADDR", inet_ntoa(caller->sin_addr));
	VLexport("REMOTE_ADDR");
	sprintf(portnumstr, "%d", ntohs(caller->sin_port));
	VLstore("REMOTE_PORT", portnumstr);
	VLexport("REMOTE_PORT");

	/* buffer the socket for input and read request */

	fpin = fdopen(fd, "r" );
 	if ( fpin == NULL )
		exit(2);
	fgets(request,BUFSIZ,fpin);
	printf("got a call: request = %s", request);
	process_header(fpin);

	/* then do the request */

	strcpy(arg, "./");
	if ( sscanf(request,"%s%s", cmd, arg+2) == 2 ){
		VLstore("REQUEST_METHOD", cmd);
		VLexport("REQUEST_METHOD");
		VLstore("REQUEST_URI",    arg+2);
		VLexport("REQUEST_URI");
		act_on_request(cmd, arg, fd);
	}
}

/* ------------------------------------------------------ *
   process_header(FILE *)
   after the HTTP request may be several lines
   of header information.  These variables provide
   define the parameters of the request.
   The set of parameters is terminated by a blank line.

   This function looks for the header line marked

     User-Agent: name-of-browser

   And puts the value for that tag in the environment
   under HTTP_USER_AGENT.  It's pretty easy to add other
   http variables to the CGI environment.  
   ------------------------------------------------------ */

void process_header(FILE *fp)
{
	char	buf[BUFSIZ];
	char	browser_tag[] = "User-Agent: ";
	int	b_taglen = strlen(browser_tag);

	while( fgets(buf,BUFSIZ,fp) != NULL && strcmp(buf,"\r\n") != 0 )
	{
		printf("Header line: %s", buf);
		if ( strncmp(buf, browser_tag, b_taglen) == 0 )
		{
			VLstore("HTTP_USER_AGENT",buf+b_taglen);
			VLexport("HTTP_USER_AGENT");
		}
	}
}
/* ------------------------------------------------------ *
   act_on_request( char *cmd, char *arg, int fd )
   do what the request asks for and write reply to fd 
   if the request is the HTTP command:  GET /foo/bar.html HTTP/1.0
   then  cmd is "GET" and arg is "/foo/bar.html"
   ------------------------------------------------------ */

void act_on_request(char *cmd, char *arg, int fd)
{

	if ( strcmp(cmd,"GET") != 0 )
		cannot_do(fd);
	else if ( not_exist( arg ) )
		do_404(arg, fd );
	else if ( isadir( arg ) )
		do_ls( arg, fd );
	else if ( ends_in_cgi( arg ) )
		do_exec( arg, fd );
	else
		do_cat( arg, fd );
}

/* ------------------------------------------------------ *
   the reply header thing: all functions need one
   if content_type is NULL then don't send content type
   ------------------------------------------------------ */

void header( FILE *fp, char *content_type )
{
	fprintf(fp, "HTTP/1.0 200 OK\r\n");
	if ( content_type )
		fprintf(fp, "Content-type: %s\r\nServer: Myhttpd/0.1\r\n", content_type );//这里有修改标记自己的server版本号
}

/* ------------------------------------------------------ *
   simple functions first:
        cannot_do(fd)       unimplemented HTTP command
    and do_404(item,fd)     no such object
   ------------------------------------------------------ */

void cannot_do(int fd)
{
	FILE	*fp = fdopen(fd,"w");

	fprintf(fp, "HTTP/1.0 501 Not Implemented\r\n");
	fprintf(fp, "Content-type: text/plain\r\n");
	fprintf(fp, "\r\n");

	fprintf(fp, "That command is not yet implemented\r\n");
	fclose(fp);
}

void do_404(char *item, int fd)
{
	FILE	*fp = fdopen(fd,"w");

	fprintf(fp, "HTTP/1.0 404 Not Found\r\n");
	fprintf(fp, "Content-type: text/plain\r\n");
	fprintf(fp, "\r\n");

	fprintf(fp, "The item you requested: %s\r\nis not found\r\n", 
			item);
	fclose(fp);
}

/* ------------------------------------------------------ *
   the directory listing section
   isadir() uses stat, not_exist() uses stat
   do_ls runs ls. It should not
   ------------------------------------------------------ */

int isadir(char *f)
{
	struct stat info;
	return ( stat(f, &info) != -1 && S_ISDIR(info.st_mode) );
}

int not_exist(char *f)
{
	struct stat info;
	return( stat(f,&info) == -1 );
}

void do_ls(char *dir, int fd)
{
	FILE	*fp ;

	fp = fdopen(fd,"w");
	header(fp, "text/plain");
	fprintf(fp,"\r\n");
	fflush(fp);

	dup2(fd,1);
	dup2(fd,2);
	close(fd);
	execlp("ls","ls","-l",dir,NULL);
	perror(dir);
	exit(1);
}

/* ------------------------------------------------------ *
   the cgi stuff.  function to check extension and
   one to run the program.
   ------------------------------------------------------ */

char * file_type(char *f)
/* returns 'extension' of file */
{
	char	*cp;
	if ( (cp = strrchr(f, '.' )) != NULL )
		return cp+1;
	return "";
}

int ends_in_cgi(char *f)
{
	return ( strcmp( file_type(f), "cgi" ) == 0 );
}

void do_exec( char *prog, int fd )
{
	extern char **environ;
	FILE	*fp ;

	fp = fdopen(fd,"w");
	header(fp, NULL);
	fflush(fp);
	dup2(fd, 1);
	dup2(fd, 2);
	close(fd);
	environ = VLtable2environ();
	execl(prog,prog,NULL);
	perror(prog);
}
/* ------------------------------------------------------ *
   do_cat(filename,fd)
   sends back contents after a header
   ------------------------------------------------------ */

void do_cat(char *f, int fd)
{
	char	*extension = file_type(f);
	char	*content = "text/plain";
	FILE	*fpsock, *fpfile;
	int	c;

	if ( strcmp(extension,"html") == 0 )
		content = "text/html";
	else if ( strcmp(extension, "gif") == 0 )
		content = "image/gif";
	else if ( strcmp(extension, "jpg") == 0 )
		content = "image/jpeg";
	else if ( strcmp(extension, "jpeg") == 0 )
		content = "image/jpeg";

	fpsock = fdopen(fd, "w");
	fpfile = fopen( f , "r");
	if ( fpsock != NULL && fpfile != NULL )
	{
		header( fpsock, content );
		fprintf(fpsock, "\r\n");
		while( (c = getc(fpfile) ) != EOF )
			putc(c, fpsock);
		fclose(fpfile);
		fclose(fpsock);
	}
	exit(0);
}

/*
 *	socklib.c
 *
 *	This file contains functions used lots when writing internet
 *	client/server programs.  The two main functions here are:
 *
 *	make_server_socket( portnum )	returns a server socket
 *					or -1 if error
 *      make_server_socket_q(portnum,backlog)
 *
 *	connect_to_server(char *hostname, int portnum)
 *					returns a connected socket
 *					or -1 if error
 */ 

#include	<stdio.h>
#include	<stdlib.h>
#include	<unistd.h>
#include	<sys/types.h>
#include	<sys/socket.h>
#include	<netinet/in.h>
#include	<netdb.h>
#include	<time.h>
#include	<strings.h>
#include	<arpa/inet.h>

#define   HOSTLEN  256
#define	  BACKLOG  1

int make_server_socket_q(int , int );

int make_server_socket(int portnum)
{
	return make_server_socket_q(portnum, BACKLOG);
}
int make_server_socket_q(int portnum, int backlog)
{
	struct  sockaddr_in   saddr;   /* build our address here */
	struct	hostent		*hp;   /* this is part of our    */
	char	hostname[HOSTLEN];     /* address 	         */
	int	sock_id;	       /* the socket             */

	sock_id = socket(PF_INET, SOCK_STREAM, 0);  /* get a socket */
	if ( sock_id == -1 ) 
		return -1;

	/** build address and bind it to socket **/

	bzero((void *)&saddr, sizeof(saddr));   /* clear out struct     */
	if ( gethostname(hostname, HOSTLEN) == -1 )   /* where am I ?   */
	{
		perror("gethostname");
		exit(1);
	}

	hp = gethostbyname(hostname);           /* get info about host  */
	if ( hp == NULL ){
		perror("Cannot get host");
		exit(2);
	}
	                                        /* fill in host part    */
	bcopy( (void *)hp->h_addr, (void *)&saddr.sin_addr, hp->h_length);
	saddr.sin_port = htons(portnum);        /* fill in socket port  */
	saddr.sin_family = AF_INET ;            /* fill in addr family  */
	saddr.sin_addr.s_addr = INADDR_ANY;     /* 作为服务器,你要绑定【bind】到本地的IP地址上进行监听【listen】,
	但是你的机器上可能有多块网卡,也就有多个IP地址,这时候你要选择绑定在哪个IP上面,如果指定为INADDR_ANY,那么系统将绑定默认的网卡【即IP地址】。
	其中INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。
    INADDR_ANY,外部的client ask 从哪个server的地址进来都可以连接到80端口.*/
	if ( bind(sock_id, (struct sockaddr *)&saddr, sizeof(saddr)) != 0 )
	       return -1;

	/** arrange for incoming calls **/

	if ( listen(sock_id, backlog) != 0 ) 
		return -1;
	return sock_id;
}

int connect_to_server(char *host, int portnum)
{
	int sock;
	struct sockaddr_in  servadd;        /* the number to call */
	struct hostent      *hp;            /* used to get number */

	/** Step 1: Get a socket **/

	sock = socket( AF_INET, SOCK_STREAM, 0 );    /* get a line   */
	if ( sock == -1 ) 
		return -1;

	/** Step 2: connect to server **/

	bzero( &servadd, sizeof(servadd) );     /* zero the address     */
	hp = gethostbyname( host );             /* lookup host's ip #   */
	if (hp == NULL) 
		return -1;
	bcopy(hp->h_addr, (struct sockaddr *)&servadd.sin_addr, hp->h_length);
	servadd.sin_port = htons(portnum);      /* fill in port number  */
	servadd.sin_family = AF_INET ;          /* fill in socket type  */

	if ( connect(sock,(struct sockaddr *)&servadd, sizeof(servadd)) !=0)
	       return -1;

	return sock;
}
注意需要加  saddr.sin_addr.s_addr = INADDR_ANY;不然只能用127.0.0.1不能用外网ip


/*
 * header file for socklib
 *	socklib.h
 */int make_server_socket_q(int , int );int make_server_socket(int );int connect_to_server(char *, int );


/* varlib.c
 *
 * a simple storage system to store name=value pairs
 * with facility to mark items as part of the environment
 *
 * interface:
 *     VLstore( name, value )    returns 1 for 0k, 0 for no
 *     VLlookup( name )          returns string or NULL if not there
 *     VLlist()			 prints out current table
 *
 * environment-related functions
 *     VLexport( name )		 adds name to list of env vars
 *     VLtable2environ()	 copy from table to environ
 *     VLenviron2table()         copy from environ to table
 *
 * details:
 *	the table is stored as an array of structs that
 *	contain a flag for `global' and a single string of
 *	the form name=value.  This allows EZ addition to the
 *	environment.  It makes searching pretty easy, as
 *	long as you search for "name=" 
 *
 */

#include	<stdio.h>
#include	<stdlib.h>
#include	"varlib.h"
#include	<string.h>

#define	MAXVARS	200		/* a linked list would be nicer */

struct var {
		char *str;		/* name=val string	*/
		int  global;		/* a boolean		*/
	};

static struct var tab[MAXVARS];			/* the table	*/

static char *new_string( char *, char *);	/* private methods	*/
static struct var *find_item(char *, int);

int VLstore( char *name, char *val )
/*
 * traverse list, if found, replace it, else add at end
 * since there is no delete, a blank one is a free one
 * return 1 if trouble, 0 if ok (like a command)
 */
{
	struct var *itemp;
	char	*s;
	int	rv = 1;

	/* find spot to put it              and make new string */
	if ((itemp=find_item(name,1))!=NULL && (s=new_string(name,val))!=NULL) 
	{
		if ( itemp->str )		/* has a val?	*/
			free(itemp->str);	/* y: remove it	*/
		itemp->str = s;
		rv = 0;				/* ok! */
	}
	return rv;
}

char * new_string( char *name, char *val )
/*
 * returns new string of form name=value or NULL on error
 */
{
	char	*retval;

	retval = malloc( strlen(name) + strlen(val) + 2 );
	if ( retval != NULL )
		sprintf(retval, "%s=%s", name, val );
	return retval;
}

char * VLlookup( char *name )
/*
 * returns value of var or empty string if not there
 */
{
	struct var *itemp;

	if ( (itemp = find_item(name,0)) != NULL )
		return itemp->str + 1 + strlen(name);
	return "";

}

int VLexport( char *name )
/*
 * marks a var for export, adds it if not there
 * returns 1 for no, 0 for ok
 */
{
	struct var *itemp;
	int	rv = 1;

	if ( (itemp = find_item(name,0)) != NULL ){
		itemp->global = 1;
		rv = 0;
	}
	else if ( VLstore(name, "") == 1 )
		rv = VLexport(name);
	return rv;
}

static struct var * find_item( char *name , int first_blank )
/*
 * searches table for an item
 * returns ptr to struct or NULL if not found
 * OR if (first_blank) then ptr to first blank one
 */
{
	int	i;
	int	len = strlen(name);
	char	*s;

	for( i = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ )
	{
		s = tab[i].str;
		if ( strncmp(s,name,len) == 0 && s[len] == '=' ){
			return &tab[i];
		}
	}
	if ( i < MAXVARS && first_blank )
		return &tab[i];
	return NULL;
}


void VLlist()
/*
 * performs the shell's  `set'  command
 * Lists the contents of the variable table, marking each
 * exported variable with the symbol  '*' 
 */
{
	int	i;
	for(i = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ )
	{
		if ( tab[i].global )
			printf("  * %s\n", tab[i].str);
		else
			printf("    %s\n", tab[i].str);
	}
}

int VLenviron2table(char *env[])
/*
 * initialize the variable table by loading array of strings
 * return 1 for ok, 0 for not ok
 */
{
	int     i;
	char	*newstring;

	for(i = 0 ; env[i] != NULL ; i++ )
	{
		if ( i == MAXVARS )
			return 0;
		newstring = malloc(1+strlen(env[i]));
		if ( newstring == NULL )
			return 0;
		strcpy(newstring, env[i]);
		tab[i].str = newstring;
		tab[i].global = 1;
	}
	while( i < MAXVARS ){		/* I know we don't need this	*/
		tab[i].str = NULL ;	/* static globals are nulled	*/
		tab[i++].global = 0;	/* by default			*/
	}
	return 1;
}

char ** VLtable2environ()
/*
 * build an array of pointers suitable for making a new environment
 * note, you need to free() this when done to avoid memory leaks
 */
{
	int	i,			/* index			*/
		j,			/* another index		*/
		n = 0;			/* counter			*/
	char	**envtab;		/* array of pointers		*/

	/*
	 * first, count the number of global variables
	 */

	for( i = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ )
		if ( tab[i].global == 1 )
			n++;

	/* then, allocate space for that many variables	*/
	envtab = (char **) malloc( (n+1) * sizeof(char *) );
	if ( envtab == NULL )
		return NULL;

	/* then, load the array with pointers		*/
	for(i = 0, j = 0 ; i<MAXVARS && tab[i].str != NULL ; i++ )
		if ( tab[i].global == 1 )
			envtab[j++] = tab[i].str;
	envtab[j] = NULL;
	return envtab;
}

/*
 * header for varlib.c package
 * varlib.h
 */

int	VLenviron2table(char **);
int	VLexport(char *);
char	*VLlookup(char *);
void	VLlist();
int	VLstore( char *, char * );
char	**VLtable2environ();
int	VLenviron2table(char **);


#cc socklib.c  varlib.c sol12.5.c -o sol1

#./sol1 80



参考:《Unix Linux编程实践教程》第12章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值