/***********************************************************************
* Code listing from "Advanced Linux Programming," by CodeSourcery LLC  *
* Copyright (C) 2001 by New Riders Publishing                          *
* See COPYRIGHT for license information.                               *
***********************************************************************/

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "server.h"

/* HTTP response and header for a successful request.  */

static char* ok_response =
  "HTTP/1.0 200 OK\n"
  "Content-type: text/html\n"
  "\n";

/* HTTP response, header, and body indicating that the we didn't
   understand the request.  */

static char* bad_request_response = 
  "HTTP/1.0 400 Bad Request\n"
  "Content-type: text/html\n"
  "\n"
  "\n"
  " \n"
  "  

Bad Request

\n" "

This server did not understand your request.

\n" " \n" "\n"; /* HTTP response, header, and body template indicating that the requested document was not found. */ static char* not_found_response_template = "HTTP/1.0 404 Not Found\n" "Content-type: text/html\n" "\n" "\n" " \n" "

Not Found

\n" "

The requested URL %s was not found on this server.

\n" " \n" "\n"; /* HTTP response, header, and body template indicating that the method was not understood. */ static char* bad_method_response_template = "HTTP/1.0 501 Method Not Implemented\n" "Content-type: text/html\n" "\n" "\n" " \n" "

Method Not Implemented

\n" "

The method %s is not implemented by this server.

