Just An Application

June 27, 2013

Programming With Rust — Part Sixteen: Representing An HTTP Response

For the moment we will concentrate on getting the client-to-server/server-to-client round-trip working so the initial representation of an HTTP Response will be distinctly minimal.

1.0 HTTP Response: The RFC 2616 Definition

1.1 Response

An HTTP Response is defined like this

    Response      = Status-Line               ; Section 6.1
                    *(( general-header        ; Section 4.5
                     | response-header        ; Section 6.2
                     | entity-header ) CRLF)  ; Section 7.1
                    CRLF
                    [ message-body ]          ; Section 7.2

1.2 Status-Line

Status-Line is defined like this

    Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF

The Status-Code is a three digit integer, 404 probably being the most familiar example, and the Reason-Phrase is any text excluding CR and/or LF, the counterpart to 404 being Not Found

2.0 Representing A Response

2.1 The Components

2.1.1 Status

The Status struct represents both the Status-Code and the Reason-Phrase since they are always paired.

    pub struct Status
    {
        code:   uint,
        reason: &'static str
    }

The Status-Code/Reason-Phrase pairs can then be represented as static items, like so

    pub static NOT_FOUND: Status = Status { code: 404, reason: "Not Found" };

2.2 The Response

For the moment the Response struct only holds a Status value.

    pub struct Response
    {
        priv status: Status
    }

3.0 Building A Response

A Response is constructed using a ResponseBuilder.

For the moment it implements a single static method notFound

    impl ResponseBuilder
    {
        pub fn notFound() -> Response
        {
            Response { status: NOT_FOUND }
        }
    }

4.0 Writing A Response

4.1 ResponseWriter

A ResponseWriter is the counterpart of a RequestBuffer.

It knows how to write the lines that make up the HTTP Response to the connection but it does not know how an HTTP Response is represented.

4.2 The Response write Method

The write method implemented by the Response struct writes the most minimal HTTP Response possible

    pub fn write(&self, writer: &mut ResponseWriter)
    {
        writer.beginResponse();
        writer.writeStatus(self.status.code, self.status.reason);
        writer.endResponse();
    }

4.0 Source Files

4.1 HTTP.rs

// HTTP.rs

// part of httpd v0.5

use std::net::url;

pub static CR: u8 = 13;

pub static LF: u8 = 10;

pub static SP: u8 = 32;

//

pub static VERSION: &'static str = "HTTP/1.1";

//

pub enum Method
{
    CONNECT,
    DELETE,
    GET,
    HEAD,
    OPTIONS,
    POST,
    PUT,
    TRACE
}

impl Method
{
    pub fn fromString(s: &str) -> Method
    {
        match s
        {
            "CONNECT" => CONNECT,
            "DELETE"  => DELETE,
            "GET"     => GET,
            "HEAD"    => HEAD,
            "OPTIONS" => OPTIONS,
            "POST"    => POST,
            "PUT"     => PUT,
            "TRACE"   => TRACE,
       
            _ => 
            {   
                fail!(fmt!("Unrecognized method %s", s));
            }
        }
    }
}

//

pub enum RequestURI
{
    AbsolutePath(~str),
    AbsoluteURI(url::Url),
    Authority(~str),
    Wildcard
}

impl RequestURI
{    
    pub fn fromString(method: Method, s: &str) -> RequestURI
    {
        match method
        {
            CONNECT =>
            {
                Authority(s.to_str())
            },
            
            _ =>
            {
                let length = s.len();
        
                if (length == 1)
                {
                    match s.char_at(0)
                    {
                        '*' =>
                        {
                            Wildcard
                        },
                
                        '/' =>
                        {
                            AbsolutePath(~"/")
                        },
                
                        _ =>
                        {
                            fail!(fmt!("Invalid URI: %s", s));
                        }
                    }
                }
                else
                {
                    match s.char_at(0)
                    {
                        '/' =>
                        {
                            AbsolutePath(s.to_str())
                        }
                        
                        _ =>
                        {                            
                            match url::from_str(s)
                            {
                                Ok(url)  => AbsoluteURI(url),
                                
                                Err(err) => fail!(err)
                            }
                        }
                    }
                }
            }
        }
    }
}

//

pub struct Status
{
    code:   uint,
	reason: &'static str
}

pub static OK:        Status = Status { code: 200, reason: "OK" };

pub static NOT_FOUND: Status = Status { code: 404, reason: "Not Found" };
 

4.2 response.rs

// response.rs

// part of httpd v0.5

use HTTP::Status;

