您的位置:首页 > 理论基础 > 计算机网络

自己动手写HTTP服务--myhttpd

2013-03-26 23:01 176 查看
/** 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章
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: