/* CSC209F
* Assignment #4 Sample Solution
* Author: W. James MacLean
*
* This program is written in a single file to make it easier to put on the
* web site, however I've marked places where it would be broken into header (.h)
* and .c files.
*
* To compile on CDF, use the following command:
*
* gcc -o webServe webServe.c -L/local/lib -lbind -lnsl -lsocket -lpthread
*
*/
/** webServe.h **/
/* this line prevents warning messages when using strtok_r() */
#define _REENTRANT
#define BASE_PORT 3000
#define SERVER_NAME "myServer/0.99"
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 256
#endif
#define STATS_INTERVAL 300
#define MAX_REQUEST_SIZE 4096
#define MAX_FILENAME_SIZE 1024
#define MAX_DATE_SIZE 128
#define HTTP_VER "HTTP/1.0"
#define STATUS_OK 200
#define STATUS_OK_MSG "OK"
#define STATUS_NOTFOUND 404
#define STATUS_NOTFOUND_MSG "File Not Found"
#define STATUS_RELOCATE 301
#define STATUS_RELOCATE_MSG "Moved Permanently"
#define HTML_NOTFOUND "
404 File Not Found404 File Not Found
The file you have requested was not found on this server"
#define HTML_RELOCATE "301 Moved PermanentlyMoved Permanently
"
/* Function Prototypes */
void *ServeFile (void *p);
void SendFile (int fd, char *fileName);
void SendRelocate(int fd, char *newLoc);
void SendNotFound(int fd);
/** webServe.c **/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* #include "webServe.h" */
/***************/
/* Global Vars */
/***************/
long nRequests = 0 ; /* statistics, must be global so threads can access them */
long nFiles = 0 ;
long nBytes = 0 ;
/* mutexes */
pthread_mutex_t statsMutex ; /* must be global, see above */
/* server info */
char rootPath[MAX_FILENAME_SIZE] ; /* global mostly for convenience */
char hostName[MAXHOSTNAMELEN] ;
int portNum ;
int main(int argc, char *argv[])
{
int listenfd ;
int acceptfd ;
int sockopt = 1 ;
struct sockaddr_in self,
peer = { AF_INET } ;
struct timeval timeout ;
time_t nextStatsTime ;
fd_set readset ;
int result ;
int addrLen ;
pthread_t threadID ;
if (getwd(rootPath) == 0)
{
fprintf(stderr, "Unable to get working directory (%s)\n", rootPath);
exit(1);
}
/* read command line parameters */
portNum = BASE_PORT + getuid() ;
{
int i = 1;
while (i < argc)
{
if (strcmp(argv[i], "-p") == 0)
{
if (i == (argc -1))
{
fprintf(stderr,"Expected parameter after '-p'\n");
exit(1);
}
i++ ;
portNum = htons(atoi(argv[i]));
if (portNum == 0)
{
fprintf(stderr,"Invalid port number!\n");
exit(1);
}
}
else
{
memset(rootPath, 0, MAX_FILENAME_SIZE);
strncpy(rootPath,argv[i], MAX_FILENAME_SIZE);
}
i++ ;
}
}
/* disable SIGPIPE & do some initialization */
signal(SIGPIPE, SIG_IGN);
pthread_mutex_init(&statsMutex, (pthread_mutexattr_t *)NULL);
/*************************/
/* set up listening port */
/*************************/
/* Get our host name */
/* The following 6 lines of code require that the libraries */
/* in the compile line be listed in the exact order as shown */
/* at the top of this file. Why? Trust me, you don't want to */
/* know ... wjm */
gethostname(hostName, MAXHOSTNAMELEN);
{
char **q ;
struct hostent *he = gethostbyname(hostName);
strcpy(hostName, he->h_name);
}
self.sin_family = AF_INET ;
self.sin_port = htons(portNum);
fprintf(stdout, "Listening on port %d.\n", portNum);
/* create socket */
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1)
{
fprintf(stderr, "Unable to create listening socket (%s)!\n", strerror(errno));
exit(1);
}
/* allow reuse of socket port */
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
(char *)&sockopt, sizeof(sockopt)) == -1)
{
printf("Warning: unable to set socket option SO_REUSEADDR on listening socket:\n");
printf("%s\n", strerror(errno));
exit(1);
}
/* bind socket */
if (bind(listenfd, (struct sockaddr *)&self, sizeof(self)) == -1)
{
fprintf(stderr, "Unable to bind listening socket (%s)!\n", strerror(errno));
exit(1);
}
/* listen on socket, allow queue of 10 connections */
if (listen(listenfd, 10) == -1)
{
fprintf(stderr, "Unable to listen on socket (%s)!\n", strerror(errno));
exit(1);
}
/*****************************************/
/* wait for connections, and print stats */
/*****************************************/
nextStatsTime = time(NULL) + STATS_INTERVAL ;
for (;;)
{
/* calc next stats report time */
timeout.tv_sec = nextStatsTime - time(NULL);
if (timeout.tv_sec < 0) timeout.tv_sec = 0 ;
timeout.tv_usec = 0 ;
/* set up required file descriptors, and call select() */
/* Note that since the call to select blocks, the server doesn't */
/* waste CPU time while it's idle */
FD_ZERO(&readset);
FD_SET(listenfd, &readset);
result = select(listenfd+1, &readset, NULL, NULL, &timeout);
if (result == -1) /* handle error */
{
fprintf(stderr, "Error in select (%s)\n", strerror(errno));
exit(1);
}
if (result == 0) /* print stats */
{
pthread_mutex_lock(&statsMutex);
fprintf(stdout, "#Reqs = %5d, #files = %5d, #bytes = %9d\n",
nRequests, nFiles, nBytes);
pthread_mutex_unlock(&statsMutex);
nextStatsTime = time(NULL) + STATS_INTERVAL ;
continue ; /* go back to select() and wait for next connection */
}
/* if we get here, we must have a connection waiting */
addrLen = sizeof(peer);
acceptfd = accept(listenfd, (struct sockaddr *)&peer, &addrLen);
if (acceptfd == -1)
{
fprintf(stderr, "Error accepting connection (%s)!\n", strerror(errno));
continue ;
}
fprintf(stdout,"Connection received ... %s:%u\n",
inet_ntoa(peer.sin_addr),
ntohs(peer.sin_port));
result = pthread_create(&threadID, (pthread_attr_t *)NULL,
ServeFile, (void *)acceptfd);
if (result != 0)
fprintf(stderr, "Unable to create thread!! (%s)\n", strerror(result));
}
}
/* converts string s to uppercase */
void MakeUpper(char *s)
{
while (*s){ *s = toupper(*s); s++ ; }
}
void *ServeFile(void *p)
{
int fd = (int)p ;
char reqBuf [MAX_REQUEST_SIZE ] ;
char fileName[MAX_FILENAME_SIZE] ;
char *s, *last ;
int result ;
result = read(fd, reqBuf, MAX_REQUEST_SIZE -1);
if (result == 0) return NULL ;
reqBuf[result] = 0 ; /* make sure buffer is NULL terminated */
/* get file name */
s = strtok_r(reqBuf, " ", &last);
MakeUpper(s); assert(strcmp(s, "GET") == 0);
s = strtok_r((char *)NULL, " ", &last);
strcpy(fileName, s);
s = strtok_r((char *)NULL, " \r\n", &last);
MakeUpper(s);
assert((strcmp(s, "HTTP/1.0") == 0) ||
(strcmp(s, "HTTP/1.1") == 0));
SendFile(fd, fileName);
return NULL ;
}
/* writes "theTime" into "buf" in appropriate form for client,
* and converts to GMT first
*/
void GMTime(char *buf, time_t theTime)
{
struct tm res ;
gmtime_r(&theTime, &res);
strftime(buf, MAX_DATE_SIZE, "%a, %d %b %Y %Tq", &res);
}
void GetFileType(const char *fileName, char *fileType)
{
const char *p = fileName + strlen(fileName) - 1 ;
char q[MAX_FILENAME_SIZE];
while ((*p != '.') && (p != fileName)) p-- ;
strcpy(q, p);
MakeUpper(q);
if (strcmp(q,".GIF" ) == 0) strcpy(fileType,"image/gif");
else if (strcmp(q,".JPG" ) == 0) strcpy(fileType,"image/jpeg");
else if (strcmp(q,".HTML") == 0
|| strcmp(q,".HTM") == 0) strcpy(fileType,"text/html");
else strcpy(fileType,"text/plain");
}
void SendFile(int fd, char *fileName)
{
struct stat fileStats ;
char fullName[MAX_FILENAME_SIZE] ;
strcpy(fullName, rootPath);
strcat(fullName, fileName);
if (stat(fullName, &fileStats) == -1)
{
if (errno == ENOENT)
SendNotFound(fd);
}
if ( (fileStats.st_mode & S_IFMT) == S_IFDIR )/* is it a directory? */
{
if ( fileName[strlen(fileName)-1] != '/') /* need to append trailing slash */
{
strcat(fileName,"/");
SendRelocate(fd, fileName);
}
else
{
char newFile[MAX_FILENAME_SIZE] ;
strcpy(newFile, fileName);
strcat(newFile, "index.html");
SendFile(fd, newFile);
}
}
else /* send file */
{
int fd1 ;
fd1 = open(fullName, O_RDONLY);
if (fd1 == -1) SendNotFound(fd);
else
{
char c ;
long bytesMoved = 0 ;
int result ;
char msg [MAX_REQUEST_SIZE] ;
char timeBuf [MAX_DATE_SIZE] ;
char lastMod [MAX_DATE_SIZE] ;
char fileType[MAX_FILENAME_SIZE] ;
GetFileType(fileName, fileType);
GMTime(timeBuf, time(NULL));
GMTime(lastMod, fileStats.st_mtime);
sprintf(msg,"%s %d %s\r\nDate: %s GMT\r\nServer: %s\r\nConnection: close\r\n"
"Content-Length: %d\r\nLast-Modified: %s GMT\r\nContent-Type: %s\r\n\r\n",
HTTP_VER,
STATUS_OK,
STATUS_OK_MSG,
timeBuf,
SERVER_NAME,
fileStats.st_size,
lastMod,
fileType);
write(fd, msg, strlen(msg));
while (read(fd1, &c, 1))
if (write(fd, &c, 1) != 1)
break ; /* connection broken */
else
bytesMoved++ ;
close(fd1);
close(fd);
/* update stats */
pthread_mutex_lock(&statsMutex);
nRequests++ ;
nFiles++ ;
nBytes += bytesMoved ;
pthread_mutex_unlock(&statsMutex);
}
}
}
void SendRelocate(int fd, char *newLoc)
{
char msg[MAX_REQUEST_SIZE] ;
char timeBuf[MAX_DATE_SIZE] ;
GMTime(timeBuf, time(NULL));
sprintf(msg, "%s %d %s\r\nDate: %s GMT\r\nServer: %s\r\nLocation: %s\r\n"
"Connection: close\r\nContent-Type: text/html\r\n\r\n"
"301 Permanently Moved"
"Permanently Moved
"
"The webpage you are looking for has permanently moved to "
"http://%s:%d%s",
HTTP_VER,
STATUS_RELOCATE,
STATUS_RELOCATE_MSG,
timeBuf,
SERVER_NAME,
newLoc,
hostName, portNum, newLoc,
hostName, portNum, newLoc);
write(fd, msg, strlen(msg));
close(fd);
/* update stats */
pthread_mutex_lock(&statsMutex);
nRequests++ ;
pthread_mutex_unlock(&statsMutex);
}
void SendNotFound(fd)
{
char msg [MAX_REQUEST_SIZE] ;
char timeBuf[MAX_DATE_SIZE ] ;
memset(timeBuf, 0, sizeof(timeBuf));
GMTime(timeBuf, time(NULL));
sprintf(msg, "%s %d %s\r\nDate: %s GMT\r\nServer: %s\r\nConnection: close\nContent-Type: text/html\r\n\r\n%s",
HTTP_VER,
STATUS_NOTFOUND,
STATUS_NOTFOUND_MSG,
timeBuf, SERVER_NAME,
HTML_NOTFOUND);
write(fd, msg, strlen(msg));
close(fd);
/* update stats */
pthread_mutex_lock(&statsMutex);
nRequests++ ;
pthread_mutex_unlock(&statsMutex);
}