Thursday, February 13, 2014

A simple HTTP client and a server in C

The HTTP server sends the files requested by the client, if it is available at the server. The HTTP client uses GET method for requesting files from the server. Only .html, .txt and .pdf files are implemented. On receiving the file, the client program opens it using firefox or gedit or acrobat reader, as per the file type. Note that the given programs were done in Linux (Debian) environment. 

httpserver.c
 
/**httpserver.c**/
#include"stdio.h"
#include"stdlib.h"
#include"sys/types.h"
#include"sys/socket.h"
#include"string.h"
#include"netinet/in.h"
#include"time.h"
#include"dirent.h"
#include"netdb.h"

#define BUF_SIZE 1024
#define CLADDR_LEN 100

int createSocket(char * host, int port);
int listenForRequest(int sockfd);
char * getFileType(char * file);

int main(int argc, char **argv) { 
 DIR * dirptr;
 FILE * fileptr;
 time_t timenow;
 struct tm * timeinfo;
 time (&timenow);
 timeinfo = localtime(&timenow);

 char * header, * request, * path, * newpath, * host;
 char * dir, * temp;
 int port, sockfd, connfd;
 char get[3], http[9];
 char filepath[BUF_SIZE];
 char http_not_found[] = "HTTP/1.0 404 Not Found\n";
 char http_ok[] = "HTTP/1.0 200 OK\n";
 char buffer[BUF_SIZE];
 char * contentType;

 if (argc != 4) {
  printf("usage: [host] [directory] [portnumber]\n");
  exit(1);
 }

 header = (char*)malloc(BUF_SIZE*sizeof(char));
 request = (char*)malloc(BUF_SIZE*sizeof(char));
 path = (char*)malloc(BUF_SIZE*sizeof(char));
 newpath = (char*)malloc(BUF_SIZE*sizeof(char));

 host = argv[1];
 dir = argv[2];
 port = atoi(argv[3]);

 if ((dirptr = opendir(dir)) == NULL) {
      printf("Directory Not Found!\n");
      exit(1);
 }
 
 sockfd = createSocket(host, port);
 
 for (;;) {
  printf("--------------------------------------------------------\n");
  printf("Waiting for a connection...\n");
  connfd = listenForRequest(sockfd);
  //gets the request from the connection
  recv(connfd, request, 100, 0);
  printf("Processing request...\n");
  //parses request
  sscanf(request, "%s %s %s", get, path, http);
  newpath = path + 1; //ignores the first slash
    sprintf(filepath,"%s/%s", dir, newpath);
  contentType = getFileType(newpath);
  sprintf(header, "Date: %sHostname: %s:%d\nLocation: %s\nContent-Type: %s\n\n", asctime(timeinfo), host, port, newpath, contentType);
  if ((fileptr = fopen(filepath, "r")) == NULL ) {
   printf("File not found!\n");
   send(connfd, http_not_found, strlen(http_not_found), 0); //sends HTTP 404
  } else {
   printf("Sending the file...\n");
   send(connfd, http_ok, strlen(http_ok), 0); //sends HTTP 200 OK  
   recv(connfd, buffer, BUF_SIZE, 0);
   if ((temp = strstr(buffer, "OK")) == NULL) {
    printf("Operation aborted by the user!\n");
    break;
   }
   send(connfd, header, strlen(header), 0); //sends the header
   recv(connfd, buffer, BUF_SIZE, 0);
   if ((temp = strstr(buffer, "OK")) == NULL) {
    printf("Operation aborted by the user!\n");
    break;
   }
   memset(&buffer, 0, sizeof(buffer));
   while (!feof(fileptr)) { //sends the file
    fread(&buffer, sizeof(buffer), 1, fileptr);
    send(connfd, buffer, sizeof(buffer), 0);
    memset(&buffer, 0, sizeof(buffer));
   }
   printf("File sent...\n");
  }
  printf("Processing completed...\n");
  close(connfd);
 }

 close(sockfd);
 free(header);
 free(request);
 free(path);
 free(newpath);

 return 0;
}

int createSocket(char * host, int port) {
 int sockfd;
 struct sockaddr_in addr;
 struct hostent * host_ent;
 char * hostAddr;

 memset(&addr, 0, sizeof(addr));
 addr.sin_family = AF_INET;
 addr.sin_addr.s_addr = INADDR_ANY;
 addr.sin_port = htons((short)port);

 sockfd = socket(AF_INET, SOCK_STREAM, 0);
 if (sockfd < 0) {  
  printf("Error creating socket!\n");  
  exit(1);  
 }  
 printf("Socket created...\n");

 if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
  printf("Error binding socket to port!\n");  
  exit(1);
   }
 printf("Binding done...\n");

 return sockfd;
}

