0

I am relatively new to C, and trying to play around with some code a professor gave me, and then modifying it since then, to help me along with a server project. I cant figure out how to actually be able to see the HTML in lynx, i just get the HTTP response. (i can curl it and see what I am trying to send, but the browser will not load the HTML body) I have been checking everything I can think of, but at this point I have to admit I don't know enough about HTTP responses and could use a step in the right direction as to where I stepped off the path.

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>

#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define BUFFER_SIZE 9999
#define HTTP_METHOD     "HTTP/1.1 "
#define HTTP__OK    "200 OK\r\n"
#define HTTP__NOT_FOUND "404 Not Found\r\n"
#define SERVER_NAME     "Server: ECE435\r\n"

/* Default port to listen on */
#define DEFAULT_PORT    8080 //modify port to listen on 8080

int main(int argc, char **argv) {

    int socket_fd,new_socket_fd;
    struct sockaddr_in server_addr, client_addr;
    int port=DEFAULT_PORT;
    int n;
    socklen_t client_len;
    char buffer[BUFFER_SIZE];

    printf("Starting server on port %d\n",port);

    /* Open a socket to listen on */
    /* AF_INET means an IPv4 connection */
    /* SOCK_STREAM means reliable two-way connection (TCP) */
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd<0) {
        fprintf(stderr,"Error opening socket! %s\n",
            strerror(errno));
        exit(1);
    }

    /* Set up the server address to listen on */
    /* The memset stes the address to 0.0.0.0 which means */
    /* listen on any interface. */
    memset(&server_addr,0,sizeof(struct sockaddr_in));
    server_addr.sin_family=AF_INET;

    /* Convert the port we want to network byte order */
    server_addr.sin_port=htons(port);

    /* Bind to the port */
    if (bind(socket_fd, (struct sockaddr *) &server_addr,
        sizeof(server_addr)) <0) {
        fprintf(stderr,"Error binding! %s\n", strerror(errno));
        fprintf(stderr,"Probably in time wait, have to wait 60s if you ^C to close\n");
        exit(1);
    }

    /* Tell the server we want to listen on the port */
    /* Second argument is backlog, how many pending connections can */
    /* build up */
    listen(socket_fd,5);


wait_for_connection:


    /* Call accept to create a new file descriptor for an incoming */
    /* connection.  It takes the oldest one off the queue */
    /* We're blocking so it waits here until a connection happens */
    client_len=sizeof(client_addr);
    new_socket_fd = accept(socket_fd,
            (struct sockaddr *)&client_addr,&client_len);
    if (new_socket_fd<0) {
        fprintf(stderr,"Error accepting! %s\n",strerror(errno));
        exit(1);
    }

    while(1){
        /* Someone connected!  Let's try to read BUFFER_SIZE-1 bytes */
        memset( buffer, 0, BUFFER_SIZE );
        n = read( new_socket_fd, buffer, ( BUFFER_SIZE-1 ) );
        if (n==0){
            fprintf( stderr, "Connection to client lost\n\n" );
            break;
        }
        else if( n < 0 ){
            fprintf(stderr,"Error reading from socket %s\n",
                strerror(errno));
        }

        /* Print the message we received */
        printf("Message received: %s\n" ,buffer);


        const char *PATTERN1 = "GET /"; //first cut to make on buffer
        const char *PATTERN2 = " HTTP"; //second cut to make on buffer
        char *target = NULL; //variable to hold the slice we're taking
        char *start, *end;  //defining variables to hold start and end positions
        if ( start = strstr( buffer, PATTERN1 ) ){  //code to grab a slice of buffer
            start += strlen( PATTERN1 );        

        if ( end = strstr( start, PATTERN2 ) ){
            target = ( char * )malloc( end - start + 1 );
            memcpy( target, start, end - start );
            target[end - start] = '\0';
        }
    }
        if ( target ) printf( "Client requested: %s\n", target ); //code is working to this point. I can tell what file to get.

    time_t rawtime;
    struct tm info;
    time( &rawtime );
    struct tm * timeinfo;
    char timestamp[100];
    time_t now = time(0);
    struct tm tm = *gmtime(&now);
    strftime(timestamp, sizeof( timestamp ) , "%a, %d %b %Y %H:%M:%S %Z", &tm);
    //printf("Time is: [%s]\n", timestamp);

    struct stat file_info;      //define statistics structure for file info
    stat( target, &file_info ); //initiate file_info as the stat structure for target


    char send_client[9999];


    sprintf( send_client, "HTTP/1.0 %s%s\r\nServer: ECE435\r\nLast-Modified: Fri, 08 Sep 2017 04:31:47 GMT\r\nContent-Length: 85\r\nContent-Type: text/html\r\n\r\n", HTTP__OK, timestamp );

    char file_path[256]; //limited to 256 char for now
    sprintf( file_path, "./%s", target); //this is how you can combine char arrays: puts "./" + 'target' into 'file_path'   

    //int fd;

    //printf( "%ld\r\n" , file_info.st_size ); //this should print the File Size


    char source[BUFFER_SIZE + 1];
    FILE *fp = fopen( file_path, "r");
    if (fp != NULL) {
        size_t newLen = fread(source, sizeof(char), BUFFER_SIZE, fp);
        if (newLen == 0) {
            fputs("Error reading file", stderr);
        } else {
            source[newLen] = '\0'; /* Just to be safe. */
        }

        fclose(fp);
    }
        strcat( send_client, source);


    /* Send a response */
    printf( "\r\n%s\r\n" , send_client ); //print response before sending
    n = write( new_socket_fd, send_client , strlen(send_client) ) ;     
    if( n < 0 ){
        fprintf( stderr, "Error writing. %s\n", strerror(errno));
    }

}

