In: Computer Science
Write a C program that accepts a port number as a command line
argument, and starts an HTTP server. This server should constantly
accept() connections, read requests of the form:
GET /path HTTP/1.1\r\n\r\n
read the file indicated by /path, and send it over the "connect"
file descriptor returned by the call to accept().
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include "csapp.h"
#include <pthread.h>
#include <string.h>
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#ifdef BIT32
typedef unsigned long long aint;
#else
typedef unsigned long aint;
#endif
static const char *user_agent = "User-Agent: Mozilla/5.0 (X11; Linux i686; rv:1.9.2.3) Gecko/20100423 Firefox/3.6.3\r\n";
static const char *accept_encoding = "Accept-Encoding: gzip, deflate\r\n";
static const char *accept_0 = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
int proxy(int fd);
int open_clientfd_safe(char *hostname, int port);
int parse_uri(char *uri, char *hostname, int *port);
int read_request(rio_t *rp, char *buf_request, char *hostname, int *port, char *uri);
void *thread(void *vargp);
void initial();
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
sem_t mutex;
static int set_num, line_num;
static void init();
static int loaddata(char *tag, char *response);
static void storedata(char *response, char *tag);
static void pop_usage(int *array, int key, int len);
struct cache_line
{
int valid;
char *tag;
char *block;
};
struct cache_set
{
struct cache_line *line;
int *usage;
};
struct cache
{
struct cache_set *set;
};
static struct cache c;
int main(int argc,char **argv)
{
int listenfd, *connfd, port;
unsigned clientlen;
//struct hostent *hp;
struct sockaddr_in clientaddr;
//char *haddrp;
pthread_t tid;
/* Check command line args */
if (argc != 2)
{
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
port = atoi(argv[1]);
//ignore sigpipe
signal(SIGPIPE, SIG_IGN);
//initialization
initial();
listenfd = Open_listenfd(port);
while(1)
{
connfd = malloc(sizeof(int));
clientlen = sizeof(clientaddr);
*connfd = accept(listenfd, (SA *)&clientaddr, &clientlen);
//hp = Gethostbyaddr((const char *) & clientaddr.sin_addr.s_addr,sizeof(clientaddr.sin_addr.s_addr),AF_INET);
//haddrp = inet_ntoa(clientaddr.sin_addr);
printf("Build new connection! Connfd:%d\n", *connfd);
pthread_create(&tid, NULL, thread, connfd);
}
}
/* Initialization*/
static void init()
{
int i, j;
c.set = malloc(sizeof (struct cache_set) * set_num);
for (i = 0; i < set_num; i++)
{
c.set[i].line = malloc(sizeof (struct cache_line) * line_num);
c.set[i].usage = malloc(sizeof (int) * line_num);
for (j = 0; j < line_num; j++)
{
c.set[i].usage[j] = j;
c.set[i].line[j].valid = 0;
c.set[i].line[j].tag = malloc(MAXLINE);
c.set[i].line[j].block = malloc(MAX_OBJECT_SIZE);
}
}
}
void initial()
{
sem_init(&mutex, 0, 1);
set_num = 1;
line_num = 10;
init();
}
static void pop_usage(int *array, int key, int len)
{
int i, j;
for(i = 0; i < len; i++)
{
if(array[i] == key)
break;
}
for(j = i; j > 0; j--)
{
array[j] = array[j - 1];
}
array[0] = key;
}
static int loaddata(char *tag, char *response)
{
aint set_index = 0;
int i;
for(i = 0; i < line_num; i++)
{
if(c.set[set_index].line[i].valid == 1 && (strcmp(c.set[set_index].line[i].tag, tag) == 0))
{
pop_usage(c.set[set_index].usage,i,line_num);
strcpy(response, c.set[set_index].line[i].block);
break;
}
}
if(i == line_num)
{
return 0;
}
else
{
return 1;
}
}
static void storedata(char *response,char *tag)
{
aint set_index = 0;
int lru;
lru = c.set[set_index].usage[line_num - 1];
strcpy(c.set[set_index].line[lru].tag, tag);
strcpy(c.set[set_index].line[lru].block, response);
if(c.set[set_index].line[lru].valid == 0)
{
c.set[set_index].line[lru].valid = 1;
}
pop_usage(c.set[set_index].usage, lru, line_num);
}
/* Thread*/
void *thread(void *vargp)
{
int connfd = *((int *)vargp);
pthread_detach(pthread_self());
free(vargp);
proxy(connfd);
close(connfd);
return NULL;
}
/*open_clientfd function */
int open_clientfd_safe(char *hostname, int port)
{
int clientfd;
struct hostent *hp;
struct sockaddr_in serveraddr;
if ((clientfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1; /* Check error */
/* Fill in the server’s IP address and port */
P(&mutex);
if ((hp = gethostbyname(hostname)) == NULL)
return -2; /* Check error */
bzero((char *) &serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
bcopy((char *)hp->h_addr_list[0], (char *)&serveraddr.sin_addr.s_addr, hp->h_length);
serveraddr.sin_port = htons(port);
V(&mutex);
/* Establish a connection with the server */
if (connect(clientfd, (SA *) &serveraddr, sizeof(serveraddr)) < 0)
return -1;
return clientfd;
}
/*
* proxy - serve as a proxy to handle one HTTP request/response transaction
*/
int proxy(int connfd)
{
int port, serverfd, len, response_len;
char buf_request[MAX_OBJECT_SIZE], buf_response[MAX_OBJECT_SIZE], hostname[MAXLINE], cache_response[MAX_OBJECT_SIZE], uri[MAXLINE];
rio_t rio_client, rio_server;
/* reset the buffer */
memset(buf_request, 0, sizeof(buf_request));
memset(buf_response, 0, sizeof(buf_response));
memset(hostname, 0, sizeof(hostname));
memset(cache_response, 0, sizeof(cache_response));
memset(uri, 0, sizeof(uri));
/* Read request line and headers */
rio_readinitb(&rio_client, connfd);
printf("read_request in!\n");
if(read_request(&rio_client, buf_request, hostname, &port, uri) < 0)
return -1;
printf("read_request out!\n");
printf("uri:%s\thostname:%s\tport:%d\n", uri, hostname, port);
printf("buf_request: %s\n", buf_request);
if((loaddata(uri, cache_response)) == 1)
{
printf("Cache hit!\n");
if(rio_writen(connfd, cache_response, sizeof(cache_response)) < 0)
{
fprintf(stderr, "cache response error\n");
return -1;
}
/*reset the buffer*/
memset(cache_response, 0, sizeof(cache_response));
}
else
{
printf("Cache miss!\n");
/*send request to real server*/
if((serverfd = open_clientfd_safe(hostname, port)) < 0)
{
fprintf(stderr, "open serverfd error\n");
return -1;
}
rio_readinitb(&rio_server, serverfd);
if(rio_writen(serverfd, buf_request, strlen(buf_request)) < 0)
{
printf("buf_request error:\n%s\n", buf_request);
fprintf(stderr, "rio_writen send request error\n");
close(serverfd);
return -1;
}
/*receive from server and send back to the client */
memset(cache_response, 0, sizeof(cache_response));
response_len = 0;
while((len = rio_readnb(&rio_server, buf_response, sizeof(buf_response))) > 0)
{
printf("hostname:%s\tport:%d\nbuf_response:%s\n", hostname, port, buf_response);
strcat(cache_response, buf_response);
response_len += len;
if(rio_writen(connfd, buf_response, len) < 0)
{
printf("buf_response error:%s\nlen:%d\n%s\n", strerror(errno), len, buf_response);
fprintf(stderr, "rio_writen send response error\n");
close(serverfd);
return -1;
}
/* reset the buffer*/
memset(buf_response, 0, sizeof(buf_response));
}
if(response_len <= MAX_OBJECT_SIZE)
storedata(uri, cache_response);
close(serverfd);
}
return 0;
}
/*
* read_request - read and parse HTTP request headers
*/
int read_request(rio_t *rp, char *buf_request, char *hostname, int *port, char *uri)
{
char buf[MAXLINE], method[MAXLINE];
memset(buf, 0, sizeof(buf));
memset(method, 0, sizeof(method));
/* request line */
if(rio_readlineb(rp, buf, MAXLINE) <= 0)
return -1;
printf("buf: %s\n", buf);
sscanf(buf, "%s %s", method, uri);
/* get hostname and port information*/
parse_uri(uri, hostname, port);
/* fill in request for real server */
sprintf(buf_request, "%s %s HTTP/1.0\r\n", method, uri);
printf("buf_request: %s\n", buf_request);
if(rio_readlineb(rp, buf, MAXLINE) < 0)
{
printf("rio_readlineb fail!\n");
return -1;
}
while(strcmp(buf, "\r\n"))
{
printf("buf: %s\n", buf);
if(strcmp(buf, "\n") == 0)
{
printf("bufn \n");
}
if(strcmp(buf, "\r") == 0)
{
printf("bufr \n");
}
if(strstr(buf, "Host"))
{
strcat(buf_request, "Host: ");
strcat(buf_request, hostname);
strcat(buf_request, "\r\n");
}
else if(strstr(buf, "Accept:"))
strcat(buf_request, accept_0);
else if(strstr(buf, "Proxy-Connection:"))
strcat(buf_request, "Proxy-Connection: close\r\n");
else if(strstr(buf, "User-Agent:"))
strcat(buf_request, user_agent);
else if(strstr(buf, "Accept-Encoding:"))
strcat(buf_request, accept_encoding);
else if(strstr(buf, "Connection:"))
strcat(buf_request, "Connection: close\r\n");
else if(!strstr(buf, "Cookie:"))
/*add addtional header except above headers*/
strcat(buf_request, buf);
memset(buf, 0, sizeof(buf));
if(rio_readlineb(rp, buf, MAXLINE) < 0)
{
fprintf(stderr, "rio_readlineb read request error\n");
return -1;
}
}
strcat(buf_request, "\r\n");
return 0;
}
/*
* parse_uri - parse URI into hostname and port args
*/
int parse_uri(char *uri, char *hostname, int *port)
{
char *ps, *pe, *phost;
char *uricp = malloc(strlen(uri) + 1);
strncpy(uricp, uri, strlen(uri));
/* start pointer */
if((ps = strstr(uricp, "http://")) == NULL)
return -1;
ps += strlen("http://");
/* end pointer */
if((pe = strstr(ps, "/")) == NULL)
return -1;
*pe = '\0';
/*if hostname contains port*/
phost = strsep(&ps, ":");
if(ps == NULL)
*port = 80;
else
*port = atoi(ps);
strncpy(hostname, phost, strlen(phost));
return 0;
}
/*
* clienterror - returns an error message to the client
*/
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)
{
char buf[MAXLINE], body[MAXBUF];
memset(buf, 0, sizeof(buf));
memset(buf, 0, sizeof(body));
/* Build the HTTP response body */
sprintf(body, "<html><title>Tiny Error</title>");
sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);
sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
sprintf(body, "%s<hr><em>The Tiny Web server</em>\r\n", body);
/* Print the HTTP response */
sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-type: text/html\r\n");
rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
rio_writen(fd, buf, strlen(buf));
rio_writen(fd, body, strlen(body));
}