Skip to content

Instantly share code, notes, and snippets.

@hortinstein
Created February 11, 2022 03:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hortinstein/a2a30ea07826b04eb72021ebc649f851 to your computer and use it in GitHub Desktop.
Save hortinstein/a2a30ea07826b04eb72021ebc649f851 to your computer and use it in GitHub Desktop.
#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