Skip to content

Instantly share code, notes, and snippets.

@Tharun8951
Created June 22, 2024 15:56
Show Gist options
  • Save Tharun8951/4dac61304ed499760989960d0c54e724 to your computer and use it in GitHub Desktop.
Save Tharun8951/4dac61304ed499760989960d0c54e724 to your computer and use it in GitHub Desktop.
Trying to implement http server from scratch in c
#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