Created
February 11, 2022 03:43
-
-
Save hortinstein/a2a30ea07826b04eb72021ebc649f851 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <unistd.h> | |
#include <stdlib.h> | |
#include <errno.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <sys/socket.h> | |
#include <string.h> | |
#include <netdb.h> | |
#include <netinet/in.h> | |
#include "gfserver.h" | |
#include "gfserver-student.h" | |
#define MAX_HEADER_STRING 1024 | |
#define MAX_REQ_STRING 1024 | |
#define MAX_FILE_PATH 1024 | |
#define STATUS_OK "OK" | |
#define STATUS_FILE_NOT_FOUND "FILE_NOT_FOUND" | |
#define STATUS_ERROR "ERROR" | |
#define STATUS_INVALID "INVALID" | |
/* | |
* Modify this file to implement the interface specified in | |
* gfserver.h. | |
*/ | |
/* | |
* struct containing the context in gfserver_t | |
*/ | |
struct gfserver_t { | |
unsigned short port; | |
unsigned short max_npending; | |
void *handlerfunc; | |
void *handlerarg; | |
}; | |
/* | |
* struct containing the context in gfcontext_t | |
*/ | |
struct gfcontext_t { | |
int cli_sockfd; | |
char filepath[MAX_FILE_PATH]; | |
int status; | |
//int total_bytes_to_read; | |
//int total_bytes_read; | |
//int total_bytes_to_send; | |
//int total_bytes_sent; | |
}; | |
/* | |
* Aborts the connection to the client associated with the input | |
* gfcontext_t. | |
*/ | |
void gfs_abort(gfcontext_t *ctx){ | |
close(ctx->cli_sockfd); | |
} | |
/* | |
* This function must be the first one called as part of | |
* setting up a server. It returns a gfserver_t handle which should be | |
* passed into all subsequent library calls of the form gfserver_*. It | |
* is not needed for the gfs_* call which are intended to be called from | |
* the handler callback. | |
*/ | |
gfserver_t* gfserver_create(){ | |
gfserver_t* tmp_ptr = (gfserver_t* )malloc(sizeof(gfserver_t)); | |
if (!tmp_ptr) { | |
fprintf(stderr, "%s @ %d: malloc failed\n", __FILE__, __LINE__); | |
goto fail; | |
} | |
tmp_ptr->port = 0; | |
tmp_ptr->max_npending = 1; | |
tmp_ptr->handlerfunc = NULL; | |
tmp_ptr->handlerarg = NULL; | |
return tmp_ptr; | |
fail: | |
return (gfserver_t *)NULL; | |
} | |
/* | |
* Sends size bytes starting at the pointer data to the client | |
* This function should only be called from within a callback registered | |
* with gfserver_set_handler. It returns once the data has been | |
* sent. | |
*/ | |
ssize_t gfs_send(gfcontext_t *ctx, void *data, size_t len){ | |
size_t n_bytes_sent = 0; | |
n_bytes_sent = write(ctx->cli_sockfd,data,len); | |
if (n_bytes_sent < 0) { | |
fprintf(stderr, "%s @ %d: error sending to socket\n", __FILE__, __LINE__); | |
goto fail; | |
} | |
if (n_bytes_sent != len) { | |
fprintf(stderr, "%s @ %d: error all the file was not sent\n", __FILE__, __LINE__); | |
goto fail; | |
} | |
return n_bytes_sent; | |
fail: | |
return -1; | |
} | |
/* | |
* Sends to the client the Getfile header containing the appropriate | |
* status and file length for the given inputs. This function should | |
* only be called from within a callback registered gfserver_set_handler. | |
*/ | |
ssize_t gfs_sendheader(gfcontext_t *ctx, gfstatus_t status, size_t file_len){ | |
//send the request in the format: | |
// GETFILE GET /path/to/file \r\n\r\n | |
char header_buf[MAX_HEADER_STRING] = {0}; | |
char status_str[16] = {0}; | |
// if (ctx->status != status) { | |
// fprintf(stderr, "%s @ %d: statuses do not match\n", __FILE__, __LINE__); | |
// goto fail; | |
// } | |
int header_to_write = 0; | |
if (status == GF_OK){ | |
strcpy(status_str,STATUS_OK); | |
header_to_write = snprintf(header_buf,MAX_HEADER_STRING,"GETFILE %s %d\r\n\r\n",status_str,(int)file_len); | |
} | |
else if (status == GF_FILE_NOT_FOUND){ | |
strcpy(status_str,STATUS_FILE_NOT_FOUND); | |
header_to_write = snprintf(header_buf,MAX_HEADER_STRING,"GETFILE %s\r\n\r\n",status_str); | |
} | |
else if (status == GF_INVALID){ | |
strcpy(status_str,STATUS_INVALID); | |
header_to_write = snprintf(header_buf,MAX_HEADER_STRING,"GETFILE %s\r\n\r\n",status_str); | |
} | |
else{ | |
strcpy(status_str,STATUS_ERROR); | |
header_to_write = snprintf(header_buf,MAX_HEADER_STRING,"GETFILE %s\r\n\r\n",status_str); | |
} | |
if (header_to_write <0){ | |
fprintf(stderr, "%s @ %d: request too long to fit into buffer\n", __FILE__, __LINE__); | |
goto fail; | |
} | |
printf("sending header: %s\n",header_buf); | |
//TODO rewrite this using he number returnd from SPRINTF | |
return write(ctx->cli_sockfd,header_buf,header_to_write); | |
fail: | |
return -1; | |
} | |
/* | |
* Helper functon to parse the header | |
*/ | |
void gfs_parseheader(gfcontext_t *ctx,char * buf,size_t buf_len){ | |
// The scheme is always GETFILE. | |
if (!buf || !ctx) { | |
fprintf(stderr, "%s @ %d: parseheader was given bad args\n", __FILE__, __LINE__); | |
return; | |
} | |
fprintf(stderr,"header to parse: %s\n",buf); | |
// The status must be in the set {‘OK’, ‘FILE_NOT_FOUND’, ‘ERROR’, 'INVALID'}. | |
char s[MAX_REQ_STRING]; | |
strncpy(s,buf,MAX_REQ_STRING); | |
//this should return GETFILE | |
char* token = strtok(s, " "); | |
if (token == NULL || strncmp("GETFILE",token,7) != 0) { | |
fprintf(stderr, "%s @ %d: GETFILE not found in request\n", __FILE__, __LINE__); | |
ctx->status = GF_INVALID;//invalid header | |
} | |
fprintf(stderr,"<scheme> : %s\n",token); | |
token = strtok(NULL, " "); | |
if (token == NULL || strncmp("GET",token,3) != 0) { | |
fprintf(stderr, "%s @ %d: GET not found in request\n", __FILE__, __LINE__); | |
ctx->status = GF_INVALID;//invalid header | |
} | |
fprintf(stderr,"<method> : %s\n",token); | |
token = strtok(NULL, " "); | |
if (token == NULL || token[0] != '/') { | |
fprintf(stderr, "%s @ %d: path in request does not start with \'/\'\n", __FILE__, __LINE__); | |
ctx->status = GF_INVALID;//invalid header | |
} else { | |
//copying the filename over into the path | |
bzero(ctx->filepath,MAX_FILE_PATH); | |
memcpy(ctx->filepath,token,strlen(token)); | |
} | |
printf("<path> : %s\n",token); | |
if (!strstr(buf,"\r\n\r\n") ) { | |
fprintf(stderr, "%s @ %d: request does not contain \'\\r\\n\\r\\n\'", __FILE__, __LINE__); | |
ctx->status = GF_INVALID;//invalid header | |
} | |
// INVALID is the appropriate status when the header is invalid. This includes a malformed header as well an incomplete header due to communication issues. | |
// FILE_NOT_FOUND is the appropriate response whenever the client has made an error in his request. ERROR is reserved for when the server is responsible for something bad happening. | |
// No content may be sent if the status is FILE_NOT_FOUND or ERROR. | |
// When the status is OK, the length should be a number expressed in ASCII (what sprintf will give you). The length parameter should be omitted for statuses of FILE_NOT_FOUND or ERROR. | |
// The sequence ‘\r\n\r\n’ marks the end of the header. All remaining bytes are the files contents. | |
// The space between the <scheme> and the <method> and the space between the <method> and the <path> are required. The space between the <path> and '\r\n\r\n' is optional. | |
// The space between the <scheme> and the <status> and the space between the <status> and the <length> are required. The space between the <length> and '\r\n\r\n' is optional. | |
// The length may be up to a 64 bit unsigned value (that is, the value will be less than 18446744073709551616) though we do not require that you support this amount (nor will we test against a 16EB-1 byte file). | |
} | |
/* | |
* Starts the server. Does not return. | |
*/ | |
void gfserver_serve(gfserver_t *gfs){ | |
int sockfd; | |
gfcontext_t* client_ctx = malloc(sizeof(gfcontext_t)); | |
socklen_t cli_len; | |
struct sockaddr_in serv_addr, cli_addr; | |
sockfd = socket(AF_INET, SOCK_STREAM, 0); | |
if (sockfd < 0) { | |
fprintf(stderr, "%s @ %d: error opening up socket\n", __FILE__, __LINE__); | |
exit(1); | |
} | |
bzero((char *) &serv_addr, sizeof(serv_addr)); | |
serv_addr.sin_family = AF_INET; | |
serv_addr.sin_addr.s_addr = INADDR_ANY; | |
serv_addr.sin_port = htons(gfs->port); | |
int enable = 1; | |
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) | |
{ | |
fprintf(stderr, "%s @ %d: setsockopt(SO_REUSEADDR) failed\n", __FILE__, __LINE__); | |
exit(1); | |
} | |
if (bind(sockfd, (struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0){ | |
fprintf(stderr, "%s @ %d: error on binding\n", __FILE__, __LINE__); | |
exit(1); | |
} | |
listen(sockfd,5); | |
cli_len = sizeof(cli_addr); | |
char req_buffer[MAX_REQ_STRING] = {0}; | |
while (1){ | |
bzero((char *) client_ctx, sizeof(gfcontext_t)); | |
client_ctx->cli_sockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &cli_len); | |
//printf("accepted client\n"); | |
if (client_ctx->cli_sockfd < 0) { | |
fprintf(stderr, "%s @ %d: error on accept\n", __FILE__, __LINE__); | |
exit(1); | |
} | |
int total_req_read =0; | |
while (!strstr(req_buffer,"\r\n\r\n")){ | |
if (total_req_read >= MAX_REQ_STRING) { | |
fprintf(stderr, "%s @ %d: request exceeded max request string\n", __FILE__, __LINE__); | |
exit(1); | |
} | |
int req_read = read(client_ctx->cli_sockfd,req_buffer+total_req_read,MAX_REQ_STRING); | |
if (req_read < 0) { | |
fprintf(stderr, "%s @ %d: error on reading client request\n", __FILE__, __LINE__); | |
exit(1); | |
//should i send and error here? | |
} | |
total_req_read+=req_read; | |
fprintf(stderr, "%s @ %d: recv read %d bytes, total_req_read: %d\n", __FILE__, __LINE__, req_read,total_req_read); | |
printf("%s @ %d: recv read %d bytes, total_req_read: %d\n", __FILE__, __LINE__, req_read,total_req_read); | |
} | |
//reads the client request in | |
gfs_parseheader(client_ctx,req_buffer,total_req_read); | |
//sets up the function pointer to get called | |
ssize_t (*handler) () = (gfs->handlerfunc); | |
if (client_ctx->status == GF_INVALID) { | |
gfs_sendheader(client_ctx,client_ctx->status,0); | |
} | |
else{ | |
handler(client_ctx,client_ctx->filepath,gfs->handlerarg); | |
} | |
close(client_ctx->cli_sockfd); | |
} | |
close(sockfd); | |
} | |
/* | |
* Sets the third argument for calls to the handler callback. | |
*/ | |
void gfserver_set_handlerarg(gfserver_t *gfs, void* arg){ | |
gfs->handlerarg = arg; | |
} | |
/* | |
* Sets the handler callback, a function that will be called for each each | |
* request. As arguments, this function receives: | |
* - a gfcontext_t handle which it must pass into the gfs_* functions that | |
* it calls as it handles the response. | |
* - the requested path | |
* - the pointer specified in the gfserver_set_handlerarg option. | |
* The handler should only return a negative value to signal an error. | |
*/ | |
void gfserver_set_handler(gfserver_t *gfs, ssize_t (*handler)(gfcontext_t *, char *, void*)){ | |
gfs->handlerfunc = handler; | |
} | |
/* | |
* Sets the maximum number of pending connections which the server | |
* will tolerate before rejecting connection requests. | |
*/ | |
void gfserver_set_maxpending(gfserver_t *gfs, int max_npending){ | |
gfs->max_npending = max_npending; | |
} | |
/* | |
* Sets the port at which the server will listen for connections. | |
*/ | |
void gfserver_set_port(gfserver_t *gfs, unsigned short port){ | |
gfs->port = port; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment