Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C++

A minimal http web server using boost/asio

Rate me:
Please Sign up or sign in to vote.
4.67/5 (11 votes)
6 Jul 2015CPOL5 min read 34.6K   1.3K   19   3
A sample implementation of a minimal http web server using boost::asio in C++

This week I was wanted to have a go at implementing a web service with boost/asio - I've managed to get the boost web server part up and running, and I wanted to let you have a look before I get too far down the track. If you're planning on building the code that I'm going to show you, I'd recommend that first you pull down boost from http://www.boost.org/

Structure of the Sample

The sample code I'm going to present is split up into 4 sections, the first, is the main section, which is responsible for starting the program, bootstrapping the essential parts of boost/asio and listening for each session, the second section that I'm going to present will include the code for handling the broad structure of each session, and finally I'll show you the details of how sessions are handled, and the responses. In order to get the c++ code to compile, certain declarations will have to be in order, so the order that I'll present the sections for you to understand them in will be different to those that you have to present to the compiler for it to understand. Never fear though, I'm going to attach some source code for reference.

boost.asio

boost.asio subscribes to an event driven programming model called the proactor pattern, which broadly speaking is an asynchronous programming model. The idea of the proactor pattern is that you initiate an operation someone else (you don't care who) performs the operation, and in time you will be notified by a callback that indicates that the operation has completed. At that point you can initiate another operation, and pend another callback that will be notified when that operation has completed.

Modern c++ has a mechanism for tersely expressing callbacks, called a lambda function. If you've been programming for a while you've probably seen lambdas in other languages, but they are new for c++. While it's not a new concept, lambdas in c++ have a few idiosyncrasies that allow you to specify which variables to capture, and when you do so, whether it is by value or by reference. This is important in our situation because since we're going to be handling callbacks asynchronously we might have to reference values that were on the stack when the lambda was created, but are no longer in scope. A value of note in the code below is the shared pointer 'sesh'. We want a reference to the object created, but we pass the shared pointer to the function by value. Because the object is a shared pointer, we know that when the last reference goes out of scope the object will be destroyed. In this sample I'll be passing the important variables via sesh and when the last copy of this pointer goes out of scope, the destructor will clean up the connection for me.

Entry point function and session handling

the specific details of the code below are that the entry point 'main' will create an io service, spin up an endpoint and an acceptor, listen for a connection and asyncronously accept it. When the connection is accepted, the accepting lambda will queue up another accept ready for the next connection, and interact with the browser.

C++
void accept_and_run(ip::tcp::acceptor& acceptor, io_service& io_service)
{
   std::shared_ptr<session> sesh = std::make_shared<session>(io_service);
   acceptor.async_accept(sesh->socket,
   [sesh, &acceptor, &io_service](const error_code& accept_error)
   {
      accept_and_run(acceptor, io_service);
      if(!accept_error)
      {
         session::interact(sesh);
      }
   });
}

int main(int argc, const char * argv[])
{
   io_service io_service;
   ip::tcp::endpoint endpoint{ip::tcp::v4(), 8080};
   ip::tcp::acceptor acceptor{io_service, endpoint};
   
   acceptor.listen();
   accept_and_run(acceptor, io_service);
   
   io_service.run();
   return 0;
}

The io_service, endpoint, acceptor and socket are boost data types, whereas session is a class for this sample that keeps track of the state of the http conversation, which I'll describe below.

HTTP Session

I've tried to isolate most of the asynchronous behaviour of the session into it's own class. In this class we're reading each line individually, and then depending on the order of the lines, how long those lines are and what we're expecting on them. Since we're expecting Http traffic, we basically want one line containing the request line, followed by several headers, and finally a content body if a content length was specified. The code below reads each line individually and passes the lines through to the http_header class, which I'll show you in a moment. Finally if there is content, that's read too.

C++
class session
{
   asio::streambuf buff;
   http_headers headers;
   
   static void read_body(std::shared_ptr<session> pThis)
   {
      int nbuffer = 1000;
      std::shared_ptr<std::vector<char>> bufptr = 
         std::make_shared<std::vector<char>>(nbuffer);
      asio::async_read(pThis->socket, boost::asio::buffer(*bufptr, nbuffer),
      [pThis](const error_code& e, std::size_t s)
      {
      });
   }
   
   static void read_next_line(std::shared_ptr<session> pThis)
   {
      asio::async_read_until(pThis->socket, pThis->buff, '\r',
      [pThis](const error_code& e, std::size_t s)
      {
         std::string line, ignore;
         std::istream stream {&pThis->buff};
         std::getline(stream, line, '\r');
         std::getline(stream, ignore, '\n');
         pThis->headers.on_read_header(line);
         
         if(line.length() == 0)
         {
            if(pThis->headers.content_length() == 0)
            {
               std::shared_ptr<std::string> str =
                  std::make_shared<std::string>(pThis->headers.get_response());
               asio::async_write(
                  pThis->socket,
                  boost::asio::buffer(str->c_str(), str->length()),
                  [pThis, str](const error_code& e, std::size_t s)
                  {
                     std::cout << "done" << std::endl;
                  });
            }
            else
            {
               pThis->read_body(pThis);
            }
         }
         else
         {
            pThis->read_next_line(pThis);
         }
      });
   }
   
   static void read_first_line(std::shared_ptr<session> pThis)
   {
      asio::async_read_until(pThis->socket, pThis->buff, '\r',
      [pThis](const error_code& e, std::size_t s)
      {
         std::string line, ignore;
         std::istream stream {&pThis->buff};
         std::getline(stream, line, '\r');
         std::getline(stream, ignore, '\n');
         pThis->headers.on_read_request_line(line);
         pThis->read_next_line(pThis);
      });
   }
   
public:

   ip::tcp::socket socket;
   
   session(io_service& io_service)
      :socket(io_service)
   {
   }
   
   static void interact(std::shared_ptr<session> pThis)
   {
      read_first_line(pThis);
   }
};

You'll note some other weird things about this class, firstly all of the methods are static, and I'm passing the this parameter in manually. I'm doing this in order to preserve the reference counts of the sessions. I know I don't want to lose this because when the object goes out of scope, so will the socket, and if that goes away it will be bad for business in our web server. Secondly since all of the methods are asynchronous, each method also has a lambda involved, which is the function that will be executed when the operation completes. Listing the code this way makes the method and it's consequence more readable without destroying it's ability to execute later.

Serving up pages

Ok, here's the final bit of the server - serving up the pages themselves. I've included a minimal service this time, but gone to the effort of serializing each result individually for readability. There's the root, which is basically a hello world page. An icon 'favicon.ico' which is a green and red square, and a 404 status page. You'll note that this section isn't asynchronous, and it doesn't have to be, basically in this case all I'm doing is writing to a string, which the previous section will send away.

C++
unsigned char * get_icon(int* pOutSize);

class session;

class http_headers
{
   std::string method;
   std::string url;
   std::string version;
   
   std::map<std::string, std::string> headers;

public:
   
   std::string get_response()
   {
      std::stringstream ssOut;
      if(url == "/favicon.ico")
      {
         int nSize = 0;
         unsigned char* data = get_icon(&nSize);
         
         ssOut << "HTTP/1.1 200 OK" << std::endl;
         ssOut << "content-type: image/vnd.microsoft.icon" << std::endl;
         ssOut << "content-length: " << nSize << std::endl;
         ssOut << std::endl;
         
         ssOut.write((char*)data, nSize);
      }
      else if(url == "/")
      {
         std::string sHTML =
         "<html><body><h1>Hello World</h1><p>This is a web server in c++</p></body></html>";
         ssOut << "HTTP/1.1 200 OK" << std::endl;
         ssOut << "content-type: text/html" << std::endl;
         ssOut << "content-length: " << sHTML.length() << std::endl;
         ssOut << std::endl;
         ssOut << sHTML;
      }
      else
      {
         std::string sHTML =
         "<html><body><h1>404 Not Found</h1><p>There's nothing here.</p></body></html>";
         ssOut << "HTTP/1.1 404 Not Found" << std::endl;
         ssOut << "content-type: text/html" << std::endl;
         ssOut << "content-length: " << sHTML.length() << std::endl;
         ssOut << std::endl;
         ssOut << sHTML;
      }
      return ssOut.str();
   }
   
   int content_length()
   {
      auto request = headers.find("content-length");
      if(request != headers.end())
      {
         std::stringstream ssLength(request->second);
         int content_length;
         ssLength >> content_length;
         return content_length;
      }
      return 0;
   }
   
   void on_read_header(std::string line)
   {
      std::stringstream ssHeader(line);
      std::string headerName;
      std::getline(ssHeader, headerName, ':');
      
      std::string value;
      std::getline(ssHeader, value);
      headers[headerName] = value;
   }
   
   void on_read_request_line(std::string line)
   {
      std::stringstream ssRequestLine(line);
      ssRequestLine >> method;
      ssRequestLine >> url;
      ssRequestLine >> version;
      
      std::cout << "request for resource: " << url << std::endl;
   }
};

Summing Up

Ok, well, that's all there is to it. I hadn't used boost.asio before today, but I found it pretty easy to work with. The documentation is a little bit cryptic, but I'm sure most of these things become more clear the more you use them. The thing that I like about this software is how easily you can get the asynchronous stuff to work without handling threads and things and all of that plumbing behind.

Thanks for reading, and if you have any comments I'd love to hear from you.

This article was originally posted here: https://dabblingseriously.wordpress.com/2015/07/06/a-minimal-http-web-server-using-boostasio/

P.S: The icon data

here's the icon code in case you're really interested - it's just a green box with a red border.

C++
unsigned char icon_data[] = {
      //reserved
      0x00,0x00,
      //icon type (1 = icon)
      0x01,0x00,
      //number of images (1)
      0x01,0x00,
      //width, height (16x16)
      0x10,0x10,
      //size of colour palette
      0x00,
      //reserved
      0x00,
      //colour planes (1)
      0x01,0x00,
      //bits per pixel (32)
      0x20,0x00,
      
      //size of data in bytes
      0x28,0x04,0x00,0x00,
      
      //offset of bitmap data
      0x16,0x00,0x00,0x00,
      
      //BEGIN BITMAPINFOHEADER
      //bcsize
      0x28,0x00,0x00,0x00, //biSize
      0x10,0x00,0x00,0x00, //biWidth
      0x20,0x00,0x00,0x00, //biHeight (with both AND and XOR mask? wtf?)
      
      0x01,0x00, //biPlanes
      0x20,0x00, //biBitCount (32)
      
      0x00,0x00,0x00,0x00, //biCompression
      0x00,0x00,0x00,0x00, //biSizeImage
      0x00,0x00,0x00,0x00, //biXPelsPerMeter
      0x00,0x00,0x00,0x00, //biYPelsPerMeter
      0x00,0x00,0x00,0x00, //biClrUsed
      0x00,0x00,0x00,0x00, //biClrImportant
      //END BITMAPINFOHEADER
      
      //BITMAP DATA (4 bytes per pixel)
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF ,0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
      0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
      0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
      0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
      0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
      0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
      0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
      
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
      0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF
};

unsigned char* get_icon(int* pOut)
{
      *pOut = sizeof(icon_data);
      return icon_data;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
New Zealand New Zealand
I've spent time programming for the security industry, video games, and telephony. I live in New Zealand, and have a Bachelor of Computing and Mathematical Sciences specializing in Computer Technology, from the University of Waikato in Hamilton, New Zealand.

Comments and Discussions

 
Questionto keep alive the server Pin
OmarMedina17-Feb-23 13:15
OmarMedina17-Feb-23 13:15 
Questionthank you Pin
Member 111312012-May-18 0:44
Member 111312012-May-18 0:44 
QuestionBrilliant!!! Pin
freddie9837019-Nov-15 10:32
freddie9837019-Nov-15 10:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.