close(new_socket_fd);

printf("Done connection, go back and wait for another\n\n");

goto wait_for_connection;

/* Try to avoid TIME_WAIT */
//  sleep(1);

/* Close the sockets */
close(socket_fd);

return 0;
}
2
  • 1
    This code is not even close to being a viable HTTP server. It is not following any of the rules of the HTTP spec (RFC 2616, and later RFCs 7230-7235). And it makes classic newbie mistakes, like treating the output of recv() as a null-terminated string when it isn't, not implementing any kind of buffering to handle message framing in either direction, etc... Commented Nov 9, 2018 at 23:09
  • In any case, the reason a browser can't display your HTML is because you are sending a malformed HTTP response. For instance, your timestamp is being sent as-is without any header name. You are sending 3 \r\n in between the headers and file body when you need to send only 2. And you are opening the file in text mode instead of binary mode, and not ensuring that the number of bytes you send for the file matches the Content-Length header you are sending. Commented Nov 9, 2018 at 23:09

1 Answer 1

1
    memset( buffer, 0, BUFFER_SIZE );
    n = read( new_socket_fd, buffer, ( BUFFER_SIZE-1 ) );
    if (n==0){
        fprintf( stderr, "Connection to client lost\n\n" );
        break;
    }
    else if( n < 0 ){
        fprintf(stderr,"Error reading from socket %s\n",
            strerror(errno));
    }

    /* Print the message we received */
    printf("Message received: %s\n" ,buffer);

When you call read on a TCP connection, you don't get a message. If you want to receive HTTP messages, you have to write code to do that. This is just broken.

sprintf( send_client, "HTTP/1.0 %s%s\r\nServer: ECE435\r\n"
   "Last-Modified: Fri, 08 Sep 2017 04:31:47 GMT\r\n"
   "Content-Length: 85\r\nContent-Type: text/html\r\n\r\n", HTTP__OK, timestamp );

Unless the file just happens to contain exactly 85 bytes, sending a "Content-Length" header is not a particulary good idea.

You have a while(1) loop that seems to be trying to receive multiple messages over a single connection. But there's no code to determine when you've actually received an HTTP request, so that's definitely not going to work.

The HTTP protocol is complicated and writing code to implement it correctly requires going through the standard and implementing everything that is required by the standard. This code doesn't do that and if it actually does work, it will be mostly by luck.

You may get away with just modifying the "Content-Length" header to have the correct length. But you're still violating lots of rules. For example, you could easily wind up sending more than one response to a single request because you do not ensure that read returned an entire HTTP request.

Sign up to request clarification or add additional context in comments.

4 Comments

David, the read line there was how I was getting the file request from LYNX when I specifically request test.html using: localhost:8080/test.html It does actually provide me with the HTTP client request.
@Robert As I said, if it works, it works by luck. It's not guaranteed to do that since a TCP read has no idea what an HTTP client request is.
I am really wondering why my professor started me with this then... Most of the connection structure other that the port modification was the framework he gave me. I can go in this weekend and attempt to implement everything the HTTP Protocol calls for, but I feel like my head is going to split from not being familiar with C doing that.
@Robert Unfortunately, if you want to receive an HTTP request, you'll have to write code that receives an HTTP request. It's not simple. But the short version is that you need to keep calling read, appending the data onto a buffer, until the buffer contains a complete HTTP request. Depending on why you're doing this, it may make sense to use an existing HTTP parser.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.