«  4.7. Application-Layer Broadcasting: DHCP   ::   Contents   ::   5.1. The Internet and Connectivity  »

4.8. Extended Example: CGI Web Server

This Extended Example creates a web server for processing requests using the common gateway interface (CGI). In a standard web server, HTML files are stored on the file system and their contents are sent through the socket when requested. With CGI, the server can execute a compiled program. Whatever the program writes to its standard output gets redirected through the socket to the client. This technique supports dynamic server-side execution, such as generating HTML based on database query results.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#include <arpa/inet.h>
#include <assert.h>
#include <netdb.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#define BUFFER_LENGTH 1000
#define WHITESPACE " \t\r\n"
#define CRLF "\r\n"

static void sigint_handler (int signum); /* SIGINT handler */
static bool process_request (int);
static int setup_server (uint16_t);

int serverfd = -1; /* Server socket file descriptor */

int
main (int argc, char *argv[])
{
  /* Create a sigaction and link it to the handler */
  struct sigaction sa;
  sa.sa_handler = sigint_handler;
  assert (sigaction (SIGINT, &sa, NULL) != -1);
  assert (sigaction (SIGTERM, &sa, NULL) != -1);

  /* Get the port number from the command line and set up the
     server. If the setup does not work, the socket file
     descriptor will be negative; exit if that happens. */
  if (argc != 2)
    return EXIT_FAILURE;
  uint16_t port = (uint16_t) strtoul (argv[1], NULL, 10);
  if ( (serverfd = setup_server (port)) < 0)
    {
      perror ("Failed to set up web server");
      return EXIT_FAILURE;
    }

  /* Enter a loop for processing web connections */
  bool running = true;
  while (running)
    {
      struct sockaddr_in address;
      memset (&address, 0, sizeof (address));
      socklen_t addrlen = 0;

      int connection = accept (serverfd, (struct sockaddr *)&address, &addrlen);
      if (connect < 0)
        break;

      running = process_request (connection);
    }

  /* After receiving a shutdown request, close the server
     socket and exit. Note that it would be good to include a
     signal handler (Chapter 2) to close the socket if the
     process is killed with SIGINT or another signal. */
  shutdown (serverfd, SHUT_RDWR);
  close (serverfd);
  serverfd = -1;
  return EXIT_SUCCESS;
}

/* Parse the incoming HTTP request and look for the requested
   file. It must be of the form /cgi-bin/foo.cgi or it must be
   shutdown. All other requests are ignored. Returning true
   from this function keeps the server running, while returning
   false will shut the server down. */
static bool
process_request (int connection)
{
  /* Read the HTTP request from the socket into a local buffer */
  char buffer[BUFFER_LENGTH];
  memset (buffer, 0, BUFFER_LENGTH);
  size_t bytes = read (connection, buffer, BUFFER_LENGTH - 1);
  if (bytes <= 0)
    {
      perror ("No data read from socket");
      shutdown (connection, SHUT_RDWR);
      close (connection);
      return true;
    }

  /* This minimal web server only supports requests of the
     following formats:

       GET /cgi-bin/hello.cgi HTTP/1.1 ...
       GET /cgi-bin/hello.cgi?username=me HTTP/1.1 ...
       GET /shutdown ...

   */

  /* Reject any request that doesn't start with "GET" */
  char *token = strtok (buffer, WHITESPACE);
  if (strncmp (token, "GET", 4))
    {
      perror ("Invalid HTTP request received");
      shutdown (connection, SHUT_RDWR);
      close (connection);
      return true;
    }

  /* Check for a shutdown request. Note that all URIs passed from
     the web browser will begin with a '/' character, so we are
     looking for "/shutdown". */
  token = strtok (NULL, WHITESPACE);
  if (!strncmp (token, "/shutdown", 10))
    {
      /* Send a message confirming the shutdown request, then
         return false to shut the server down */
      char *message = "HTTP/1.1 200 OK" CRLF
        "Connection: close" CRLF
        "Content-Type: text/html; charset=UTF-8" CRLF CRLF
        "<html><body><h2>Shutdown received</h2>"
        "<p>Goodbye</p></body></html>\n";

      write (connection, message, strlen (message));
      shutdown (connection, SHUT_RDWR);
      close (connection);
      return false;
    }

  /* For this example, we are only supporting CGI executable
     files and they must exist in a "cgi-bin" subdirectory of the
     server's working directory. Reject any request that does not
     begin with "/cgi-bin/". */
  if (strncmp (token, "/cgi-bin/", 9))
    {
      shutdown (connection, SHUT_RDWR);
      close (connection);
      return true;
    }

  /* Remove the leading '/' character, as we are looking for
     files based on an relative path, not an absolute path */
  char *cgi_file = token + 1;

  /* Search for a query string, which begins immediately following
     the '?' character if it exists. If one is found, keep track
     of where the query string starts and replace the '?' with a 
     null byte. This will ensure the file name is properly
     terminated. For example, "cgi-bin/hello.cgi?user=me" will be
     converted to the cgi_file "cgi-bin/hello.cgi" while the 
     question pointer will point to "user=me". */
  char *question = strchr (cgi_file, '?');
  if (question != NULL)
    {
      *question = '\0';
      question++;
    }
  /* Check for file read and execute permissions */
  if (access (cgi_file, R_OK | X_OK) != 0)
    {
      shutdown (connection, SHUT_RDWR);
      close (connection);
      return true;
    }

  /* Now we are ready to respond. Write back an HTTP/1.1 header
     to the web browser then run the CGI program. */
  char *message = "HTTP/1.1 200 OK" CRLF "Connection: close" CRLF
    "Content-Type: text/html; charset=UTF-8" CRLF CRLF;
  write (connection, message, strlen (message));

  pid_t child_pid = fork ();
  if (child_pid < 0)
    {
      shutdown (connection, SHUT_RDWR);
      close (connection);
      return false;
    }

  if (child_pid == 0)
    {
      /* If query string passed, set the environment variable */
      if (question != NULL)
        setenv ("QUERY_STRING", question, 1);

      /* Redirect the child process's STDOUT to write into the
         socket and execute the CGI program */
      dup2 (connection, STDOUT_FILENO);
      execlp (cgi_file, cgi_file, NULL);
      return true;
    }

  /* The parent waits until the child process runs (writing to the
     client over the socket), then closes the socket and continues
     with the next request */
  wait (NULL);
  shutdown (connection, SHUT_RDWR);
  close (connection);
  return true;
}