int listenForRequest(int sockfd) {
 int conn;
 char hostip[32];
 struct sockaddr_in addr;
 struct hostent * host;
 struct in_addr inAddr;
 int len;

 addr.sin_family = AF_INET;

 listen(sockfd, 5); //maximum 5 connections
 len = sizeof(addr); 
 if ((conn = accept(sockfd, (struct sockaddr *)&addr, &len)) < 0) {
  printf("Error accepting connection!\n");
  exit(1);
 }
 printf("Connection accepted...\n");
  
 inet_ntop(AF_INET, &(addr.sin_addr), hostip, 32);
 inet_pton(AF_INET, hostip, &inAddr);
 host = gethostbyaddr(&inAddr, sizeof(inAddr), AF_INET);

 printf("---Connection received from: %s [IP= %s]---\n", host->h_name, hostip);
 return conn;
}

char * getFileType(char * file) {
 char * temp;
 if ((temp = strstr(file, ".html")) != NULL) {
  return "text/html";
 } else if ((temp = strstr(file, ".pdf")) != NULL) {
  return "application/pdf";
 } else if ((temp = strstr(file, ".txt")) != NULL) {
  return "text/html";
 }
}

httpclient.c
 
/**httpclient.c**/
#include"stdio.h"  
#include"stdlib.h"  
#include"sys/types.h"  
#include"sys/socket.h"  
#include"string.h"  
#include"netinet/in.h"  
#include"netdb.h"
  
#define BUF_SIZE 1024 
  
int get_request(char * url, char * port);
int isValidIP(char * ip);
int parseHeader(char * header);
char * splitKeyValue(char * line, int index);
void openFile();

FILE * fileptr;
char keys[][25] = {"Date: ", "Hostname: ", "Location: ", "Content-Type: "};
char status[4] = {0, 0, 0, 0};
char contentFileType[100];
char path[1000];

int main(int argc, char**argv) {  
 struct sockaddr_in addr, cl_addr;  
 int sockfd, ret; 
 struct hostent * server;
 char * url, * temp;
 int portNumber;
 char * fileName;
 char status_ok[] = "OK";
 char buffer[BUF_SIZE]; 
 char http_not_found[] = "HTTP/1.0 404 Not Found";
 char http_ok[] = "HTTP/1.0 200 OK";
 char location[] = "Location: ";
 char contentType[] = "Content-Type: ";
 int sPos, ePos;

 if (argc < 3) {
  printf("usage: [URL] [port number]\n");
  exit(1);  
 }

 url = argv[1];
 portNumber = atoi(argv[2]);

 //checking the protocol specified
 if ((temp = strstr(url, "http://")) != NULL) {
  url = url + 7;
 } else if ((temp = strstr(url, "https://")) != NULL) {
  url = url + 8;
 }

 //checking the port number
 if (portNumber > 65536 || portNumber < 0) {
  printf("Invalid Port Number!");
  exit(1);
 }
 
 sockfd = get_request(url, argv[2]); 

 memset(&buffer, 0, sizeof(buffer));
 ret = recv(sockfd, buffer, BUF_SIZE, 0);  
 if (ret < 0) {  
  printf("Error receiving HTTP status!\n");    
 } else {
  printf("%s\n", buffer);
  if ((temp = strstr(buffer, http_ok)) != NULL) {
   send(sockfd, status_ok, strlen(status_ok), 0);
  } else {
   close(sockfd);
   return 0;
  }
 }

 memset(&buffer, 0, sizeof(buffer)); 
 ret = recv(sockfd, buffer, BUF_SIZE, 0);  
 if (ret < 0) {  
  printf("Error receiving HTTP header!\n");    
 } else {
  printf("%s\n", buffer);
  if (parseHeader(buffer) == 0) {
   send(sockfd, status_ok, strlen(status_ok), 0);
  } else {
   printf("Error in HTTP header!\n");
   close(sockfd);
   return 0;
  }
 } 

 //printf("file: [%s]\n", fileName);
 fileptr = fopen(path, "w");
 if (fileptr == NULL) {
  printf("Error opening file!\n");
  close(sockfd);
  return 0;
 }

 memset(&buffer, 0, sizeof(buffer));
 while (recv(sockfd, buffer, BUF_SIZE, 0) > 0) { //receives the file
  if ((strstr(contentFileType, "text/html")) != NULL) {
   fprintf(fileptr, "%s", buffer);
  } else {
   fwrite(&buffer, sizeof(buffer), 1, fileptr);
  }
  memset(&buffer, 0, sizeof(buffer));
 }

 fclose(fileptr);
 close(sockfd);

 openFile();

 return 0;
}


int get_request(char * url, char * port) {

 int sockfd, bindfd;
        char * ptr, * host;
 char getrequest[1024];
        struct sockaddr_in addr;

 if (isValidIP(url)) { //when an IP address is given
  sprintf(getrequest, "GET / HTTP/1.0\nHOST: %s\n\n", url);
        } else { //when a host name is given
  if ((ptr = strstr(url, "/")) == NULL) {
   //when hostname does not contain a slash
   sprintf(getrequest, "GET / HTTP/1.0\nHOST: %s\n\n", url);
  } else {
   //when hostname contains a slash, it is a path to file
   strcpy(path, ptr);
          host = strtok(url, "/");
   sprintf(getrequest, "GET %s HTTP/1.0\nHOST: %s\n\n", path, url);
  }
 } 

 // creates a socket to the host
 sockfd = socket(AF_INET, SOCK_STREAM, 0);
 if (sockfd < 0) {  
  printf("Error creating socket!\n");  
  exit(1);  
 }  
 printf("Socket created...\n");

 memset(&addr, 0, sizeof(addr));  
 addr.sin_family = AF_INET;  
 addr.sin_addr.s_addr = inet_addr(url);
 addr.sin_port = htons(atoi(port));

 if (connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)) < 0 ) {
  printf("Connection Error!\n");
  exit(1);
 }
 printf("Connection successful...\n\n\n");
 ptr = strtok(path, "/");
 strcpy(path, ptr);
 //printf("path=%s\n", path); 
 //fileptr = fopen(path, "w");
 //strcpy(fileName, path);
 //sprintf(fileName, "%s", path);

        //int optval = 1;
        //setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);

 // writes the HTTP GET Request to the sockfd
 write(sockfd, getrequest, strlen(getrequest));

 return sockfd;
}


int isValidIP(char * ip) {
 struct sockaddr_in addr;
 int valid = inet_pton(AF_INET, ip, &(addr.sin_addr));
 return valid != 0;
}


int parseHeader(char * header) {
 //"Date: %sHostname: %s:%d\nLocation: %s\nContent-Type: %s\n\n"
 char * line, * key, * value;
 char temp[100];
 int i = 0;
 line = strtok(header, "\n");
 while (line != NULL) {
  //printf("%s\n", line);
  strcpy(temp, line);
  value = splitKeyValue(line, i);  
  if (i == 3) {   
   strcpy(contentFileType, value);
  }
  //printf("value=%s\n", value);
  line = strtok(NULL, "\n");
  i++; 
 }
 for (i = 0; i < 4; i++) {
  if (status[i] == 0) return 1;
  //printf("status[%d]=%d\n", i, status[i]);
 }
 return 0;
}

char * splitKeyValue(char * line, int index) {
 char * temp;
 if ((temp = strstr(line, keys[index])) != NULL) {
  temp = temp + strlen(keys[index]);
  status[index] = 1;
 }
 return temp;
}

void openFile() {
 char * temp;
 char command[100];
 char fileName[1000];
 strcpy(fileName, path);
 //printf("File Name: %s\n", fileName);
 //printf("Content Type: %s\n", contentFileType);
 if ((temp = strstr(contentFileType, "text/html")) != NULL) {
  if ((temp = strstr(fileName, ".txt")) != NULL) {
   sprintf(command, "gedit %s", fileName);
  } else {
   sprintf(command, "firefox %s", fileName);
  }
  system(command);
 } else if ((temp = strstr(contentFileType, "application/pdf")) != NULL) {
  sprintf(command, "acroread %s", fileName);
  system(command);
 } else {
  printf("The filetype %s is not supported. Failed to open %s!\n", contentFileType, fileName);
 }
}

Execution: 

(terminal 1) 
gcc httpserver.c -o server 
./server 'www.dhanoop.com' '/home/dhanoopbhaskar/coding/http/dir/' 4444 

(terminal 2) 
gcc httpclient.c -o client 
./client '192.168.0.6/abc.txt' 4444 
./client '192.168.0.6/abc.html' 4444 
./client '192.168.0.6/abc.pdf' 4444

NB: The requested files abc.txt, abc.html and abc.pdf should be present in the path '/home/dhanoopbhaskar/coding/http/dir/' (or whatever is given while running server).

4 comments:

Contact Form

Name

Email *

Message *