Changes between Version 12 and Version 13 of soc/2007/cgi


Ignore:
Timestamp:
Nov 12, 2007, 12:26:59 PM (15 years ago)
Author:
Darren Garvey
Comment:

Changing the examples to make them more relevant.

Legend:

Unmodified
Added
Removed
Modified
  • soc/2007/cgi

    v12 v13  
    44 * '''Implement the Controller part of the [http://en.wikipedia.org/wiki/Model-view-controller model-view-controller] idiom''' for any CGI-compatible protocols.
    55 * Simple access to standard CGI environment variables and input data.
    6  * Clean access to request meta-data (ie. 'environment vars') and input data using alternative protocols, such as FastCGI.
    7  * Asynchronous read/write support.
    8  * A clean way to write to clients without knowledge of the underlying protocol in use.
     6 * Easy access to request meta-data (ie. 'environment vars') and input data using alternative protocols, such as FastCGI.
     7 * Asynchronous read/write support to encourage multi-threaded *CGI daemons.
     8 * A uniform way to write to clients without knowledge of the underlying protocol in use.
    99 * Minimal process initialisation time: most of the time, clients will be handled by multiple process images, so each time one is started up, there can't be a noticeable load-time before handling the first request.
    1010 * Basic session support: by default Boost.Interprocess will be used for saving the memory, although the SessionAdapter concept should allow for other saving methods to be added later (such as files, databases or in-process memory).
    1111 * Internationalisation support should be considered, although to what extent this can be tackled I'm not yet sure.
     12 * An interface that encourages separation of client-supplied variables (eg. GET vs. POST), but does not enforce it.
    1213
    1314== Usage ==
    1415
     16Note: these examples are not yet compilable, but are expected to be reasonably soon.
     17
    1518First, a standard CGI example:
    1619{{{
    1720#!cpp
     21#include <boost/cgi.hpp>
     22
     23using namespace boost::cgi;
     24
    1825int main()
    1926{
    20   cgi::request req;   // set up the request
    21   cgi::reply rep; // see Design Ideas for more about this
    22 
    23   rep<< "Hello, " << req.param<cgi::GET>("user_name") << "!";
    24   rep.send(req);
     27  request req;   // set up the request
     28  response resp; // make an empty response
     29
     30  std::string name(req.GET("user_name"));
     31  resp<< "Hello there, "
     32      << (name.empty()? "stranger" : name)
     33      << "!";
     34  resp.send(req.client());
    2535 
    2636  return 0;
    2737}
    2838}}}
    29 Using an alternate protocol (FastCGI in this case) will alter the above like so:
    30 {{{
    31 #!cpp
    32 int sub_main(cgi::request req)
    33 {
    34   cgi::reply rep; // see Design Notes for more about this
    35 
    36   rep<< "Hello, " << req.param<cgi::POST>("user_name") << "!";
    37   rep.send(req);
     39
     40Protocols such as SCGI and FastCGI allow a single process to handle multiple requests. The simplest way to turn the above into an SCGI ''daemon'' is to use the synchronous API provided by the library. For example:
     41
     42{{{
     43#!cpp
     44#include <boost/cgi/scgi.hpp> // Include only SCGI functionality
     45
     46using namespace boost::scgi;
     47
     48int main()
     49{
     50  service s; // This manages the request queue, amongst other things. More about this later.
     51  request req(s); // Constructed with the service from above.
     52  response resp;
     53  acceptor a(s); // This is responsible for 'accepting' requests from the server.
     54
     55  // Initialise anything else you want to keep between
     56 
     57  while(a.accept(req) == 0) // no error here
     58  {
     59    std::string name(req.GET("user_name"));
     60    resp<< "Hello there, "
     61        << (name.empty()? "stranger" : name)
     62        << "!";
     63    resp.send(req.client());
     64    req.close(http::ok);
     65    resp.clear();
     66  }
    3867
    3968  return 0;
    4069}
     70}}}
     71
     72Using a multiplexing protocol (SCGI or FastCGI) in a multi-threaded environment can be much more flexible than the example above allows. In many (if not most) cases you will be able to increase the throughput of your daemon by handling more than one request at a time (ie. within each process). To get the most out of your process you should use the asynchronous functionality of the library - kindly provided by Boost.Asio. Making a fully asynchronous program requires a different approach to synchronous ones and can be quite mind-bending until you are used to it.
     73
     74That said, you can still benefit from the asynchronous nature of the library without complicating your program by distinguishing between '''accepting''' requests and '''using''' them. For the example below, we can first create a Server, the purpose of which is to accept requests and pre-load the data from the clients (this is likely sub-optimal, but keeps the demonstration more to the point). The class shown is also not generic - it's only useable with FastCGI programs - but this is just to keep it concise. A generic version is provided (or should be soon) in the distribution.
     75
     76{{{
     77#!cpp
     78#include <boost/cgi/fcgi.hpp>
     79#include <boost/function.hpp> // Really cool library!
     80
     81using boost::fcgi;
     82
     83// Define the Server class
     84class Server
     85{
     86public:
     87  Server()
     88    : service_()
     89    , acceptor_(service_)
     90  {
     91  }
     92
     93  void run()
     94  {
     95    start_accept();
     96  }
     97
     98  void start_accept()
     99  {
     100    fcgi::request::pointer new_request(fcgi::request::create(service_));
     101    acceptor_.async_accept(new_request, boost::bind(&Server::handle_accept, new_request));
     102  }
     103
     104  void handle_accept(fcgi::request::pointer req, boost::system::error_code& ec)
     105  {
     106    if (!ec) { // no errors, so load the request data
     107      req->async_load(fcgi::parse_all, boost::bind(&Server::handle_load, req));
     108    }
     109    start_accept(); // start another request
     110  }
     111
     112  void handle_load(fcgi::request::pointer req, boost::system::error_code& ec)
     113  {
     114    if (!ec) { // no errors
     115      handler_(*req); // Call the user-supplied handler
     116    } else {
     117      req.abort(http::bad_request);
     118    }
     119  }
     120private:
     121  fcgi::service  service_;
     122  fcgi::acceptor acceptor_;
     123  boost::function<int (fcgi::request)> handler_; // The user-supplied handler function.   
     124};
     125}}}
     126
     127You'll notice that this request runs in an infinite loop and will possibly accept requests faster than you can handle them. We'll deal with that later. For now, we can use the above example class.
     128
     129{{{
     130#!cpp
     131#include <boost/cgi/fcgi.hpp>
     132#include "Server.hpp" // The above example
     133
     134using namespace boost::fcgi;
     135
     136// This is where you deal with the request
     137int sub_main(request& req)
     138{
     139  response resp;
     140
     141  resp<< "Hello, "
     142      << req.form("user_name") // this accepts either GET or POST variables.
     143      << "!";
     144  resp.send(req.client());
     145
     146  return req.close(http::ok); // we have to explicitly close the requests now.
     147}
    41148
    42149int main()
    43150{
    44   cgi::fcgi_service service(&sub_main);
    45   service.run();
     151  Server server(&sub_main);
     152  server.run();
    46153 
    47154  return 0;
     
    49156}}}
    50157
     158And that's it!
     159
     160(below notes are outdated)
    51161
    52162== Design Notes ==
     
    58168See [wiki:soc/2007/cgi/Concepts Concepts] for more.
    59169
    60 '''Separation of ```cgi::request``` and ```cgi::reply```''':
    61 
    62 This separation is only a recent change. The main reasoning is that equivalent meta-data exists for both the request and the reply (ie. same identifier, different value). Using getters/setters is one idea, although in a large program, there could be a situation where you set a response header and then need to check it later. If everything was done with the ```request``` object then there'd be no clean way to achieve this.
     170'''Separation of ```cgi::request``` and ```cgi::response```''':
     171
     172This separation is only a recent change. The main reasoning is that equivalent meta-data exists for both the request and the response (ie. same identifier, different value). Using getters/setters is one idea, although in a large program, there could be a situation where you set a response header and then need to check it later. If everything was done with the ```request``` object then there'd be no clean way to achieve this.
    63173
    64174Having two objects has other advantages:
    65175 * Code is clearer, without being too verbose
    66  * Response caching is easier to implement; code can just cache a ```cgi::reply``` since it holds no data relevant to the specific request (note: response caching isn't really part of this project, although ```cgi::session``` will probably provide basic facilities)
     176 * Response caching is easier to implement; code can just cache a ```cgi::response``` since it holds no data relevant to the specific request (note: response caching isn't really part of this project, although ```cgi::session``` will probably provide basic facilities)
    67177
    68178'''Having the !CommonGatewayService control threading'''