\n" " \n" "\n"; /* Handler for SIGCHLD, to clean up child processes that have terminated. */ static void clean_up_child_process (int signal_number) { int status; wait (&status); } /* Process an HTTP "GET" request for PAGE, and send the results to the file descriptor CONNECTION_FD. */ static void handle_get (int connection_fd, const char* page) { struct server_module* module = NULL; /* Make sure the requested page begins with a slash and does not contain any additional slashes -- we don't support any subdirectories. */ if (*page == '/' && strchr (page + 1, '/') == NULL) { char module_file_name[64]; /* The page name looks OK. Construct the module name by appending ".so" to the page name. */ snprintf (module_file_name, sizeof (module_file_name), "%s.so", page + 1); /* Try to open the module. */ module = module_open (module_file_name); } if (module == NULL) { /* Either the requested page was malformed, or we couldn't open a module with the indicated name. Either way, return the HTTP response 404, Not Found. */ char response[1024]; /* Generate the response message. */ snprintf (response, sizeof (response), not_found_response_template, page); /* Send it to the client. */ write (connection_fd, response, strlen (response)); } else { /* The requested module was loaded successfully. */ /* Send the HTTP response indicating success, and the HTTP header for an HTML page. */ write (connection_fd, ok_response, strlen (ok_response)); /* Invoke the module, which will generate HTML output and send it to the client file descriptor. */ (*module->generate_function) (connection_fd); /* We're done with the module. */ module_close (module); } } /* Handle a client connection on the file descriptor CONNECTION_FD. */ static void handle_connection (int connection_fd) { char buffer[256]; ssize_t bytes_read; /* Read some data from the client. */ bytes_read = read (connection_fd, buffer, sizeof (buffer) - 1); if (bytes_read > 0) { char method[sizeof (buffer)]; char url[sizeof (buffer)]; char protocol[sizeof (buffer)]; /* Some data was read successfully. NUL-terminate the buffer so we can use string operations on it. */ buffer[bytes_read] = '\0'; /* The first line the client sends is the HTTP request, which is composed of a method, the requested page, and the protocol version. */ sscanf (buffer, "%s %s %s", method, url, protocol); /* The client may send various header information following the request. For this HTTP implementation, we don't care about it. However, we need to read any data the client tries to send. Keep on reading data until we get to the end of the header, which is delimited by a blank line. HTTP specifies CR/LF as the line delimiter. */ while (strstr (buffer, "\r\n\r\n") == NULL) bytes_read = read (connection_fd, buffer, sizeof (buffer)); /* Make sure the last read didn't fail. If it did, there's a problem with the connection, so give up. */ if (bytes_read == -1) { close (connection_fd); return; } /* Check the protocol field. We understand HTTP versions 1.0 and 1.1. */ if (strcmp (protocol, "HTTP/1.0") && strcmp (protocol, "HTTP/1.1")) { /* We don't understand this protocol. Report a bad response. */ write (connection_fd, bad_request_response, sizeof (bad_request_response)); } else if (strcmp (method, "GET")) { /* This server only implements the GET method. The client specified some other method, so report the failure. */ char response[1024]; snprintf (response, sizeof (response), bad_method_response_template, method); write (connection_fd, response, strlen (response)); } else /* A valid request. Process it. */ handle_get (connection_fd, url); } else if (bytes_read == 0) /* The client closed the connection before sending any data. Nothing to do. */ ; else /* The call to read failed. */ system_error ("read"); } void server_run (struct in_addr local_address, uint16_t port) { struct sockaddr_in socket_address; int rval; struct sigaction sigchld_action; int server_socket; /* Install a handler for SIGCHLD that cleans up child processes that have terminated. */ memset (&sigchld_action, 0, sizeof (sigchld_action)); sigchld_action.sa_handler = &clean_up_child_process; sigaction (SIGCHLD, &sigchld_action, NULL); /* Create a TCP socket. */ server_socket = socket (PF_INET, SOCK_STREAM, 0); if (server_socket == -1) system_error ("socket"); /* Construct a socket address structure for the local address on which we want to listen for connections. */ memset (&socket_address, 0, sizeof (socket_address)); socket_address.sin_family = AF_INET; socket_address.sin_port = port; socket_address.sin_addr = local_address; /* Bind the socket to that address. */ rval = bind (server_socket, &socket_address, sizeof (socket_address)); if (rval != 0) system_error ("bind"); /* Instruct the socket to accept connections. */ rval = listen (server_socket, 10); if (rval != 0) system_error ("listen"); if (verbose) { /* In verbose mode, display the local address and port number we're listening on. */ socklen_t address_length; /* Find the socket's local address. */ address_length = sizeof (socket_address); rval = getsockname (server_socket, &socket_address, &address_length); assert (rval == 0); /* Print a message. The port number needs to be converted from network byte order (big endian) to host byte order. */ printf ("server listening on %s:%d\n", inet_ntoa (socket_address.sin_addr), (int) ntohs (socket_address.sin_port)); } /* Loop forever, handling connections. */ while (1) { struct sockaddr_in remote_address; socklen_t address_length; int connection; pid_t child_pid; /* Accept a connection. This call blocks until a connection is ready. */ address_length = sizeof (remote_address); connection = accept (server_socket, &remote_address, &address_length); if (connection == -1) { /* The call to accept failed. */ if (errno == EINTR) /* The call was interrupted by a signal. Try again. */ continue; else /* Something else went wrong. */ system_error ("accept"); } /* We have a connection. Print a message if we're running in verbose mode. */ if (verbose) { socklen_t address_length; /* Get the remote address of the connection. */ address_length = sizeof (socket_address); rval = getpeername (connection, &socket_address, &address_length); assert (rval == 0); /* Print a message. */ printf ("connection accepted from %s\n", inet_ntoa (socket_address.sin_addr)); } /* Fork a child process to handle the connection. */ child_pid = fork (); if (child_pid == 0) { /* This is the child process. It shouldn't use stdin or stdout, so close them. */ close (STDIN_FILENO); close (STDOUT_FILENO); /* Also this child process shouldn't do anything with the listening socket. */ close (server_socket); /* Handle a request from the connection. We have our own copy of the connected socket descriptor. */ handle_connection (connection); /* All done; close the connection socket, and end the child process. */ close (connection); exit (0); } else if (child_pid > 0) { /* This is the parent process. The child process handles the connection, so we don't need our copy of the connected socket descriptor. Close it. Then continue with the loop and accept another connection. */ close (connection); } else /* Call to fork failed. */ system_error ("fork"); } }