Skip to content

Stage 15: HTTP Response Module

Recap

  • In the previous stage we have seen how to parse an HTTP request and create a request instance.
  • Two new modules named xps_http and xps_http_req were introduced to achieve this.

Learning Objective

  • In this stage we will see how to create an HTTP response for the HTTP request received.
  • A new module named xps_http_res is created for this.

Introduction

In the previous stage we have seen how to parse an incoming HTTP request from the client and how to create a request instance. There the responses to the received HTTP requests were directly being send by the session_process_request() function in the session module. But in reality the HTTP responses that we receive from a web browser follow a specific structure. Thus it is essential to follow the structure while sending responses back to client from the server. In this stage we will send HTTP responses from server back to client. Before getting into this stage it is very essential to understand the basic structure of an HTTP response. Let’s have a quick glance through the structure of an HTTP response.

HTTP Response

An HTTP response is sent by a server to a client as a reply to an HTTP request. The response provides the outcome of the request and may include the requested content. The structure of an HTTP response typically consists of three main components: the status line, headers, and an optional body.

Status line: is the first line that the client receives from the server after making a request. It provides essential information about result of the request. It consists of three parts such as, HTTP version, status code and status text.

  • HTTP version - indicates the protocol version used by the server, which helps the client understand supported features like persistent connections or multiplexing(e.g., HTTP/1.1),
  • Status code - a three-digit number that categorizes the response—1xx for informational, 2xx for success (e.g., 200 OK), 3xx for redirection (e.g., 301 Moved Permanently), 4xx for client errors (e.g., 404 Not Found), and 5xx for server errors (e.g., 500 Internal Server Error).
  • Status text - a human-readable phrase that describes the status code, aiding in debugging and clarity.(e.g., OK, Not Found, Internal Server Error).

Response headers: These are similar to request headers and provide additional information about the server's response. They appear as key-value pairs and help the client understand how to handle the response data. These headers can define the format of the data, manage caching, control connection behavior, and more. eg:

  • Content-Type: Specifies the format of the response body (e.g., text/html, application/json), helping the client understand how to process the data.
  • Content-Length: Indicates the size of the response body in bytes.
  • Date: Shows the exact date and time when the response was generated by the server.
  • Cache-Control: Provides caching directives to control how and for how long the response can be cached.
  • Connection: Informs whether the server will keep the connection alive (keep-alive) or close it (close) after the response.
  • Server: Reveals information about the server software (e.g., Apache, nginx).
  • Set-Cookie: Sends cookies from the server to the client, which can be stored and sent back with future requests.

Body : it is the actual content returned by server in response to the client request. It can be of various formats like html(for rendering pages), json(used in APIs for exchanging structured data), xml(structured data format, often used in older systems or specific applications), plain text(simple unformatted text), binary data(such as images, videos, or downloadable files) etc. With respect to the format of the body, Content-Type header is set.

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 138
Connection: keep-alive

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
</head>
<body>
    <h1>Hello, World!</h1>
</body>
</html>

In the above HTTP response, line, we get the response line as HTTP/1.1 200 OK. Here

HTTP version : HTTP/1.1

Status code : 200

Status text : OK

Here, the response headers include the key-value pairs, Content-Type: text/html; charset=UTF-8, Content-Length: 138, Connection: keep-alive

Here the body of the response is an html document shown above.

So now we are familiar with the HTTP response structure. In this stage we will implement the HTTP responses with respect to these specifications.

File Structure

stage-15-filestructure.png

Design

For implementing HTTP responses, a new module named xps_http_res has to be created. A new struct named xps_http_res_s is introduced in this module. The struct xps_http_res_s contain fields for response line, headers and body. xps_http_res module will take care of the creation, cleanup and serialization of the HTTP response headers. A new function named xps_http_set_header() has to be added in the xps_http module. This function is responsible for adding key-value pairs to the headers list.

Implementation

design.png

In this stage we will be creating the new module named xps_http_res and modifications are there in the existing modules like, xps_http , xps_session

xps_http_res Module

xps_http_res.h

The code below has the contents of the header file for xps_http_res. Have a look at it and make a copy of it in your codebase.

  • expserver/src/http/xps_http_res.h

    c
    #ifndef XPS_HTTP_RES_H
    #define XPS_HTTP_RES_H
    
    #include "../xps.h"
    
    struct xps_http_res_s {
      char response_line[70];
      vec_void_t headers;
      xps_buffer_t *body;
    };
    
    xps_http_res_t *xps_http_res_create(xps_core_t *core, u_int code);
    void xps_http_res_destroy(xps_http_res_t *res);
    xps_buffer_t *xps_http_res_serialize(xps_http_res_t *res);
    void xps_http_res_set_body(xps_http_res_t *http_res, xps_buffer_t *buff);
    
    #endif

A new struct xps_http_res_s is introduced here. From this stage on wards an HTTP response will be represented using this struct. It contain fields to carry the information related to HTTP responses. The fields of xps_http_res_s are as follows,

  • char response_line[70] : a character array that contains the whole response line of the HTTP response. It will include HTTP version, status code and status text.
  • vec_void_t headers : an array of parsed headers in the form of key-value pairs.
  • xps_buffer_t *body : a buffer carrying the body of the response

xps_http_res.c

The main functions introduced in the http_res module are xps_http_res_create(), xps_http_res_destroy() and xps_http_res_serialize() . Let’s see them in more detail.

xps_http_res_create() : creates an HTTP response. The overview of this function is as follows:

  • Allocates memory to struct xps_http_res_t .
c
xps_http_res_t *http_res = malloc(/* fill this */);
  if (http_res == NULL) {
    logger(LOG_ERROR, "xps_http_res_create()",
           "failed to alloc memory for http_res. malloc() returned NULL");
    return NULL;
  }
  • Initialize the response_line, headers and body fields of the struct. Initializing of the response line is done based on the status code received. The HTTP version used is HTTP/1.1, which is an improved version of HTTP. For each status code the status line is to be given appropriately. The response_line for status code 200 looks like "HTTP/1.1 200 OK" .
  • The headers are to be added to the headers field of the xps_http_res struct, in the form of key-value pairs. For this xps_http_set_header() can be used. Default headers like Date, Server and Access-Control-Allow-Origin are to be set here. Date header should contain current date and time, when the response is generated. Server name can be hard coded to eXpServer in xps.h, which is to be used as value for server key in the header. For Access-Control-Allow-Origin, the value can be set to “*”. This header tells browsers(clients) that they can allow any website to access the resource, effectively enabling cross-origin requests without restrictions.
c
xps_http_set_header(&(http_res->headers), "Date", time_buf);
xps_http_set_header(&(http_res->headers), "Server", SERVER_NAME);
xps_http_set_header(&(http_res->headers), "Access-Control-Allow-Origin", "*");
  • Finally return the response instance created.

xps_http_res_destroy() : releases the memory and resources allocated to the http_response object. The body of the response and keys and values present in each of the headers are released.

xps_http_res_serialize() : used for serializing the whole response into a buffer. It is similiar to the serialize function used in http_request module.

  • First the headers are serialized using xps_http_res_serialize() , provided in the xps_http module and are stored into a temporary buffer.
  • Then create a buffer instance for storing the serialized HTTP response. The length of the buffer is calculated by adding the lengths of each field in the struct xps_http_res_s.
  • Copy the response_line, header and body to the newly created buffer. Don’t forget to include a newline between response_line, headers and body.
c
xps_buffer_t *xps_http_res_serialize(xps_http_res_t *http_res) {
  /* valid params */
  
  // Serialize headers
  xps_buffer_t *headers_str = /* fill this */;
  if (headers_str == NULL) {
    logger(LOG_ERROR, "xps_http_res_serialize()", "failed to serialize headers");
    return NULL;
  }

  // Calculate length for final buffer
  size_t final_len = /* fill this */;

  // Create instance for final buffer
  xps_buffer_t *buff = /* fill this */;
  if (buff == NULL) {
    logger(LOG_ERROR, "xps_http_res_serialize()", "failed to create buffer instance");
    xps_buffer_destroy(headers_str);
    return NULL;
  }

  // Copy everything
  /* copy response line */
  /* copy headers */
  /* copy response body*/

  xps_buffer_destroy(headers_str);

  return buff;
}