use HTTP::NOT_FOUND;

use writer::ResponseWriter;

pub struct Response
{
    priv status: Status
}

impl Response
{
    pub fn write(&self, writer: &mut ResponseWriter)
    {
        writer.beginResponse();
        writer.writeStatus(self.status.code, self.status.reason);
        writer.endResponse();
    }
}

pub struct ResponseBuilder
{
    priv status: Status
}

impl ResponseBuilder
{
    pub fn notFound() -> Response
    {
        Response { status: NOT_FOUND }
    }
}

4.3 writer.rs

// writer.rs

// part of httpd v0.5

use std::net::tcp::TcpSocketBuf;

use HTTP::CR;
use HTTP::LF;
use HTTP::SP;
use HTTP::VERSION;

enum State
{
    INIT,
    STATUS,
    HEADERS
}

pub struct ResponseWriter
{
    priv socketBuf: TcpSocketBuf,
    priv state:     State,
    priv buffer:    ~[u8]
}

static SIZE: uint = 1024;

//

static COLON: u8 = 58;

impl ResponseWriter
{
    pub fn new(socketBuf: TcpSocketBuf) -> ResponseWriter
    {
        ResponseWriter { socketBuf: socketBuf, state: INIT, buffer: ~[0u8, ..SIZE] }
    }
    
    //
    
    pub fn beginResponse(&mut self)
    {
        self.state = INIT;
        self.buffer.clear();
    }
    
    pub fn writeStatus(&mut self, code: uint, reason: &str)
    {
        match self.state
        {
            INIT =>
            {
                // build status line
        
                self.appendString(VERSION);
                self.append(SP);
                self.appendString(code.to_str());
                self.append(SP);
                self.appendString(reason);
                //
                self.writeLine();
                //
                self.state = STATUS;
            }
            
            _ =>
            {
                fail!(~"Internal Error");
            }
        }
    }
    
    pub fn writeHeader(&mut self, key: &str, value: &str)
    {
        match self.state
        {
            STATUS|HEADERS =>
            {
                // build message-header
        
                self.appendString(key);
                self.append(COLON);
                self.append(SP);
                self.appendString(value);
                //
                self.writeLine();
                //
                self.state = HEADERS;
            }
            
            _ =>
            {
                fail!(~"Internal Error");
            }
        }
    }
    
    pub fn endResponse(&mut self)
    {
        match self.state
        {
            STATUS|HEADERS =>
            {
                self.writeLine();
            }
            
            _ =>
            {
                fail!(~"Internal Error");
            }
        }
    }
    
    //
    
    fn writeLine(&mut self)
    {
        io::println(str::from_bytes(self.buffer));
        
        
        self.append(CR);
        self.append(LF);
        self.socketBuf.write(self.buffer);
        self.buffer.clear();
    }
    
    fn append(&mut self, byte: u8)
    {
        self.buffer.push(byte);
    }
    
    fn appendBytes(&mut self, bytes: &[u8])
    {
        self.buffer.push_all(bytes);
    }
    
    fn appendString(&mut self, s: &str)
    {
        self.appendBytes(s.to_bytes());
    }
}

Copyright (c) 2013 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

June 20, 2013

Programming With Rust — Part Thirteen: Reading An HTTP Request

Now we have a connection we can read the incoming HTTP request.

1.0 An HTTP Request

An HTTP request arrives over a connection as

  • a request line

followed by

  • zero or more header lines

followed by

  • an empty line

followed by

  • a message body

which is optional.

A line is terminated by a carriage-return (CR == ASCII 13) immediately followed by a line-feed (LF == ASCII 10).

Ideally we would like to read the entire request from the connection “in one go” but that is not possible because there is no way of knowing how big a given HTTP request is before we read it.

To abstract out the unfortunately indeterminate nature of an incoming HTTP request, we will start by defining a RequestBuffer type which will deal with the vagaries of reading the necessary bytes from the connection and converting them into lines.

2.0 RequestBuffer: Take One

The original idea was that RequestBuffer would look something like this

    struct RequestBuffer
    {
        priv socketBuf:     TcpSocketBuf,
        priv bytes:         ~[u8],
        priv size:          uint,
        priv available:     uint,
        priv position:      uint,
        priv lastLineEnd:   uint
    }

