fastcgi-sdk 0.0.5

FastCGI SDK for Rust
Documentation
//! A safe Rust binding to the FastCGI SDK. For examples, see the shipped
//! binaries.
//!
//! The current version uses the C library of the FastCGI SDK. The plan is to
//! rewrite it in Rust completely, removing this dependency and improving the
//! interface.
//!
//! The binding has near zero overhead compared to the C library. The
//! differences are:
//!
//!  1. It does one extra llocation per request, for ABI portability reasons.
//!  2. It does unnecessary `strlen` calls, because of unfortunate limitations
//!     in the standard library `CStr` implementation.

extern crate libc;

pub mod ffi;

use ffi::*;

use std::ffi::CStr;
use std::io;
use std::marker::PhantomData;
use std::os::unix::io::AsRawFd;
use std::sync::{ONCE_INIT, Once};

static FCGX_INIT: Once = ONCE_INIT;

/// An HTTP message exchange. This is a pair of a request and a response.
#[derive(Debug)]
pub struct Exchange {
    request: *mut FCGX_Request,
}

impl Exchange {
    /// Create an exchange from a FastCGI request. The exchange will take
    /// ownership of the request, and free it with `free`.
    ///
    /// You probably want to use `accept` instead.
    pub unsafe fn new(request: *mut FCGX_Request) -> Self {
        Exchange{request}
    }

    /// Accept a request from a listener. This can be a TCP listener or a Unix
    /// domain socket listener. Do not pass an already-accepted socket; that
    /// will not work.
    ///
    /// The reason this method exists, rather than requiring the caller to
    /// accept the connection from the listener, is that the C library of the
    /// FastCGI SDK does not expose a function that does not itself accept the
    /// connection.
    pub fn accept<L>(listener: &L) -> io::Result<Self>
        where L: AsRawFd {
        unsafe {
            FCGX_INIT.call_once(|| {
                let status = FCGX_Init();
                assert!(status == 0);
            });

            let request = FCGX_ABICompat_MallocRequest();
            if request.is_null() {
                return Err(io::Error::last_os_error());
            }

            let file_descriptor = listener.as_raw_fd();
            let status = FCGX_InitRequest(request, file_descriptor, 0);
            if status != 0 {
                libc::free(request);
                return Err(io::Error::last_os_error());
            }

            let status = FCGX_Accept_r(request);
            if status == -1 {
                FCGX_Free(request, 0);
                libc::free(request);
                return Err(io::Error::last_os_error());
            }

            Ok(Self::new(request))
        }
    }

    /// Return the FastCGI environment. These include the request method and the
    /// request header, and various information about the server.
    pub fn environment(&self) -> Environment {
        Environment::new(self)
    }

    /// Return the request body for this exchange. You can call this method
    /// multiple times, and all returned writers will write to the same buffer
    /// and socket.
    pub fn request_body(&self) -> RequestBody {
        RequestBody::new(self)
    }

    /// Return the response for this exchange. You can call this method multiple
    /// times, and all returned readers will read from the same buffer and
    /// socket.
    pub fn response(&self) -> Response {
        Response::new(self)
    }
}

impl Drop for Exchange {
    fn drop(&mut self) {
        unsafe {
            FCGX_Finish_r(self.request);
            libc::free(self.request);
        }
    }
}

unsafe impl Send for Exchange {
}

/// A FastCGI environment. This is an iterator that returns strings in the form
/// `name=value`.
#[derive(Clone, Debug)]
pub struct Environment<'a> {
    envp: FCGX_ParamArray,
    phantom: PhantomData<&'a ()>,
}

impl<'a> Environment<'a> {
    fn new(exchange: &Exchange) -> Self {
        unsafe {
            let envp = FCGX_ABICompat_RequestEnvp(exchange.request);
            Environment{envp, phantom: PhantomData}
        }
    }
}

impl<'a> Iterator for Environment<'a> {
    type Item = &'a CStr;

    fn next(&mut self) -> Option<Self::Item> {
        unsafe {
            if (*self.envp).is_null() {
                return None;
            }

            let entry = CStr::from_ptr(*self.envp);
            self.envp = self.envp.offset(1);
            Some(entry)
        }
    }
}

/// The body of the request of an exchange.
#[derive(Debug)]
pub struct RequestBody<'a> {
    stream: *mut FCGX_Stream,
    phantom: PhantomData<&'a ()>,
}

impl<'a> RequestBody<'a> {
    fn new(exchange: &Exchange) -> Self {
        unsafe {
            let stream = FCGX_ABICompat_RequestIn(exchange.request);
            RequestBody{stream, phantom: PhantomData}
        }
    }
}

impl<'a> io::Read for RequestBody<'a> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        unsafe {
            let actual_read = FCGX_GetStr(buf.as_mut_ptr() as *mut i8,
                                          buf.len() as i32, self.stream);
            if FCGX_GetError(self.stream) != 0 {
                return Err(io::Error::last_os_error());
            }
            Ok(actual_read as usize)
        }
    }
}

/// A response for an exchange. This includes both the response status line,
/// headers, and body.
#[derive(Debug)]
pub struct Response<'a> {
    stream: *mut FCGX_Stream,
    phantom: PhantomData<&'a ()>,
}

impl<'a> Response<'a> {
    fn new(exchange: &Exchange) -> Self {
        unsafe {
            let stream = FCGX_ABICompat_RequestOut(exchange.request);
            Response{stream, phantom: PhantomData}
        }
    }
}

impl<'a> io::Write for Response<'a> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        unsafe {
            let actual_written = FCGX_PutStr(buf.as_ptr() as *const i8,
                                             buf.len() as i32, self.stream);
            if actual_written == -1 {
                return Err(io::Error::last_os_error());
            }
            Ok(actual_written as usize)
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        unsafe {
            let status = FCGX_FFlush(self.stream);
            if status == -1 {
                return Err(io::Error::last_os_error());
            }
            Ok(())
        }
    }
}