Created
June 22, 2024 15:56
-
-
Save Tharun8951/4dac61304ed499760989960d0c54e724 to your computer and use it in GitHub Desktop.
Trying to implement http server from scratch in c
This file contains 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 <netinet/in.h> | |
#include <stdio.h> | |
#include <sys/socket.h> | |
#include <unistd.h> | |
#include <stdlib.h> | |
#include <sys/types.h> | |
#include <string.h> | |
#include <arpa/inet.h> | |
#include <bits/socket.h> | |
#include <sys/stat.h> | |
#include <fcntl.h> | |
/* Structures */ | |
struct sHttpRequest { | |
char method[8]; | |
char url[128]; | |
}; | |
typedef struct sHttpRequest httpreq; | |
struct sFile { | |
char fileName[64]; | |
char *fileContents; | |
int size; | |
}; | |
typedef struct sFile File; | |
/* GLOBAL ERROR */ | |
char *error; | |
#define LISTENADDR "0.0.0.0" | |
/* server_init takes a port number and returns socket file descriptor, return 0 or error */ | |
int server_init(int portNo){ | |
int s; | |
struct sockaddr_in srv; | |
s = socket(AF_INET, SOCK_STREAM, 0); | |
if(s < 0){ | |
error = "socket() error"; | |
return 0; | |
} | |
srv.sin_family = AF_INET; | |
srv.sin_addr.s_addr = inet_addr(LISTENADDR); | |
srv.sin_port = htons(portNo); | |
if (bind(s, (struct sockaddr *)&srv, sizeof(srv))){ | |
close(s); | |
error = "bind() error"; | |
return 0; | |
} | |
if(listen(s, 5)){ | |
close(s); | |
error = "listen() error"; | |
return 0; | |
} | |
return s; | |
} | |
/* client_accept accepts a connection and returs a new client's file descriptor, returns 0 on error */ | |
int client_accept(int s){ | |
int c; | |
socklen_t addrlen; | |
struct sockaddr_in cli; | |
addrlen = 0; | |
memset(&cli, 0, sizeof(cli)); | |
c = accept(s, (struct sockaddr *)&cli, &addrlen); | |
if (c < 0){ | |
close(c); | |
error = "accept() error"; | |
return 0; | |
} | |
return c; | |
} | |
httpreq *parse_http(char *str) { | |
httpreq *req = malloc(sizeof(httpreq)); | |
if (!req) { | |
error = "Memory allocation error"; | |
return NULL; | |
} | |
memset(req, 0, sizeof(httpreq)); | |
char *space = strchr(str, ' '); | |
if (!space) { | |
error = "parse_http() NOSPACE error"; | |
free(req); | |
return NULL; | |
} | |
*space = '\0'; | |
strncpy(req->method, str, sizeof(req->method) - 1); | |
req->method[sizeof(req->method) - 1] = '\0'; // Ensure null-termination | |
char *space2 = strchr(space + 1, ' '); | |
if (!space2) { | |
error = "parse_http() NOSPACE error for URL"; | |
free(req); | |
return NULL; | |
} | |
*space2 = '\0'; | |
strncpy(req->url, space + 1, sizeof(req->url) - 1); | |
req->url[sizeof(req->url) - 1] = '\0'; // Ensure null-termination | |
return req; | |
} | |
/* return 0 if error, return data on success */ | |
char *client_read(int c){ | |
static char buff[512]; | |
memset(buff, 0, 512); | |
if(read(c, buff, 511) < 0) { | |
error = "read() error"; | |
return 0; | |
} | |
return buff; | |
} | |
/* | |
HTTP/1.1 301 Moved Permanently | |
Date: Sun, 28 Apr 2024 06:01:51 GMT | |
Connection: keep-alive | |
Server: ATS | |
Cache-Control: no-store | |
Content-Type: text/html | |
Content-Language: en | |
Location: https://www.yahoo.com/ | |
Content-Length: 1 | |
*/ | |
void http_response(int c, char *ContentType, char *data){ | |
char buff[512]; | |
int n; | |
memset(buff, 0, 512); | |
n = strlen(data); | |
snprintf(buff, 511, | |
"Content-Type: %s\n" | |
"Content-Length: %d\n\n" | |
"\n%s\n" | |
, ContentType, n, data | |
); | |
n = strlen(buff); | |
write(c, buff, n); | |
return; | |
} | |
void http_headers(int c, int code){ | |
char buff[512]; | |
int n; | |
memset(buff, 0, 512); | |
snprintf(buff, 511, | |
"HTTP/1.1 %d Ok\n" | |
"Date: Sun, 28 Apr 2024 06:01:51 GMT\n" | |
"Connection: keep-alive\n" | |
"Server: httpe.c\n" | |
"Cache-Control: no-store\n" | |
"Content-Language: en\n" | |
"Location: Bangalore\n" | |
, code | |
); | |
n = strlen(buff); | |
write(c, buff, n); | |
return; | |
} | |
/* Return 0 on error, file structure on success */ | |
File *readFile(char *fileName){ | |
printf("File name: %s\n", fileName); | |
char buff[512]; | |
char *p; | |
int n, x, fd; | |
File *f; | |
fd = open(fileName, O_RDONLY); | |
if(fd < 0){ | |
printf("Error opening the file, exiting readFile(), %d\n", fd); | |
return 0; | |
} | |
// if (fd < 0) return 0; | |
f = malloc(sizeof(File)); | |
if (f == NULL){ | |
printf("f malloc error\n"); | |
close(fd); | |
return 0; | |
} | |
strncpy(f->fileName, fileName, 63); | |
f->fileContents = malloc(512); | |
n = 0; //number or bytes read | |
while(1){ | |
// printf("Inside the readFile while loop\n"); | |
memset(buff, 0, 512); | |
x = read(fd, buff, 512); | |
// printf("reading fd into buff, readFile while loop and x = %d\n", x); | |
if (x == 0){ | |
// printf("x == 0\n"); | |
break; | |
} | |
else if (x == -1) { | |
printf("x == -1\n"); | |
close(fd); | |
free(f->fileContents); | |
free(f); | |
error = "readFile() error"; | |
return 0; | |
} | |
// printf("Copying buff into filecontents\n"); | |
strncpy((f->fileContents)+n, buff, x); | |
// printf("Copied buff into filecontents\n"); | |
n += x; | |
f->fileContents = realloc(f->fileContents, (512 + n)); | |
} | |
f->size = n; | |
close(fd); | |
printf("Returning %s file\n", fileName); | |
return f; | |
} | |
/* returns 0 on error, returns 1 if everything ok! */ | |
int sendFile(int c, char *ContentType, File *file){ | |
printf("Inside the sendFile function, sending: '%s'\n", file->fileName); | |
if(!file){ | |
error = "File is empty, error in opening file"; | |
return 0; | |
} | |
char buff[512]; | |
char *p; | |
int n, x; | |
memset(buff, 0, 512); | |
snprintf(buff, 511, | |
"Content-Type: %s\n" | |
"Content-Length: %d\n\n" | |
"\r\n" | |
, ContentType, file->size | |
); | |
n = file->size; | |
p = file->fileContents; | |
write(c, buff, 512); | |
memset(buff, 0, 512); | |
while(1){ | |
x = write(c, p, n < 512 ? n : 512); | |
if(x<1) return 0; | |
n = n - x; | |
if(n < 1){ | |
break; | |
} else { | |
p += x; | |
} | |
} | |
printf("Succeffully sent '%s'\n", file->fileName); | |
printf("File size '%d'\n", file->size); | |
return 1; | |
} | |
void client_conn(int s, int c){ | |
httpreq *req; | |
char buff[512]; | |
char reqUrlStr[120]; | |
char *p; | |
char *res; | |
File *f; | |
p = client_read(c); | |
if(!p){ | |
fprintf(stderr, "%s\n", error); | |
close(c); | |
return; | |
} | |
req = parse_http(p); | |
if(!req){ | |
fprintf(stderr, "%s\n", error); | |
close(c); | |
return; | |
} | |
if (!strcmp(req->method, "GET")) { | |
if (strncmp(req->url, "/img/", 5) == 0) { | |
char fullPath[256]; | |
snprintf(fullPath, sizeof(fullPath), ".%s", req->url); // Adjust path as necessary | |
f = readFile(fullPath); | |
if (f) { | |
http_headers(c, 200); | |
sendFile(c, "image/png", f); | |
free(f); | |
return; | |
} | |
} | |
} | |
if(!strcmp(req->method, "GET") && !strcmp(req->url, "/app/webpage")){ | |
// res = "<html>Hello world</html>"; | |
res = "<html><img src = 'js.jpg' alt = 'js runtime image'/></html>"; | |
http_headers(c, 200); | |
http_response(c, "text/html", res); | |
} else { | |
res = "File not found!"; | |
http_headers(c, 404); | |
http_response(c, "text/plain", res); | |
} | |
printf("Method: '%s'\nURL: '%s'\n", req->method, req->url); | |
free(req); | |
return; | |
} | |
int main (int argc, char **argv) { | |
int s, c; | |
char *port; | |
if (argc < 2) { | |
fprintf(stderr, "Usage: %s <listening port>\n", argv[0]); | |
return -1; | |
} else { | |
port = argv[1]; | |
} | |
s = server_init(atoi(port)); | |
if(!s){ | |
fprintf(stderr, "%s\n", error); | |
return -1; | |
} | |
printf("Listening on %s:%s\n", LISTENADDR, port); | |
while(1){ | |
c = client_accept(s); | |
if(!c){ | |
fprintf(stderr, "%s\n", error); | |
continue; | |
} | |
printf("Incoming connecetion \n"); | |
if (!fork()){ | |
client_conn(s, c); | |
} | |
} | |
return -1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment