Select Git revision
Anqi Chen authored
new.c 14.28 KiB
/*
** http-server.c
*/
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <strings.h>
#include <sys/select.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX_BUFF 2049
// cookie for user, formtted with userID
static char const * const HTTP_200_FORMAT = "HTTP/1.1 200 OK\r\n\
Content-Length: %ld\r\n\
Content-Type: text/html\r\n\
Set-Cookie: cookie_id= %d\r\n\r\n";
static char const * const HTTP_400 = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n";
static int const HTTP_400_LENGTH = 47;
static char const * const HTTP_404 = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n";
static int const HTTP_404_LENGTH = 45;
typedef enum
{
GET,
POST,
UNKNOWN
} METHOD;
// struct User
struct User {
int userID;
char* username;
char** keywords;
int isOnline; // Online=1, OffLine=0
int stage;
int n_word;
};
// malloc a newUser
struct User* newUser() {
struct User* newUser = malloc(sizeof (struct User));
newUser->userID = 0;
//newUser->username = malloc(MAX_BUFF*sizeof(char));
//newUser->keywords = NULL; newUser->keywords =malloc(MAX_BUFF*sizeof(char*));
for (int i = 0; i < MAX_BUFF; i++)
{
newUser->keywords[i] = NULL;
}
newUser->isOnline = 0;
newUser->stage = 0;
newUser->n_word = 0;
return newUser;
}
void clear_user_keyword(struct User* user){
user->stage=0;
user->isOnline=0;
user->n_word=0;
for (int i = 0; i < MAX_BUFF; i++)
{
user->keywords[i] = NULL;
}
}
int find_cookie(char* buff)
{
char* cookie_id=strstr(buff, "cookie_id=");
char* temp;
if(cookie_id == NULL)
{
return 0;
}
else
{
temp = malloc(sizeof(char)*strlen(cookie_id)+1);
strcpy(temp, cookie_id);
temp+=10;
}
printf("find_cookie in buff %s\n", cookie_id);
//int cookie_length = strlen(username);
temp[strlen(temp)-3]='\0';
printf("find_cookie in buff %s\n", cookie_id);
return atoi(temp);
}
struct User* find_curr(struct User* user_arr[], char* buff)
{
int cookie_id = find_cookie(buff);
printf("the kmimimji%d %d\n", cookie_id,user_arr[0]->userID);
if(cookie_id==0){
struct User* curr_user = newUser();
if ((cookie_id!=1||cookie_id!=2)&& user_arr[0]->userID!=1)
{
curr_user->userID = 1;
user_arr[0]= curr_user;
}
else if((cookie_id!=1||cookie_id!=2)&& user_arr[1]->userID!=2){
curr_user->userID = 2;
user_arr[1]= curr_user;
}
cookie_id=curr_user->userID;
printf(" func curr: cookie_id is %d\n%d\n\n\n", cookie_id,curr_user->userID);
}
for (int i=0; i<2; i++) { if (user_arr[i]->userID == cookie_id)
{
printf("existing curr user %d , with cookie_id %d", i,cookie_id);
return user_arr[i];
}
}
}
struct User* the_other_user(struct User* curr_user, struct User* user_arr[]){
for(int i=0; i<2; i++){
if (user_arr[i]!=curr_user){
return user_arr[i];
}
}
}
bool match(char* w1, char** w2, int n_word){
for (int i=0; i<n_word; i++){
if (w2[i]==w1){
return true;
}
}
return false;
}
void simple_load_html(char* html, int sockfd, char* buff, int n, struct User* curr_user){
struct stat st;
stat(html, &st);
n = sprintf(buff, HTTP_200_FORMAT, st.st_size+6,curr_user->userID);
if (write(sockfd, buff, n) < 0)
{
perror("write");
return ;
}
int filefd = open(html, O_RDONLY);
do
{
n = sendfile(sockfd, filefd, NULL, 2048);
}
while (n > 0);
if (n < 0)
{
perror("sendfile");
close(filefd);
return ;
}
close(filefd);
}
/* adds the new word in the html (between the <body> */
void plug_in_html(char* newword, char* old_buffer, char* new_buffer){
// slice the html string into 2 parts
char start[MAX_BUFF];
char* end = strstr(old_buffer, "</body>");
// front part
strncpy(start, old_buffer, strlen(old_buffer)-strlen(end));
start[strlen(start)] = '\0';
// combine
strcpy(new_buffer, start);
strcat(new_buffer, newword);
strcat(new_buffer, end);
new_buffer[strlen(new_buffer)] = '\0';}
void edit_load_html(char* html, int sockfd, char* buff, char* newword,struct User* curr_user){
// get the size of the file
struct stat st;
stat(html, &st);
// increase file size to accommodate the new guess
int added_length= strlen(newword);
long size = st.st_size + added_length;
int n = sprintf(buff, HTTP_200_FORMAT, size+6, curr_user->userID);
// send the header first
if (write(sockfd, buff, n) < 0)
{
perror("write");
return;
}
// read the content of the HTML file
int filefd = open(html, O_RDONLY);
n = read(filefd, buff, st.st_size);
if (n < 0)
{
perror("read");
close(filefd);
return;
}
close(filefd);
// makes new buffer, copies the username into it
char* new_buffer = (char *) malloc(sizeof(MAX_BUFF));
plug_in_html(newword, buff, new_buffer);
if (write(sockfd, new_buffer, size) < 0)
{
perror("write");
return;
}
free(new_buffer);
}
void record_keyword(struct User* curr_user, char *new_guess){
for (int i=0; i<=curr_user->n_word; i++){
if(curr_user->keywords[curr_user->n_word]==NULL){
curr_user->keywords[curr_user->n_word] = malloc((1 + strlen(new_guess)) * sizeof(char*));
strcpy(curr_user->keywords[curr_user->n_word],new_guess);
curr_user->n_word++;
break;
}
}
}
static bool handle_http_request(int sockfd, struct User* user_arr[])
{
// try to read the request
char buff[2049];
int n = read(sockfd, buff, 2049);
if (n <= 0)
{
if (n < 0)
perror("read");
else
printf("socket %d close the connection\n", sockfd);
return false;
}
// terminate the string
buff[n] = 0;
char * curr = buff;
// parse the method
METHOD method = UNKNOWN;
if (strncmp(curr, "GET ", 4) == 0)
{
curr += 4;
method = GET;
}
else if (strncmp(curr, "POST ", 5) == 0)
{
curr += 5;
method = POST;
}
else if (write(sockfd, HTTP_400, HTTP_400_LENGTH) < 0)
{
perror("write");
return false;
}
printf("buff1: \n");
printf("%s\n",buff);
struct User* curr_user=find_curr(user_arr,buff);
// sanitise the URI
while (*curr == '.' || *curr == '/')
++curr;
printf("stage%d\n",curr_user->stage );
// assume the only valid request URI is "/" but it can be modified to accept more files
if (*curr == ' ' || *curr == '?')
//Stage1: when user name is not registed
if (curr_user->stage==0){
if (method==GET)
{
printf("curr_user->username%s == %d\n", curr_user->username,curr_user->username==NULL);
simple_load_html("1_intro.html",sockfd,buff,n,curr_user);
}
else if (method == POST)
{
printf("curr_user->username%s == %d\n", curr_user->username,curr_user->username==NULL);
// locate the username, it is safe to do so in this sample code, but usually the result is expected to be
// copied to another buffer using strcpy or strncpy to ensure that it will not be overwritten.
char * user = strstr(buff, "user=") + 5;
char username[MAX_BUFF];
strcpy(username, "<br>");
strcat(username, user);
username[strlen(username)] = '\0';
edit_load_html("2_start.html", sockfd,buff,username,curr_user);
printf("buffer %s\n",buff );
curr_user->stage=1;
}
}
//Stage 1: start the game
else if(curr_user->stage==1)
{
// quit: user not online
if(strstr(buff, "quit=Quit") != NULL)
{ curr_user->isOnline=0;
simple_load_html("7_gameover.html",sockfd,buff,n,curr_user);
curr_user->stage=3;
} // start is the only GET mathod for ther stage 1
else if (method == GET)
{ curr_user->isOnline=1;
simple_load_html("3_first_turn.html",sockfd,buff,n,curr_user);
curr_user->stage =2;
curr_user->isOnline=1;
}
}
// Stage 2: in the process of guessing
else if (curr_user->stage ==2){
char * new_guess = strstr(buff, "keyword=") + 8;
if(strstr(buff, "quit=Quit") != NULL)
{ curr_user->isOnline=0;
simple_load_html("7_gameover.html",sockfd,buff,n,curr_user);
clear_user_keyword(curr_user);
}
else if (the_other_user(curr_user, user_arr)->stage==3){
simple_load_html("7_gameover.html",sockfd,buff,n,curr_user);
clear_user_keyword(curr_user);
}
else if(the_other_user(curr_user, user_arr)->isOnline==0){
simple_load_html("5_discarded.html",sockfd,buff,n,curr_user);
clear_user_keyword(curr_user);
}
else if(new_guess!=NULL){
if(match(new_guess, the_other_user(curr_user, user_arr)->keywords, the_other_user(curr_user, user_arr)->n_word))
{
simple_load_html("6_endgame.html",sockfd,buff,n,curr_user);
clear_user_keyword(curr_user);
}
else{
curr_user->stage =2;
printf("curr_user->n_word%d\n",curr_user->n_word );
// remember new guess
record_keyword(curr_user, new_guess);
// display new guess
char guess[MAX_BUFF];
strcpy(guess,"<br>");
if (curr_user->n_word==1){
strcat(guess, new_guess);
guess[strlen(new_guess)]='\0';
}else
{
for (int i = 0; i < curr_user->n_word; i++)
{
strcat(guess, curr_user->keywords[i]);
strcat(guess,"<br>");
}
}
edit_load_html("4_accepted.html", sockfd,buff,guess,curr_user);
}
}
}
else
// never used, just for completeness
fprintf(stderr, "no other methods supported");
// send 404
else if (write(sockfd, HTTP_404, HTTP_404_LENGTH) < 0) {
perror("write");
return false;
}
return true;
}
int main(int argc, char * argv[])
{
if (argc < 3)
{
fprintf(stderr, "usage: %s ip port\n", argv[0]);
return 0;
}
// create TCP socket which only accept IPv4
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket");
exit(EXIT_FAILURE);
}
// reuse the socket if possible
int const reuse = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) < 0)
{
perror("setsockopt");
exit(EXIT_FAILURE);
}
// create and initialise address we will listen on
struct sockaddr_in serv_addr;
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
// if ip parameter is not specified
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
serv_addr.sin_port = htons(atoi(argv[2]));
// bind address to socket
if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
perror("bind");
exit(EXIT_FAILURE);
}
// listen on the socket
listen(sockfd, 5);
// initialise an active file descriptors set
fd_set masterfds;
FD_ZERO(&masterfds);
FD_SET(sockfd, &masterfds);
// record the maximum socket number
int maxfd = sockfd;
//global variable for 2 users
struct User* user_arr[2];
while (1)
{
// monitor file descriptors
fd_set readfds = masterfds;
if (select(FD_SETSIZE, &readfds, NULL, NULL, NULL) < 0)
{
perror("select");
exit(EXIT_FAILURE); }
// loop all possible descriptor
for (int i = 0; i <= maxfd; ++i)
// determine if the current file descriptor is active
if (FD_ISSET(i, &readfds))
{
// create new socket if there is new incoming connection request
if (i == sockfd)
{
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int newsockfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
if (newsockfd < 0)
perror("accept");
else
{
// add the socket to the set
FD_SET(newsockfd, &masterfds);
// update the maximum tracker
if (newsockfd > maxfd)
maxfd = newsockfd;
// print out the IP and the socket number
char ip[INET_ADDRSTRLEN];
printf(
"new connection from %s on socket %d\n",
// convert to human readable string
inet_ntop(cliaddr.sin_family, &cliaddr.sin_addr, ip, INET_ADDRSTRLEN),
newsockfd
);
}
}
// a request is sent from the client
else if (!handle_http_request(i, user_arr))
{
close(i);
FD_CLR(i, &masterfds);
}
}
}
return 0;
}