and there would be a readLine method which would look something like this

    impl RequestBuffer
    {
        ...
        
        fn readLine(&mut self) -> ~str
        {
            let mut state = 0;
		
            loop
            {
                if (self.position == self.available)
                {
                    // read all the bytes currently available from the connection
            
                    ...
                }
            
                let b = self.bytes[self.position];
			
                self.position += 1;
            
                match state
                {
                    0 => 
                    {
                        if (b == 13) // CR
                        {
                            state = 1;
                        }
                    },
						
                    1 => 
                    {
                        if (b == 10) // LF
                        {
                            // make string representing line from buffered bytes
                    
                            let line = ...
                        
                            self.lastLineEnd = self.position;
                            return line;
                        }
                        else
                        {
                            state = 0;
                        }
                    },
						
                    _ => 
                    {
                        fail!(fmt!("state == %u !", state));
                    }
                }
            }
        }
        
        ...
        
    }

but at the moment there does not seem to be any way of simply reading all the bytes currently available on the connection in one go that actually works.

2.1 RequestBuffer: Take Two

This is a version that works but there really isn’t a whole lot of buffering going on.

2.1.1 RequestBuffer

    struct RequestBuffer
    {
        priv socketBuf: TcpSocketBuf,
        priv bytes:     ~[u8],
    }

2.1.2 The readLine Method

    ...

    static CR: u8 = 13;

    static LF: u8 = 10;

    ...

    impl RequestBuffer
    {
    
        ...
        
        fn readLine(&mut self) -> ~str
        {
            self.bytes.clear();
        
            let mut state = 0;
		
            loop
            {
                let i = self.socketBuf.read_byte();
			
                if (i < 0)
                {
                    fail!(~"EOF");
                }
            
                let b = i as u8;
            
                match state
                {
                    0 => 
                    {
                        if (b == CR)
                        {
                            state = 1;
                        }
                    },
						
                    1 => 
                    {
                        if (b == LF)
                        {
                            return str::from_bytes(vec::const_slice(self.bytes, 0, self.bytes.len() - 1));
                        }
                        else
                        {
                            state = 0;
                        }
                    },
						
                    _ => 
                    {
                        fail!(fmt!("state == %u !", state));
                    }
                }
                self.bytes.push(b);
            }
        }

        ...
        
    }

2.1.3 The new Method

The new method is a static method which can be used to create a RequestBuffer.

    fn new(socketBuf: TcpSocketBuf) -> RequestBuffer
    {
        RequestBuffer { socketBuf: socketBuf, bytes: ~[0u8, ..SIZE] }
    }

3.0 The handleConnection Function

If the accept function is successful we now call the handleConnection function which is defined like this

    fn handleConnection(socket: TcpSocket)
    {	
        let mut buffer      = RequestBuffer::new(socket_buf(socket));
        let     requestLine = buffer.readLine();
	
        io::println(requestLine);
        loop
        {
            let line = buffer.readLine();
		
            io::println(line);
            if (str::len(line) == 0)
            {
                break;
            }
        }
        io::stdout().flush();
        fail!(~"Now what ?");
    }

4.0 Running The Code

Running the code and pointing a web browser at 127.0.0.1:3534 produces this

 
    ./httpd
    on_establish_callback({x: {data: (0x1007094f0 as *())}})
    new_connection_callback(NewTcpConn((0x10200b000 as *())), {x: {data: (0x1007094f0 as *())}})
    accept succeeded
    GET / HTTP/1.1
    Host: 127.0.0.1:3534
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:21.0) Gecko/20100101 Firefox/21.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    DNT: 1
    Connection: keep-alive

    rust: task failed at 'Now what ?', httpd.rc:172
    rust: domain main @0x10201ee10 root task failed
    rust: task failed at 'killed', /Users/simon/Src/lang/rust-0.6/src/libcore/pipes.rs:314

5.0 The Source Code For httpd v0.3


// httpd.rc

// v0.3

extern mod std;

use core::comm::SharedChan;

use core::option::Option;


use core::task;

use std::net::ip;
use std::net::tcp;
use std::net::tcp::TcpErrData;
use std::net::tcp::TcpNewConnection;
use std::net::tcp::TcpSocket;
use std::net::tcp::TcpSocketBuf;

use std::net::tcp::socket_buf;

use std::sync::Mutex;

use std::uv_iotask;

// RequestBuffer

struct RequestBuffer
{
    priv socketBuf: TcpSocketBuf,
    priv bytes:     ~[u8],
}

//

static SIZE: uint = 4096;

// 

static CR: u8 = 13;

static LF: u8 = 10;

impl RequestBuffer
{    
    fn new(socketBuf: TcpSocketBuf) -> RequestBuffer
    {
        RequestBuffer { socketBuf: socketBuf, bytes: ~[0u8, ..SIZE] }
    }