/* Set up a basic HTTP server on the localhost, using the port
   number passed on the command line. In this example, we are
   manually configuring the server to use 127.0.0.1 (localhost
   loopback) as the IP address. */
static int
setup_server (uint16_t port)
{
  /* Manually configure socket address and protocol information */
  struct sockaddr_in localhost;
  memset (&localhost, 0, sizeof (localhost));
  localhost.sin_family = AF_INET;                 /* IPv4 address */
  localhost.sin_port = htons (port);              /* port number from user */
  localhost.sin_addr.s_addr = htonl (0x7f000001); /* localhost loopback */

  struct addrinfo server;
  memset (&server, 0, sizeof (server));
  server.ai_family = AF_INET;         /* grab IPv4 only */
  server.ai_socktype = SOCK_STREAM;   /* specify byte-streaming */
  server.ai_flags = AI_PASSIVE;       /* use default IP address */
  server.ai_protocol = IPPROTO_TCP;   /* create as a TCP socket */
  server.ai_addrlen = INET_ADDRSTRLEN; /* IPv4 address length */
  server.ai_addr = (struct sockaddr *) &localhost;

  /* Create a TCP socket, then configure it to ignore bind reuse
     false errors and set a 5-second timeout for receiving */
  int socketfd = socket (server.ai_family, server.ai_socktype, 0);
  assert (socketfd >= 0);
  int sockopt = 1;
  setsockopt (socketfd, SOL_SOCKET, SO_REUSEADDR, (const void *) &sockopt,
              sizeof (int));
  struct timeval timeout = { 5, 0 };
  setsockopt (socketfd, SOL_SOCKET, SO_RCVTIMEO, (const void *) &timeout,
              sizeof (timeout));

  /* Bind the socket to the port number and convert it to a
     server socket */
  if (bind (socketfd, server.ai_addr, server.ai_addrlen) != 0) 
    {
      close (socketfd);
      return -1;
    }
  listen (socketfd, 10);
  return socketfd;
}

/* Interrupt handler in case we need to shut down with Ctrl-c.
   This helps to ensure the server socket is shutdown cleanly. */
static void
sigint_handler (int signum)
{
  if (serverfd >= 0)
    {
      shutdown (serverfd, SHUT_RDWR);
      close (serverfd);
    }
  exit (EXIT_SUCCESS);
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* This file needs to be compiled and the executable should be
   placed in a subdirectory of the directory where the server
   runs. If the server runs on port 8000 and this file is
   compiled into an executable named "hello.cgi", it can be
   accessed with a web browser at
     http://localhost:8000/cgi-bin/hello.cgi
 */
#include <stdio.h>
#include <stdlib.h>
/* CGI (common gateway interface) version of "Hello world".
   Programs like this can write to STDOUT. The web server that
   runs the executable form of this will redirect that output
   into a socket created for a web page access. */
int
main (void)
{
  printf ("<html>\n<body>\n");
  printf ("<h2>Hello world!</h2>\n");
  printf ("<p>Your CGI bin is set up properly</p>\n");
  /* Check to see if an HTTP query string was passed */
  char *query = getenv ("QUERY_STRING");
  if (query != NULL)
    printf ("<p>Here is your query string data: %s</p>\n", query);

  printf ("</body>\n</html>\n");
  return 0;
}
«  4.7. Application-Layer Broadcasting: DHCP   ::   Contents   ::   5.1. The Internet and Connectivity  »

Contact Us License