PageRenderTime 84ms CodeModel.GetById 40ms app.highlight 8ms RepoModel.GetById 32ms app.codeStats 1ms

C++ | 250 lines | 49 code | 6 blank | 195 comment | 5 complexity | 0b080ffc27e85625777da5d2430708ff MD5 | raw file
  1//                     -- cgi_quickstart.cpp --
  3//            Copyright (c) Darren Garvey 2009.
  4// Distributed under the Boost Software License, Version 1.0.
  5//    (See accompanying file LICENSE_1_0.txt or copy at
 13A catch-all header is available which includes all of the headers you should
 14need for CGI.
 16For the sake of clarity we alias the `boost::cgi` namespace rather than
 17dumping all of the library names with a `using namespace`. This way, you can
 18see what comes from the library.
 20#include <boost/cgi/cgi.hpp>
 21namespace cgi = boost::cgi;
 24The first thing to do is write a handler function which takes a request and a
 25response and does all request-specific work. Later, we will look at writing
 26the code that calls this function.
 28int handle_request(cgi::request& req, cgi::response& resp)
 31In our request handler, we will assume that the request has been fully-parsed
 32and we can access all of the request data. The request data is available using
 33public members of a `cgi::request`. These member variables are instances of
 34the [classref boost::cgi::common::data_map_proxy data_map_proxy], which has a
 35`std::map<>`-like interface along with some additional helper functions to
 36facilitate common CGI tasks, such as lexical conversion to different types.
 37[footnote The data is stored internally in a single `fusion::vector<>` which
 38is not currently publicly accessible.]
 40A CGI request has several types of variables available. These are listed in
 41the table below, assuming that `req` is an instance of `cgi::request`:
 44  [[Source] [Variable] [Description]]
 45  [
 46    [Environment] [`req.env`] [The environment of a CGI request contains most
 47    of the information you will need to handle a request. There is a basic set of
 48    common environment variables that you can expect to be set by most HTTP
 49    servers around. A list of them is available on the __TODO__ (link) variables
 50    page.]
 51  ]
 52  [
 53    [GET] [`req.get`] [The variables passed in the query string of an HTTP GET
 54    request.]
 55  ]
 56  [
 57    [POST] [``] [The HTTP POST data that is sent in an HTTP request's
 58    body. For file uploads, the file's name is the value stored in the map. You
 59    should use `req.uploads` for more information on file uploads.]
 60  ]
 61  [
 62    [Cookies] [`req.cookies`] [Cookies are sent in the HTTP_COOKIE environment
 63    variable. These can store limited amounts session information on the client's
 64    machine, such as database session ids or tracking information.]
 65  ]
 66  [
 67    [File Uploads] [`req.uploads`] [File uploads, sent in an HTTP POST where
 68    the body is MIME-encoded as multipart/form-data. Uploaded files are written
 69    onto the server's file system and meta-data related to the file is stored in
 70    a [classref boost::cgi::common::form_part form_part]. The value of an upload
 71    variable is the `form_part` for the upload and all `form_part`s are implicitly
 72    convertible to a string, which corresponds to the original filename.]
 73  ]
 74  [
 75    [Form] [`req.form`] [The form variables are either the GET variables or
 76    the POST variables, depending on the request method of the request.]
 77  ]
 80Let's assume you now want to check if the user has a cookie, "user_name",
 81set. We can check if a user has a cookie set like this:
 83  if (req.cookies.count("user_name"))
 84  {
 86First, we need to be able to clear the cookie we are setting. We will reset
 87the cookie if the user navigates to `"/path/to/script?reset=1"`.
 89The `reset` variable in the query string is a GET variable. The request data
 90is accessed through a proxy class which works just like a `std::map<>` with
 91some extra features.
 93One of them is `pick`, which looks up a key in the map and returns the value
 94if it is found. Otherwise it returns a default value, which is the second
 97The default value can be any type that supports
 98[@ Boost.Lexical_cast]. If the key isn't
 99found in the map, or the value cannot be cast to the type of the default
100value, the default is returned.
102    if (req.get.pick<std::string>("reset", "") == "1")
103    {
104      resp<< cgi::cookie("user_name") /*<
105Set a cookie with no value to delete it.
107          << cgi::redirect(req, req.script_name()) /*<
108The `cgi::redirect` free function returns a `"Location"` header that will
109redirect the user to the specified URL. This URL can be a relative or absolute
110but an absolute URL is always returned. To perform an internal redirect, use
111`cgi::location` instead.
113          << cgi::content_type("text/plain");
114    }
115    else
116    {
117      std::string user_name( req.cookies["user_name"] );
119Looking up a request cookie in `req.cookies` really returns a `cgi::cookie`.
120The line above works though because a `cgi::cookie` is implicitly convertible
121to a `std::string`.
123The lookup is case-insensitive, so "USER_NAME" and "User_Name" would be
124equivalent lookup keys.
126If the cookie is set, we'll be polite and say hello before quitting.
128      if (!user_name.empty())
129      {
130        resp<< cgi::content_type("text/html")
131            << "<p>Hello there, " << req.cookies["user_name"]
132            << ". How are you?</p>"
133            << "<a href=\"" << req.script_name() << "?reset=1\">Reset</a>";
134      }
135    }
137That's all we want to say for now, so we can return.
139If you are familiar with CGI programming, you will notice the lack of any
140HTTP headers in the response. A `cgi::response` handles headers separately
141to the body. You can set headers at any point and when you send the response
142the headers will all be sent first.
144If you don't explicitly set any response headers, a default header
145`"Content-type: text/plain"` is sent, followed by the usual HTTP end-of-line
146`"\r\n"` and a blank line which indicates the end of the headers and the
147start of response body.
149  } else
151If the cookie isn't set, we will check if the user has posted a __GET__/
152__POST__ form with their name.
154  if (req.form.count("user_name"))
155  {
156    std::string user_name (req.form["user_name"]);
158If they have told us their name, we should set a cookie so we remember it next
159time. Then we can say hello and exit.
161There are two ways to set a cookie: either directly using
162`req.set_cookie("user_name", user_name)` or the method shown. You can also
163send an expiry date and a path for the cookie.[footnote 
164See [@ RFC822] for more.
166Note that if you set a cookie with no value, the cookie will be deleted.
168Again, the request object isn't buffered, so we are going to keep using the
169`response` in case something breaks and we end up not wanting to set the
170cookie. The cookie we set below will expire when the client closes their
173This time, we shall send a Date header. If we do this (ie. send a header
174ourselves), we must also set the Content-type header, like below.
176    resp<< cgi::cookie("user_name", user_name)
177        << cgi::header("Date", "Tue, 15 Nov 1994 08:12:31 GMT")
178        << cgi::content_type("text/html")
179        << "Hello there, " << user_name << ". You're new around here."
180        << "user_name.length() = " << user_name.length() ;
181  }
182  else
183  {
185Now, if we have no idea who they are, we'll send a form asking them for their
186name. As the default `"Content-type"` header is `"text/plain"`, we'll change
187this to `"text/html"` so the user's browser will display the HTML form. You
188can do this using
189    `set_header(req,  "Content-type", "text/html")`
191    `resp<< header("Content-type", "text/html")`.
192Since writing with raw strings is error-prone, the shortcut below is available.
194  resp<< cgi::content_type("text/html")
195      << "Hello there. What's your name?" "<p />"
196         "<form method='POST'>"
197         "<input type='text' name='user_name' />"
198         "<input type='submit' />";
199  }
201A CGI program will handle one request each time it is invoked. Returning a
202non-zero status to the OS indicates an error handling the request. I don't
203know that HTTP servers treat non-zero exit codes specially.[footnote I may well
204may well be wrong about this.]
206To send the response back to the request, use `cgi::commit`. The third
207`status` argument is optional and defaults to zero. The return value of
208`cgi::commit` is `status`.
210  return cgi::commit(req, resp);
214We now have a request handler in all of it's contrived glory.
216The program's `main` function needs to parse the request, call the request
217handler defined above, and finally send the response.
220int main(int, char**)
222  cgi::request req;
224At this point, the environment variables are accessible. This includes cookie
225and form variables too, which are all parsed by default-constructing a
226`cgi::request` (this is optional).
228  cgi::response resp;
230The `response` class provides a streaming interface for writing replies. You
231can write to the request object directly, but for now we're going to just
232use the `response`, which works well for most situations.
234Writing to a `response` is buffered. If an error occurs, you can simply
235`clear()` the response and send an error message instead. Buffered writing
236may not always suit your use-case (eg. returning large files), but when memory
237is not at a real premium, buffering the response is highly preferable.
239Not only does buffering avoid network latency issues, but being able to cancel
240the response and send another is much cleaner than sending half a response,
241followed by "...Ooops". A `cgi::response` is not tied to a request, so the
242same response can be reused across multiple requests.
244When sending a response that is large relative to the amount of memory
245available to the program, you may want to write unbuffered.
247  return handle_request(req, resp);