0

I have coded this C function to make a simple GET request to my Apache HTTP server at localhost. The function uses a socket file descriptor, sockfd.

char *send_http_get (int sockfd) {
  // Allocate a buffer to write/read bytes
  const int buffer_size = 512;
  char *buffer = malloc(buffer_size);

  // write "GET /" to the server
  bzero(buffer, buffer_size);
  strncpy(
    buffer, 
    "GET / HTTP/1.1\r\n"
    "\r\n",
    buffer_size
  );
  
  write(sockfd, buffer, strlen(buffer));

  // Read the response from the server
  bzero(buffer, buffer_size);
  read(sockfd, buffer, buffer_size);

  // Make buffer a c-string
  buffer[buffer_size - 1] = 0;
  return buffer;
}

The function sends the request and receives the response, but something very silly may be wrong with the sending process as I always get a 400 Bad Request status code from my Apache HTTP server.

HTTP/1.1 400 Bad Request
Date: Fri, 28 Oct 2022 11:31:21 GMT
Server: Apache/2.4.51 (Unix)
Content-Length: 226
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
</body></html>

This is a complete test program to test the idea.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h> // for hostent

#define SA struct sockaddr
#define DEFAULT_TIMEOUT 5
#define HOSTNAME_MAX_LEN 128

///////////////////
// TCP FUNCTIONS //
///////////////////

char *send_http_get (int sockfd) {
  // Allocate a buffer to write/read bytes
  const int buffer_size = 512;
  char *buffer = malloc(buffer_size);

  // Write "GET /" to the server
  bzero(buffer, buffer_size);
  strncpy(
    buffer, 
    "GET / HTTP/1.1\r\n"
    "\r\n",
    buffer_size
  );
  
  write(sockfd, buffer, strlen(buffer));

  // read the response from the server
  bzero(buffer, buffer_size);
  read(sockfd, buffer, buffer_size);

  // Make buffer a c-string
  buffer[buffer_size - 1] = 0;
  return buffer;
}

/**
 * Creates a client socket to connect to the target server at hostname:port.
 * Returns the client socket file descriptor or -1 if anything goes wrong.
 */
int connect_to_server (char *hostname, int port) {
  // Ask for a new client socket to the OS
  int sockfd;
  struct sockaddr_in ipa;
  sockfd = socket(AF_INET, SOCK_STREAM, 0);

  // Check the socket is OK
  if (sockfd == -1) {
    fprintf(stderr, "Error: Cannot create a socket\n");
    return -1;
  }

  // Zero the ipa struct
  bzero(&ipa, sizeof(ipa));

  // Populate the server IP address struct
  ipa.sin_family = AF_INET;
  ipa.sin_addr.s_addr = inet_addr(hostname);
  ipa.sin_port = htons(port);

  // Connect the client socket to the server socket
  if (connect(sockfd, (SA*)&ipa, sizeof(ipa)) != 0) {

    // todo: find everything that can be learned from the remote socket

    close(sockfd);
    return -1;
  }

  // Set a timeout for read operations on the server
  // See https://stackoverflow.com/a/2939145/8520235
  struct timeval tv;
  tv.tv_sec = 2; // 2s
  tv.tv_usec = 0;
  setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);

  return sockfd;
}


//////////////////
// MAIN PROGRAM //
//////////////////

int main () {
  int sockfd;
  char *buffer;

  // Connect to the server port
  if ((sockfd = connect_to_server("127.0.0.1", 80)) > 0) {
    buffer = send_http_get(sockfd);
    printf("%s\n", buffer);
    close(sockfd);
    free(buffer);
  }

  return 0;
}

Any idea of what is going wrong here?

EDIT A Host header was missing, as Example person mentioned.

  strncpy(
    buffer, 
    "GET / HTTP/1.1\r\n"
    "Host: localhost\r\n"
    "\r\n",
    buffer_size
  );

A good explanation on the need of Host header can be found here https://stackoverflow.com/a/8824910/8520235

coterobarros
  • 941
  • 1
  • 16
  • 25
  • have you checked with wireshark that your message is sent correctly? – stark Oct 28 '22 at 12:00
  • no, I have no Wireshark in this computer. – coterobarros Oct 28 '22 at 12:00
  • A `printf(buffer)` before sending the request returns the expected `GET / HTTP/1.1 ` – coterobarros Oct 28 '22 at 12:02
  • Ok, I installed Wireshark and captured the sequence. There is a "Continuation" after the request that does not appears when the request is performed from a normal browser. – coterobarros Oct 28 '22 at 12:14
  • 1
    Minor issue: `write()` is allowed to perform short writes. For robustness, you need to check its return value to see how many bytes it actually wrote (or if it failed), and if necessary, perform additional `write()`s to transfer the rest of the bytes you want to send. But I don't think failure to do this is causing the particular HTTP errors you ask about. – John Bollinger Oct 28 '22 at 12:21
  • 3
    The "Host" header is missing. It is required by HTTP/1.1. – Example person Oct 28 '22 at 12:22
  • Fixed after adding the Host header. Thank you. ` "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "\r\n",` – coterobarros Oct 28 '22 at 12:25
  • Thank you Example person – coterobarros Oct 28 '22 at 12:27
  • 1
    @coterobarros if you're going to try HTTP/2 or HTTP/3 in the future, please note that they are not text-based protocols as the `curl` command may represent, but they are binary protocols. So you might need to use some tool like nghttp2 or nghttp3 for those protocols. – Example person Oct 28 '22 at 12:36

0 Answers0