So we have successfully built the response module. Now it’s time to use this module, therefore we will have to make certain modifications in some of the existing modules.

Modifications to xps_http Module

xps_http.c

A new function named xps_http_set_header() is added in this stage. This function is used to add a key-value pair to the headers list. Given a key and value, first a header is created and then added to the header list. This function is used in the xps_http_res_create() , while setting the headers.

c
int xps_http_set_header(vec_void_t *headers, const char *key, const char *val) {
  // Validate params
  /* Validate params */

  xps_keyval_t *header = /* fill this */;
  if (header == NULL) {
    logger(LOG_ERROR, "xps_http_set_header()", "malloc() failed for 'header'");
    return E_FAIL;
  }
  
  /* allocate memory for header->key and header->val */

  if (header->key == NULL || header->val == NULL) {
    free(header->key);
    free(header->val);
    free(header);
    logger(LOG_ERROR, "xps_http_set_header()",
           "malloc() failed for 'header->key' or 'header->val'");
    return E_FAIL;
  }

  /* copy contents of key and value given into the header fields */

  /* add the header to header list */

  return OK;
}

Modifications to xps_session Module

void session_process_request()

The xps_http_res_create() function is getting called from this function. Based on the HTTP request received, the HTTP response is getting created.

  • If the HTTP request was NULL, then an HTTP response is created with the response status code set to HTTP_BAD_REQUEST. It indicates that there was error from client due to which the server can’t process the request. (The HTTP request will become NULL, if the requested URL was incorrect or if there were some missing fields or invalid data types in the request.)

  • If there was error while creating the file to be served by the server, then an HTTP response is created with response status codes set to indicate those particular errors. For the error E_PERMISSION, the status code is set to HTTP_FORBIDDEN and for E_NOTFOUND it is set to HTTP_NOT_FOUND.

    HTTP_NOT_FOUND indicates that the server could not find the requested resources.

    HTTP_FORBIDDEN indicates that the server is unable to authorize the request. It can be due to insufficient permissions or access controls.

  • If there were no errors with the request, then HTTP response is created with HTTP_OK status code.

  • If the path for the requested file is not found, then HTTP response is created with status code set to HTTP_NOT_FOUND.

  • After creating the http response, headers for content length and type are added using xps_http_set_header().

  • Further the response is serialized using xps_http_res_serialize() and it is then stored into the to_client_buff .

Milestone #1

So now we have completed the implementation of the response module. Now it’s time to verify the implementation. In stage 14, we have seen on requesting a file in the public folder, through an HTTP request, the contents get displayed in the browser. (There we have hard coded the HTTP responses in the session process request function itself.) Similarly check whether on passing a valid HTTP request, whether contents in the file mentioned is getting displayed in the browser or not.

First compile the changes and run the server.

In the browser enter the HTTP request (eg : http://localhost:8001/sample.txt, sample.txt should be present in the public folder).

Verify whether the contents in the file is getting displayed on the browser or not. If the contents are getting displayed correctly then we can infer that the body of response is created and rendered properly.

Milestone #2

Now let’s verify whether the response line and headers are generated and getting displayed correctly or not.

First start the server and in the browser enter an HTTP request (eg: http://localhost:8001/sample.txt). In the browser try opening the networks tab.(right click on the screen, click on inspect option then select networks tab on top side).

Inside networks tab you can see all the requests made by the client. Click on the current request(ie sample.txt). Then select the headers tab seen on top. In the headers section, you can see response headers section. Check whether the response headers that we created are getting displayed correctly. Also verify the status code displayed is correct or not.

Conclusion

In this stage, we modularized the HTTP response handling by introducing the xps_http_res module, which manages the creation, cleanup, and serialization of HTTP responses. This replaced the earlier hardcoded approach with a structured system that adheres to standard HTTP response formats, including status lines, headers, and body content. The xps_http_res_s structure was implemented to represent responses, and new functions were added for setting headers and serializing data. We also updated the xps_http and xps_session modules to integrate this new functionality. Successful testing through browser rendering and inspection confirmed that responses are correctly structured and transmitted.