dumb_cgi 0.6.0

An adequate CGI library for server-side CGI programs
Documentation
# dumb-cgi
An adequate, essentially dependencyless CGI library in Rust.

The purpose of this library is to allow server-side CGI programs to easily
parse requests (in particular "multipart/formdata" requests) and generate
responses without pulling in a bunch of full-featured crates. `dumb_cgi`
does not attempt (or at least won't start off attempting) to be
resource-efficient; its chief goals are simplicity and ease of use. Some
trade-offs it makes are:

  * It does a lot of copying and makes a lot of small allocations. This
    makes it easier to use (and write), but it carries a performance and
    resource usage penalty.
  
  * It forces lossy conversion of all environment variable and header names
    and values to UTF-8 (so they can be stored as `String`s). The spec
    should guarantee that header names are valid UTF-8, but if you need
    any of the other three (header values, environment variable names, or
    environment variable values) to be something that can't correctly,
    meaningfully be converted to UTF-8, then this crate is too dumb for
    your use case.
        
  * It doesn't make any effort to try to parse improperly-formed or
    almost-properly-formed requests, and it might even mishandle some
    uncommon corner cases in order to simplify implementation. For instance,
    the headers and blank lines in a multipart body part _must_ end with
    "\r\n" (which is strictly conformant to the spec), even though plenty
    of other HTTP implementations will still recognize plain ol' "\n"
    line endings. This simplifies the implementation.

  * Its intended use case is server-side CGI programs only. It supports
    _reading_ requests, but not making them, and _writing_ responses, but
    not reading them,  and only supports the parts of the HTTP-verse directly
    related to reading, parsing, and responding to CGI requests.

## Usage

To illustrate both reading and responding to requests, below is a sample
program that reads a request, logs the information about it, and then
returns a cursory "success" response. If any of the `.unwrap()`s or
`.expect()s` panic, the web server will just return a generic 500 response.

For logging, we will use macros from the
[`log`](https://crates.io/crates/log) logging facade (the sole required
dependency of `dumb-cgi`) and the
[`simplelog`](https://crates.io/crates/simplelog) logging crate (which
becomes a dependency if you compile `dumb-cgi` with the `log` feature).

```rust
use dumb_cgi::{Request, EmptyResponse, Query, Body};
use simplelog::{WriteLogger, LevelFilter, Config};

fn main() {
    // Open the log file.
    WriteLogger::init(
        LevelFilter::max(),
        Config::default(),
        std::fs::OpenOptions::new()
            .create(true)
            .append(true)
            .open("dumb_example.log")
            .unwrap()
    ).unwrap();
    
    // Gather info about the CGI request, including reading any body
    // (if present) from stdin.
    let request = Request::new().unwrap();
    
    // Log method request origin information.
    log::trace!(
        "Rec'd {} request from {} on port {}:",
        request.var("METHOD").unwrap_or("none"),
        request.var("REMOTE_ADDR").unwrap_or("nowhere"),
        request.var("SERVER_PORT").unwrap_or("-1")
    );
    
    // Log all the headers.
    //
    // The `Request::header()` method will return individual header values
    // (if present); the `Request::headers()` method will return an
    // iterator over all `(name, value)` header pairs.
    log::trace!("    Request headers:");
    for (name, value) in request.headers() {
        log::trace!("        {}: {}", name, value);
    }
    
    // If there's a query string, log info about it.
    //
    // The `Request::query()` method returns a reference to a
    // `dumb_cgi::Body` enum.
    match request.query() {
        Query::None => {
            log::trace!("    No query string.");
        },
        Query::Some(form) => {
            // If this variant is returned, then the query string was
            // parseable as `&`-separated `name=value` pairs, and the
            // contained `form` value is a `HashMap<String, String>`.
            log::trace!("    Form data from query string:");
            for (name, value) in form.iter() {
                log::trace!("        {}={}", name, value);
            }
        },
        Query::Err(e) => {
            // If this variant is returned, there was an error attempting
            // to parse the `QUERY_STRING` environment variable as a series
            // of `&`-separated `name=value` pairs.
            
            // `dumb_cgi::Error`s have a public `.details` member that is a
            // string with information about the error.
            log::trace!("    Error parsing query string: {}", &e.details);
            // You can still access the value of `QUERY_STRING` directly:
            log::trace!(
                "    Raw QUERY_STRING value: {}",
                request.var("QUERY_STRING").unwrap()
            );
        },
    }
    
    // If there's a body, log info about it.
    //
    // The `Request::body()` method returns a reference to a
    // `dumb_cgi::Body` enum.
    match request.body() {
        Body::None => {
            log::trace!("    No body.");
        },
        Body::Some(bytes) => {
            // Most valid bodies of properly-formed requests will return
            // this variant; `bytes` will be an `&[u8]`.
            log::trace!("    {} bytes of body.", bytes.len());
        },
        Body::Multipart(parts) => {
            // If the request has a properly-formed `Content-type` header
            // indicating `multipart/form-data`, and the body of the request
            // is also properly formed, this variant will be returned.
            //
            // The contained `parts` is a vector of `dumb_cgi::MultipartPart`
            // structs, one per part.
            log::trace!("    Multipart body with {} part(s).", parts.len());
        },
        Body::Err(e) => {
            // This variant will be returned if there is an error reading
            // the body.
            log::trace!("    Error reading body: {}", &e.details);
        },
    }
    
    // And we'll just put a blank line here in the log to separate
    // info about separate requests.
    log::trace!("");
    
    // Now that we've read and logged all the information we want from our
    // request, it's time to generate and send a response.
    //
    // Responses can be created with the builder pattern, starting with
    // an `EmptyResponse` (which has no body). In order to send a response
    // with a body, we need to call `EmptyResponse::with_content_type()`,
    // which turns our `EmptyResponse` into a `FullResponse`, which takes
    // a body.
    
    // Takes the HTTP response code.
    let response = EmptyResponse::new(200)
        // Headers can be added any time.
        .with_header("Cache-Control", "no-store")
        // Now we can add a body.
        .with_content_type("text/plain")
        // A body can be added this way; `FullResponse` also implements
        // `std::io::Write` for writing to the response body.
        .with_body("Success. Your request has been logged.")
        // Again, headers can be added any time.
        .with_header("Request-Status", "logged");
    
    // `FullResponse::respond()` consumes the response value and writes the
    // response to stdout.
    response.respond().unwrap();
}
```

Obviously, more details are available in the documentation.

## To Do

This may actually be more or less the final form of this crate (aside from bug
fixes or usability tweaks). I have ideas for improvements, but they all
drag this project dangerously into "less dumb" territory. Then again, I'm
not particularly fond of the idea of maintaining both `dumb_cgi` and
`not_quite_as_dumb_cgi`, so this may very well just feature creep and grow
warts until I'm disgusted with it.

## Notes

  * v 0.3.0: Removed dependence on
    [`lua-patterns`]https://crates.io/crates/lua-patterns,
    because even though I like the idea of it, and it has worked well for
    me in other projects, it kept panicing. `dumb_cgi` now depends only
    on the [`log`]https://crates.io/crates/log logging facade (and
    [`simplelog`]https://crates.io/crates/simplelog if you actually
    want to do some logging and enable the `log` feature).
    
  * v 0.4.0: Added explicit query string parsing.
  
  * v 0.5.0: Added response types and functionality.
    
  * v 0.6.0: Changed `Error` type and error handling.