    fn readLine(&mut self) -> ~str
    {
        self.bytes.clear();
        
        let mut state = 0;
		
        loop
        {
            let i = self.socketBuf.read_byte();
			
            if (i < 0)
            {
                fail!(~"EOF");
            }
            
            let b = i as u8;
            
            match state
            {
                0 => 
                {
                    if (b == CR)
                    {
                        state = 1;
                    }
                },
						
                1 => 
                {
                    if (b == LF)
                    {
                        return str::from_bytes(vec::const_slice(self.bytes, 0, self.bytes.len() - 1));
                    }
                    else
                    {
                        state = 0;
                    }
                },
						
                _ => 
                {
                    fail!(fmt!("state == %u !", state));
                }
            }
            self.bytes.push(b);
        }
    }
}


static BACKLOG: uint = 5;
static PORT:    uint = 3534;

static IPV4_LOOPBACK: &'static str = "127.0.0.1";


fn on_establish_callback(chan: SharedChan<Option<TcpErrData>>)
{
    io::println(fmt!("on_establish_callback(%?)", chan));
}

fn new_connection_callback(newConn :TcpNewConnection, chan: SharedChan<Option<TcpErrData>>)
{
    io::println(fmt!("new_connection_callback(%?, %?)", newConn, chan));
	
    let mx = Mutex();
	
    do mx.lock_cond
        |cv|
        {
            let mxc = ~mx.clone();

            do task::spawn 
            {
                match tcp::accept(newConn)
                {
                    Ok(socket) => 
                    {
                        io::println("accept succeeded");
                        do mxc.lock_cond
                            |cv|
                            {
                                cv.signal();
                            }
                        handleConnection(socket);
				                
                    },
                    Err(error) => 
                    {
                        io::println(fmt!("accept failed: %?", error));
                        do mxc.lock_cond
                            |cv|
                            {
                                cv.signal();
                            }
                    }
                }
            }
            cv.wait();
        }
}

fn handleConnection(socket: TcpSocket)
{	
    let mut buffer      = RequestBuffer::new(socket_buf(socket));
    let     requestLine = buffer.readLine();
	
    io::println(requestLine);
    loop
    {
        let line = buffer.readLine();
		
        io::println(line);
        if (str::len(line) == 0)
        {
            break;
        }
    }
    io::stdout().flush();
    fail!(~"Now what ?");
}

fn main()
{	
    tcp::listen(
        ip::v4::parse_addr(IPV4_LOOPBACK),
        PORT,
        BACKLOG,
        &uv_iotask::spawn_iotask(task::task()),
        on_establish_callback,
        new_connection_callback);
}


Copyright (c) 2013 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

June 18, 2013

Programming With Rust — Part Twelve: Even More Things We Need To Know – Methods

1.0 Implementations

Methods are defined for a specific type using an implementation.

For example, given this struct definition

    struct QName
    {
        prefix:    ~str,
        nsURI:     ~str,
        localPart: ~str
    }

we would do this to define methods on the type QName

    impl QName
    {
        ... method definitions go here ...
    }

2.0 Method Definitions

A method is defined in the same way as a function but with self or some kind of pointer to self as a special first parameter.

At runtime self is bound to the value on which the method has been invoked.

For example,

    impl QName
    {
        ...
        
        fn getPrefix(&self) -> ~str
        {
            self.prefix.clone()
        }

        ...
    }

Using a borrowed pointer to self means that the method is applicable to a value of type QName regardless of where it is stored.

If a method can modify the value on which it is called the self parameter must be declared mutable.

    impl QName
    {
        ...
        
        fn setPrefix(&mut self, prefix: ~str)
        {
            self.prefix = prefix;
        }
        
        ...
    }

3.0 Static Method Definitions

A static method definition is an ordinary function definition, or, if you prefer, a method definition without a self parameter.

We can define a static method new to create a QName value like this

    impl QName
    {
        ...
        
        
        fn new(prefix: ~str, nsURI: ~str, localPart: ~str) -> QName
        {
            QName { prefix: prefix, nsURI: nsURI, localPart: localPart }
        }
        
        ...
    }

4.0 Invoking Static Methods

Static methods are invoked as functions but with the name prefixed by the type name like so

    let rights = QName::new(~"dc", ~"http://purl.org/dc/elements/1.1/", ~"rights");


Copyright (c) 2013 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and
specific direction to the original content.

Create a free website or blog at WordPress.com.

%d bloggers like this: