Skip to content
Snippets Groups Projects
Commit f97c1262 authored by Anqi Chen's avatar Anqi Chen
Browse files

finish

parent caab1561
Branches patch-1
No related tags found
No related merge requests found
/*
** http-server.c
*/
/********************************************Libraries**********************************************/
#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>
/********************************************constants**********************************************/
#define GUESS_SUFFIX "&guess=Guess"
#define MAX_BUFF 2049
/*******isOnline******/
#define INTRO 0
#define START 1
#define FIRST_TURN 2
#define ACCEPTED 3
#define DISCARDED 4
#define ENDGAME 5
#define GAMEOVER 6
/*******stage******/
#define ENTER 0
#define GUESS 1
#define ENDED 2
#define QUIT 3
#define RSTAR 4
/********************************************statics**********************************************/
// 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";
/*******isOnline******/
#define INTRO 0
#define START 1
#define FIRST_TURN 2
#define ACCEPTED 3
#define DISCARDED 4
#define ENDGAME 5
#define GAMEOVER 6
/*******stage******/
#define ENTER 0
#define GUESS 1
#define ENDED 2
#define QUIT 3
#define RSTAR 4
/********************************************Cookie**********************************************/
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;
/********************************************Structs**********************************************/
// enumerate all the methods
typedef enum
{
GET,
POST,
UNKNOWN
} METHOD;
// struct User
struct User {
int userID; // userID is the identification of the user, used for cookie
char** keywords; // an array of guessing words
int isOnline; // Online=1, OffLine=0
int stage; // record the stage of the user
int n_word; // number of guessing words
};
// secound round flag
int is_second_round = 0;
/********************************************Function Signitures**********************************************/
struct User* newUser();
void clear_user_keyword(struct User* user);
int find_cookie(char* buff);
struct User* find_curr(struct User* user_arr[], char* buff);
struct User* the_other_user(struct User* curr_user, struct User* user_arr[]);
bool match(char* w1, char** w2, int n_word);
void simple_load_html(char* html, int sockfd, char* buff, int n, struct User* curr_user);
void edit_load_html(char* html, int sockfd, char* buff, char* newword,struct User* curr_user);
void record_keyword(struct User* curr_user, char *new_guess);
static bool handle_http_request(int sockfd, struct User* user_arr[]);
/********************************************Functions**********************************************/
/******_________________________________user___________________________________*******/
// replace '+' with ' '
char* remove_plus(char* word){
for (int i=0; i<strlen(word);i++)
{
if (word[i]=='+'){
word[i]=' ';
}
return word;
}
}
// malloc a newUser
struct User* newUser() {
struct User* newUser = malloc(sizeof (struct User));
newUser->userID = 0;
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;
}
// clear user: point the array to NULL, reset the int to 0
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;
}
}
// read the words into the curr_user->keywords
void record_keyword(struct User* curr_user, char *new_guess){
remove_plus(new_guess);
for (int i=0; i<=curr_user->n_word; i++){
// add to the first NULL bit and malloc
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);
printf("record_keyword :curr_user->userID %d the last word stored is %s", curr_user->userID, curr_user->keywords[curr_user->n_word]);
curr_user->n_word++;
break;
}
}
}
//find the cookie according to id, in buff
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;
}
temp[strlen(temp)-3]='\0';
return atoi(temp);
}
// find the current user by analyse buffer
struct User* find_curr(struct User* user_arr[], char* buff)
{
int cookie_id = find_cookie(buff);
// if the cookie has not set up, assign it with a new id and malloc the space
if(cookie_id==0){
struct User* curr_user = newUser();
//if never accessed before, create new user
//check according to cookie and id
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);
}
// get the user from array
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];
}
}
}
// the user in arrsy if not this one, then should be that one
struct User* the_other_user(struct User* curr_user, struct User* user_arr[]){
for(int i=0; i<2; i++){
if (user_arr[i]->userID!=curr_user->userID){
printf("the other user id is %d\n", user_arr[i]->userID);
return user_arr[i];
}
}
}
// try to match the new guess to the other user keywords
bool match(char* w1, char** w2, int n_word){
for (int i=0; i<n_word; i++){
printf("%s - %s \n", w1, w2[i]);
if (strcmp(w1, w2[i])==0){
printf("all good");
return true;
}
}
return false;
}
/******_________________________________html___________________________________*******/
// only load page
void simple_load_html(char* html, int sockfd, char* buff, int n, struct User* curr_user){
struct stat st;
stat(html, &st);
// print into buff
n = sprintf(buff, HTTP_200_FORMAT, st.st_size,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);
}
// load page with new words
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);
remove_plus(newword);
// 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, 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);
// create two buffers to read the thing before and after </body>
char front_buff[MAX_BUFF], back_buff[MAX_BUFF];
int endlen=strstr(buff, "</body>")-buff;
// close teh string
strcpy(back_buff,"\n\r\r<p> ");
back_buff[strlen(back_buff)]='\0';
//add new word at the back_buff
strcat(back_buff, newword);
back_buff[strlen(back_buff)]='\0';
// close back_buff
strcat(back_buff, "\r\r</p>\r</body> </html>");
back_buff[strlen(back_buff)]='\0';
strncpy(front_buff, buff, endlen);
front_buff[strlen(front_buff)]='\0';
//merge
strcpy(buff, front_buff);
buff[strlen(buff)]='\0';
strcat(buff, back_buff);
buff[strlen(buff)]='\0';
if (is_second_round){
endlen = strstr(buff,"image-")+6-buff;
buff[endlen] = 3;
}
//write
if (write(sockfd, buff, size) < 0){
perror("write");
return;
}
}
// handle with every single thread from the client requests
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 == '?')
{
///////////// handle GET requests /////////////
if (method == GET)
{
//first visit to intro
if (curr_user->isOnline==INTRO)
{
simple_load_html("1_intro.html",sockfd,buff,n,curr_user);
}
else if (curr_user->isOnline==START){
simple_load_html("3_first_turn.html",sockfd,buff,n,curr_user);
curr_user->stage = GUESS;
}
else if (curr_user->stage==ENDED){
simple_load_html("3_first_turn.html",sockfd,buff,n,curr_user);
curr_user->stage = 1;
if (the_other_user(curr_user, user_arr)->stage==ENDED){
the_other_user(curr_user, user_arr)->stage==RSTAR;
}
}
}
///////////// handle POST requests/////////////
else if (method == POST){
if(strstr(buff, "quit=") != NULL)
{
simple_load_html("7_gameover.html",sockfd,buff,n,curr_user);
curr_user->stage=QUIT;
}
// in registeration process: read name into html "2_start.html"
else if(curr_user->isOnline == INTRO)
{
// get string
char* username = strstr(buff, "user=")+5;
char u[MAX_BUFF];
strcpy(u,"<br>");
strcat(u, username);
u[strlen(username)]='\0';
curr_user->isOnline = 1;
//edit into html
edit_load_html("2_start.html", sockfd,buff,username,curr_user);
}
// in guessing process:
else if (curr_user->isOnline == FIRST_TURN ||curr_user->isOnline == START ) {
curr_user->isOnline = FIRST_TURN;
// 1. the other user not ready but not quit: discard curr_user keyword
if (curr_user->stage==ENTER || curr_user->stage == RSTAR)
{
simple_load_html("5_discarded.html",sockfd,buff,n,curr_user);
}
else if(the_other_user(curr_user, user_arr)->stage==ENTER && curr_user->stage==GUESS){
simple_load_html("5_discarded.html",sockfd,buff,n,curr_user);
}
// 2. the other user quit: curr_user quit
else if (curr_user->stage==QUIT){
simple_load_html("7_gameover.html",sockfd,buff,n,curr_user);
curr_user->stage = QUIT;
}
// 3. match word successfully
else if (curr_user->stage == ENDED|| curr_user->stage == RSTAR){
simple_load_html("6_endgame.html",sockfd,buff,n,curr_user);
if (curr_user->stage == RSTAR){
curr_user->stage = ENTER;
curr_user->isOnline = START;
is_second_round = 1;
}
else{
curr_user->stage = ENDED;
}
}
else {
// read the new guess word
char * guess = strstr(buff, "keyword=") + 8;
guess[strlen(guess)-strlen(GUESS_SUFFIX)] = '\0';
if (match(guess, the_other_user(curr_user, user_arr)->keywords, the_other_user(curr_user, user_arr)->n_word)){
// go to end page
curr_user->stage = ENDED;
the_other_user(curr_user, user_arr)->stage = ENDED;
simple_load_html("6_endgame.html",sockfd,buff,n,curr_user);
clear_user_keyword(curr_user);
}else{
// read new guess word into curr_user array
record_keyword(curr_user, guess);
char guess_buff[MAX_BUFF];
strcpy(guess_buff,"<br>");
// add a null bit at the end of the string
if(curr_user->n_word==1)
{
strcat(guess_buff, guess);
guess_buff[strlen(guess_buff)]='\0';
}
// loop through and print every single word
else
{
for (int i = 0; i < curr_user->n_word; ++i)
{
strcat(guess_buff, curr_user->keywords[i]);
strcat(guess_buff,"<br>");
}
}
edit_load_html("4_accepted.html", sockfd,buff,guess_buff,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;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment