Project

To put all of this together, we're going to make an HTTP client and server utility. The program will be able to host web servers and connect to others as well. It will be a command line program that accepts the following commands:

  • -serve <directory> - create a server with the specified root directory
  • -get <url> - GET request
  • -post <url> - POST request
  • -port <port> - set the port number on the server or request
    • Overrides any port specified in the URL for a request
  • --<header> <value> - set a HTTP header in a request
  • -content [-f] <text> - specifies the content in a request or server. An optional -f command may follow indicating that the content is to be read from the specified file. If the content contains spaces, it must be enclosed with quotes. If content is specified for a server, this essentially specifies the index.html.
  • -ssl [-only] <certificate> - makes the server use SSL using the specified certificate file. If the -only flag is set the server rejects plaintext
  • -o <file> - stores the result of a request into the specified file
  • If no arguments are passed, or invalid/incorrect arguments are used, a help message containing the above information should be shown.

The program should be cross-compatible on Windows and Unix systems. For windows, you will have to link with Ws2_32.lib and most of the networking stuff will be in the WinSock2.h header. You may also need WS2tcpip.h (and others). For Unix, you may want to look at sys/socket.h, sys/types.h, netinet/in.h, sys/select.h, fcntl.h, arpa/inet.h, sys/ioctl.h, unistd.h, netdb.h. The socket API for both libraries are very similar, but there are a few slight differences: (these are not the only ones)

WindowsUnix
ioctlsocket(socket, long cmd, u_long* arg)ioctl(int fd, long cmd, ...) (sort of equal but not quite)
closesocket(socket)shutdown(sock, SHUT_RDWR); close(sock) (the shutdown() isn't necessary)
WSAGetLastError()errno

Winsock requires manual initialization with WSAStartup() and WSACleanup().

Start with whatever OS you natively have, then try to support the other afterwards. This is a good case for extracting the OS specific code into a policy which can be passed to the socket classes as a template argument.

Since both of these APIs are C APIs, most of their functions return error codes. It's important to make sure these errors are handled.

Setup

Starter code available here. You will also need to install OpenSSL. For windows, you can download pre-compiled binaries via an installer here. OpenSSL also have a list of some other recommended third parties for getting the pre-compiled binaries here.

For Unix users, you can simply install libssl-dev and openssl from your package manager. You will also need to make sure you have CMake and Doxygen installed. Ubuntu users can simply install cmake and doxygen from apt (other distros might have the binaries available from the package manager, otherwise go the the link), and Windows users can find installations for cmake here and doxygen here. You may need to install GraphViz (what Doxygen uses to generate inheritance graphs) separately here. For Windows, you should add dot to your path system variable via the installer.

The starter code has the following architecture. Black boxes indicate already provided modules. The double angled brackets indicates an interface, an open arrow indicates a subtyping relationship, and a closed arrow indicates a dependency. Boxes with dotted lines indicates unimplemented modules and only show a possible implementation. You may find that this possible architecture poses problems (I just drew it up quickly). Furthermore, in the spirit of YAGNI ("you ain't gonna need it"), you may find some things (such as an abstract server and client) unnecessary.

Image of Possible Architecture

The starter code contains a Networking.h file that has os dependent includes. Ideally, you should minimize dependencies of every module, thus this file should really only be included in source files or header files that do not need to be directly included by the client.

Goals

  • Gain experience with GTest, GMock, and CMake
  • Practice with static and dynamic polymorphism in C++ and programming to an interface
  • Practice designing template function and classes
  • Use C++ Idioms, Clean Architecture and Design Patterns
  • Familiarize yourself with the standard library
  • Learn about socket programming
  • Experience with Doxygen

What you'll (probably) need to know

  • Section 2 (Basics to Basic Containers)
  • Template Basics Chapter (at least to specialization)
  • STL Functional Programming Chapter
  • Section 4 (Tools and Project)

Tasks

  • Implement the Socket Class
    • This class will adhere to the RemotePort concept (see Port.h)
    • It will take a policy class that handles code changes depending on the OS
    • Test your implementation in SocketTest.cpp
    • Use SSLSocket for guidance
    • For Windows: create a RAII class for initialization and cleanup of Winsock. You might want to make this a singleton
  • Implement Exception class(es)
    • I crudely throw integers just to show which function gets the error code you'd want to look at, this isn't that helpful for displaying error messages or catching exceptions
    • Implement an exception class or exception hierarchy
  • Implement the HTTP request and response frames
    • User should be able to choose HTTP version (parameter to compose method or pass to constructors?)
  • Implement the HTTP client
    • Should take ownership of a Port -> reads and writes requests on this port
    • Be able to send request Http Frames and receive response frames
    • User should not have to do any string formatting (except for any content encoding)
  • Implement HTTP content and transfer encodings:
    • Need to support chunked transfer encoding, and url type data
    • Design Ideas:
      • HTTP writer decorators:
        • A class that would subtype some kind of HTTP interface and be composed of an implementation of that interface
        • A high level set_content or write (member or overrided from interface) function would then set the content of the underlying HTTP writer following the specified format and all relevant headers
      • Transaction Scripts:
        • Non-member helper functions set the writer's content and relevant headers following the specified format
      • Encoding strategy:
        • A callable object or implementation of some kind of strategy interface is passed to a write function
    • See ChunkedTest and QueryTest
  • Implement the HTTP server
    • Should take ownership of a Port -> reads and writes requests on this port
    • Root directory (where all the paths in the request are relative to) should be customizable
    • Handles GET and POST requests
    • Supports URL encoding
    • See ChunkedTest and QueryTest
  • Implement the command line argument marshallers
    • Recognize command line arguments
    • The arguments can be specified in any order
  • Feel free to add your own tests and expand upon the existing ones
  • Tie it all together:
    • Create a website with a C++ backend:
      • More than 1 page
      • Something that accepts user input
      • Bonus:
        • CRUD (create, read, update, delete) operations on some "database"
        • Fetching images (or some other non-plaintext data) from the site
  • Use Doxygen to generate documentation for your project
    • Comment your code in a Doxygen recognized format
  • (Optional) Implement FdSet
    • Not really necessary because HTTP is a stateless connection protocol
    • But if you needed to keep track of active connections this would be vital
  • (Optional) Add IPv6 Support to Address
    • Make Address a template accepting a policy which controls:
      • type of the addrData member. (sockaddr_in vs sockaddr_in6)
      • family AF_INET vs AF_INET6
      • the is_ip member function
    • OR use runtime polymorphism and a factory function so that the correct Address class can be chosen at runtime based on the IP address passed to the factory function
  • (Optional) Add JSON support
    • JSON object and array types which can be serialized from and into JSON text
    • Support for sending and receiving JSON data in the HTTP client and server

Possibly Useful

Other Notes

You can use preprocessor conditions around the definition of WIN32 to conditionally enable code depending on if using Windows or Unix. WIN32 is a macro defined by the compiler when compiling on Windows (even Win64). In the CMake, I also define WINDOWS when the platform is Windows and UNIX when it is not.

#ifdef WIN32
// windows only code

// ifdef -> "if defined"
// if the macro WIN32 is defined, include this code
#else
// non-windows code
#endif

Feel free to change the implementation or design of anything. Although there is a comment saying not to, you may change the Port interface so long as you make sure to have tests.

The current project structure is as follows:

<Project Directory>
|   CMakeLists.txt
|---HttpProject
|   |   CMakeLists.txt
|   |---include
|   |---src
|   |---test
|   |   |   CMakeLists.txt
|   |   |---data
|   |
|---external
|---out
|

To connect to a HTTP server running on your machine, you can simply type localhost or 127.0.0.1 into the address bar of your browser. If you use a port other than 80 for HTTP and 443 for HTTPS, then the port must be specified by a colon and port number following the address. Ex: localhost:3000

Other devices on the same network as you can connect via your LAN address which is listed when you run the command ipconfig on Windows or ifconfig on Unix.


"Most good programmers do programming not because they expect to get paid or get adulation by the public, but because it is fun to program."

- Linus Torvalds