Just An Application

June 27, 2013

Programming With Rust — Part Seventeen: The Rest Of httpd v0.5

1.0 Handling A Connection

The handleConnection function now looks like this

    fn handleConnection(socket: TcpSocket)
    {	    
        let     socketBuf = tcp::socket_buf(socket);
        let mut buffer    = RequestBuffer::new(socketBuf);
        let mut writer    = ResponseWriter::new(socketBuf);

        let     request   = Request::read(&mut buffer);
        let     response  = handleRequest(&request);
        
        response.write(&mut writer);
    }

This is both sub-optimal and incorrect and results in the connection being closed after a single Request but it will do for now.

2.0 Handling A Request

The handleRequest function currently does nothing more than print out the Request and return a ‘not found’ Response.

    fn handleRequest(request: &Request) -> Response
    {
        io::println(fmt!("request == %?", request));
    
        ResponseBuilder::notFound()
    }

3.0 Testing

Connecting with a web browser is OK as a basic ‘smoke test’ but we are now at the stage where we need some actual tests.

While it is tempting to write the tests in Rust as well by extending the server side code to support HTTP clients, when writing
anything involving protocols it is always a good idea to test against a completely independent implementation if possible.

3.1 The Test Code

Below is some very simple test code written in Ruby.

    require 'net/http'

    class MethodTests

        def initialize(host, port)
    
            @host = host
            @port = port
        
        end
        
        def run()
    
            method_tests.each { |method| self.method(method).call() }

        end
    
        def connect()

            begin
         
                Net::HTTP.start(
                              @host, @port + 2, 
                              @host, @port, 
                              'sjL', 'sjL', { :use_ssl => true } ) do |http|
            
                    http.get('/connect_test')

                end
         
            rescue Net::HTTPServerException => hse
         
                puts 'connect: ' + hse.to_s

            end

        end

        def delete

            http_method('delete')

        end
    
        def get

            http_method('get')

        end
    
        def get_proxy
    
            begin
         
                Net::HTTP.start(@host, @port + 2, @host, @port) do |http|
            
                    res = http.get('/absolute_uri_test')

                    puts 'get_proxy: ' + res.code

                end
         
            rescue Net::HTTPServerException => hse
         
                puts 'get_proxy: ' + hse.to_s

            end
    
        end
    
        def head

            http_method('head')
        end
    
        def options

            http_method('options', '*')

        end

        def post
        
            http_method('post', '/post_test', '')

        end

        def put

            http_method('put', '/put_test', '')

        end


        def trace
    
            http_method('trace')
        
        end
    
        private

            def method_tests
        
                ['connect', 'delete', 'get', 'get_proxy', 'head', 'options', 'post', 'put', 'trace']

            end
        
            def http_method(name, *args)
        
                res = Net::HTTP.start(@host, @port) do |http|
    
                    case args.length
                
                        when 1
                    
                            http.method(name).call(args[0])
                       
                        when 2
                    
                            http.method(name).call(args[0], args[1])
                       
                        else
                      
                            http.method(name).call('/' + name + '_test')
                        
                    end

                end
                puts name + ': ' + res.code

            end
        
    end


    MethodTests.new('127.0.0.1', 3534).run()


3.2 The Tests

Between them the tests test all the supported methods and request URI formats.

The only test methods that are possibly not self-explanatory are connect and get_proxy

3.2.1 connect

The connect test method creates an HTTP client that will attampt to use the actual HTTP server at @host:@port as a proxy to access another non-existent HTTP server at @host:(@port + 2) using a secure connection.

The call to the HTTP client’s get method causes it to attempt to create an SSL connection which tunnels through the proxy using the HTTP CONNECT method, which is what we want.

3.2.2 get_proxy

Like the connect test method, the get_proxy test method creates an HTTP client that will attampt to use the actual HTTP server at @host:@port as a proxy to access another non-existent HTTP server at @host:(@port + 2) but in this case using an ordinary non-secure connection.

The call to the HTTP client’s get method causes it to send an HTTP Request with an Absolute URI as the Request URI.

4.0 Running The Tests

4.1 Test Output

When run the test code outputs the following

connect: 404 "Not Found"
delete: 404
get: 404
get_proxy: 404
head: 404
options: 404
post: 404
put: 404
trace: 404

4.2 Server Output

When the test code is run httpd outputs the following (heavily edited for readability)

./httpd
on_establish_callback({x: {data: (0x100709600 as *())}})
new_connection_callback(NewTcpConn((0x101810400 as *())), {x: {data: (0x100709600 as *())}})
accept succeeded
request == &{method: CONNECT, \
             uri: Authority(~"127.0.0.1:3536"), \
             headers: {headers: ~[{key: ~"Host", value: ~"127.0.0.1:3536"}, \
                                  {key: ~"Proxy-Authorization", value: ~"Basic c2pMOnNqTA=="}]}}
new_connection_callback(NewTcpConn((0x101810400 as *())), {x: {data: (0x100709600 as *())}})
accept succeeded
request == &{method: DELETE, \
             uri: AbsolutePath(~"/delete_test"), \
             headers: {headers: ~[{key: ~"Depth", value: ~"Infinity"}, {key: ~"Accept", value: ~"*/*"}, \
                                  {key: ~"User-Agent", value: ~"Ruby"}, {key: ~"Host", value: ~"127.0.0.1:3534"}]}}
new_connection_callback(NewTcpConn((0x101810400 as *())), {x: {data: (0x100709600 as *())}})
accept succeeded
request == &{method: GET, \
             uri: AbsolutePath(~"/get_test"), \
             headers: {headers: ~[{key: ~"Accept-Encoding", value: ~"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"}, \
                                  {key: ~"Accept", value: ~"*/*"}, {key: ~"User-Agent", value: ~"Ruby"}, \
                                  {key: ~"Host", value: ~"127.0.0.1:3534"}]}}
new_connection_callback(NewTcpConn((0x101810400 as *())), {x: {data: (0x100709600 as *())}})
accept succeeded
request == &{method: GET, \
             uri: AbsoluteURI({scheme: ~"http", user: None, host: ~"127.0.0.1", port: Some(~"3536"), \
             path: ~"/absolute_uri_test", query: ~[], fragment: None}), \
             headers: {headers: ~[{key: ~"Accept-Encoding", value: ~"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"}, \
                                  {key: ~"Accept", value: ~"*/*"}, {key: ~"User-Agent", value: ~"Ruby"},\
                                  {key: ~"Host", value: ~"127.0.0.1:3536"}]}}
new_connection_callback(NewTcpConn((0x101810400 as *())), {x: {data: (0x100709600 as *())}})
accept succeeded
request == &{method: HEAD, \
             uri: AbsolutePath(~"/head_test"), \
             headers: {headers: ~[{key: ~"Accept", value: ~"*/*"}, {key: ~"User-Agent", value: ~"Ruby"}, \
                                  {key: ~"Host", value: ~"127.0.0.1:3534"}]}}
new_connection_callback(NewTcpConn((0x101810400 as *())), {x: {data: (0x100709600 as *())}})
accept succeeded
request == &{method: OPTIONS, \
             uri: Wildcard, \
             headers: {headers: ~[{key: ~"Accept", value: ~"*/*"}, {key: ~"User-Agent", value: ~"Ruby"}, \
                                  {key: ~"Host", value: ~"127.0.0.1:3534"}]}}
new_connection_callback(NewTcpConn((0x101810400 as *())), {x: {data: (0x100709600 as *())}})
accept succeeded
request == &{method: POST, \
             uri: AbsolutePath(~"/post_test"), \
             headers: {headers: ~[{key: ~"Accept", value: ~"*/*"}, {key: ~"User-Agent", value: ~"Ruby"}, \
                                  {key: ~"Host", value: ~"127.0.0.1:3534"}, {key: ~"Content-Length", value: ~"0"}, \
                                  {key: ~"Content-Type", value: ~"application/x-www-form-urlencoded"}]}}
new_connection_callback(NewTcpConn((0x101810400 as *())), {x: {data: (0x100709600 as *())}})
accept succeeded
request == &{method: PUT, 
             uri: AbsolutePath(~"/put_test"), \
             headers: {headers: ~[{key: ~"Accept", value: ~"*/*"}, {key: ~"User-Agent", value: ~"Ruby"}, \
                                  {key: ~"Host", value: ~"127.0.0.1:3534"}, {key: ~"Content-Length", value: ~"0"}, \
                                  {key: ~"Content-Type", value: ~"application/x-www-form-urlencoded"}]}}
new_connection_callback(NewTcpConn((0x101810400 as *())), {x: {data: (0x100709600 as *())}})
accept succeeded
request == &{method: TRACE, \
             uri: AbsolutePath(~"/trace_test"), \
             headers: {headers: ~[{key: ~"Accept", value: ~"*/*"}, {key: ~"User-Agent", value: ~"Ruby"},\
                                  {key: ~"Host", value: ~"127.0.0.1:3534"}]}}

5.0 Source Files

5.1 httpd.rc

// httpd.rc

// v0.5

extern mod std;

mod HTTP;
mod buffer;
mod headers;
mod request;
mod response;
mod server;
mod writer;

static PORT: uint = 3534;

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

fn main()
{	
    server::run(IPV4_LOOPBACK, PORT);
}

5.2 server.rs

// server.rs

// part of httpd v0.5

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::sync::Mutex;

use std::uv_iotask;


use buffer::RequestBuffer;

use request::Request;

use response::Response;
use response::ResponseBuilder;

use writer::ResponseWriter;

//

static BACKLOG: uint = 5;


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

fn new_connection_callback(newConn :TcpNewConnection, chan: SharedChan<Option>)
{
    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     socketBuf = tcp::socket_buf(socket);
    let mut buffer    = RequestBuffer::new(socketBuf);
    let mut writer    = ResponseWriter::new(socketBuf);
    
    let     request   = Request::read(&mut buffer);
    let     response  = handleRequest(&request);
    
    response.write(&mut writer);
}

//


fn handleRequest(request: &Request) -> Response
{
    io::println(fmt!("request == %?", request));
    
    ResponseBuilder::notFound()
}


//

pub fn run(address: &str, port: uint)
{	
    tcp::listen(
        ip::v4::parse_addr(address),
        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.

Advertisements

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 24, 2013

Programming With Rust — Part Fifteen: Representing An HTTP Request

Now we can read a raw HTTP response we need to turn it into something useful.

1.0 HTTP Request: The RFC 2616 Definition

1.1 Request

An HTTP Request is defined like this

    Request    = Request-Line              ; Section 5.1
                 *(( general-header        ; Section 4.5
                  | request-header         ; Section 5.3
                  | entity-header ) CRLF)  ; Section 7.1
                 CRLF
                 [ message-body ]          ; Section 4.3

1.2 Request-Line

Request-Line is defined like this

    Request-Line    = Method SP Request-URI SP HTTP-Version CRLF

1.2.1 Method

Method is defined like this

    Method    = "OPTIONS"                ; Section 9.2
              | "GET"                    ; Section 9.3
              | "HEAD"                   ; Section 9.4
              | "POST"                   ; Section 9.5
              | "PUT"                    ; Section 9.6
              | "DELETE"                 ; Section 9.7
              | "TRACE"                  ; Section 9.8
              | "CONNECT"                ; Section 9.9
              | extension-method

1.2.2 Request-URI

Request-URI is defined like this

    Request-URI    = "*" | absoluteURI | abs_path | authority

where absoluteURI, abs_path and authority are as defined in RFC 2396.

1.3 Headers

general-header, request-header and entity-header
are all forms of message-header which is defined like this

    message-header = field-name ":" [ field-value ]

1.4 The Message Body

message-body is defined like this.

    message-body = entity-body
                   | <entity-body encoded as per Transfer-Encoding>
    entity-body    = *OCTET

2.0 Representing A Request

2.1 The Components

2.1.1 The Request Method

We can represent the Method using an enum like so

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

For the moment we are not going to support extension methods

2.1.2 The Request URI

We can represent the Request URI in terms of its four possible variants by using an enum like so

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

The AbsoluteURI variant uses the type Url from the std library.

2.1.3 Headers

We are going to represent the set of headers using a new type Headers which is defined like this

    pub struct Headers
    {
        priv headers: ~[Header]
    }

and Header is defined like this

    struct Header
    {
        priv key:   ~str,
        priv value: ~str
    }

2.2 The Request

A Request simply holds the method, the URI and the headers.

    pub struct Request
    {
        priv method:  Method,
        priv uri:     RequestURI,
        priv headers: Headers
    }

For the moment we are going to ignore the message-body.

3.0 The Request Methods

3.1 Reading A Request

We define a static method read on the Request type which takes a RequestBuffer and returns a Request

    pub fn read(buffer: &mut RequestBuffer) -> Request
    {
        let (method, uri) = readRequestLine(buffer);
        let headers       = Headers::read(buffer);
        
        Request { method: method, uri: uri, headers: headers}
    }

The function readRequestLine reads the request line and splits it into its constituent parts and returns them as a tuple.

    fn readRequestLine(buffer: &mut RequestBuffer) -> (Method, RequestURI)
    {
        let     line           = buffer.readLine();
        let mut parts: ~[&str] = ~[];
        
        str::each_word(line, |part| { parts.push(part); true });
    
        if (vec::len(parts) != 3)
        {
            fail!(fmt!("Invalid status line: %s", line));
        }
    
        let method = Method::fromString(parts[0]);
        let uri    = RequestURI::fromString(method, parts[1]);
    
        (method, uri)
    }

3.2 Accessors

3.2.1 getMethod

This method is very simple. It just returns the Method held by the Request.

    pub fn getMethod(&self) -> Method
    {
         self.method
    }

3.2.2 getURI

This method is a little less straightforward than getMethod.

3.2.1.1 Take One

We cannot simply return a shallow copy of the RequestURI like this

    pub fn getURI(&self) -> RequestURI
    {
        self.uri
    }

because to quote the compiler

    request.rs:41:8: 41:16 error: moving out of immutable field
    request.rs:41         self.uri
                          ^~~~~~~~
    error: aborting due to previous error

As we know in this case the RequestURI value is returned via a shallow copy.

A RequestURI value may contain an owned pointer so this would be moved to the copy being returned which would make the original in the Request invalid.

3.2.1.2 Take Two

We could return a deep copy with a pointer to a copy of the original string in the owned heap as well, but this seems a tad extravagant.

An alternative would be to return a borrowed pointer like so

    pub fn getURI(&self) -> &RequestURI
    {
        &self.uri
    }

but the compiler is now very unhappy indeed

    request.rs:41:8: 41:17 error: cannot infer an appropriate lifetime due to conflicting requirements
    request.rs:41         &self.uri
                      ^~~~~~~~~
    request.rs:40:4: 42:5 note: first, the lifetime cannot outlive the lifetime &'self  as defined on the block at 40:4...
    request.rs:40     {
    request.rs:41         &self.uri
    request.rs:42     }
    request.rs:41:8: 41:17 note: ...due to the following expression
    request.rs:41         &self.uri
                      ^~~~~~~~~
    request.rs:40:4: 42:5 note: but, the lifetime must be valid for the anonymous lifetime #1 defined on the block at 40:4...
    request.rs:40     {
    request.rs:41         &self.uri
    request.rs:42     }
    request.rs:41:8: 41:17 note: ...due to the following expression
    request.rs:41         &self.uri
                      ^~~~~~~~~
    error: aborting due to previous error

but it does have a point.

We have not so much attempted to borrow a pointer, as tried to walk off with it without any indication of when we might be planning to give it back, if ever.

3.2.1.3 Take Three

To placate the compiler and assure it of our good intentions we need to explicitly specify the intended lifetime of the borrowed pointer like this

    pub fn getURI(&self) -> &'self RequestURI
    {
        &self.uri
    }

The named lifetime

    'self

specifies that the lifetime of the borrowed pointer is the same as the thing from which it was borrowed which in this case is the Request.

This makes sense because as long as the Request exists the RequestURI exists, so as long as the Request exists the borrowed pointer to the RequestURI is valid.

3.2.3 getHeader

This method delegates to the Headers get method and returns its result.

    pub fn getHeader(&self, key: &str) -> Option<&'self str>
    {
        self.headers.get(key)
    }

4.0 The Headers Methods

4.1 Reading The Headers

The static method read reads the successive header lines until an empty line is read.

Each header lined is convered to a Header value.

The method returns a Headers value constructed using the collected Header values.

    pub fn read(buffer: &mut RequestBuffer) -> Headers
    {
        let mut headers: ~[Header] = ~[];
        
        loop
        {
            let line = buffer.readLine();
        
            if (line.len() == 0)
            {
                break;
            }
            headers.push(toHeader(line));
        }
        Headers { headers: headers }
    }

The function toHeader splits the header line into a key and a value and returns the result as a Header value.

    fn toHeader(line: &str) -> Header
    {
        let mut parts: ~[&str] = ~[];
    
        str::each_splitn_char(line, ':', 1, |s| { parts.push(s); true });
        if (vec::len(parts) != 2)
        {
            fail!(fmt!("Bad header: %s", line));
        }
    
        Header { key: parts[0].to_str(), value: parts[1].trim().to_str() }
    }

4.2 Getting A Header Value

The get method returns an Option as it is possible that the specified header was not present in the original HTTP Request.

If the header is present a borrowed pointer to the value is returned via the Option Ok variant.

The lifetime of the borrowed pointer is explicitly specified to be self. (See the Request getURI method.)

    pub fn get(&self, key: &str) -> Option<&'self str>
    {
        let lcKey = str::to_lower(key);
        
        match self.headers.position(|h| h.key.to_lower() == lcKey)
        {
            Some(index) => 
            {
                let value: &str = self.headers[index].value;
                
                Some(value)
            },
            
            None        => None
        }
    }

5.0 Method and RequestURI Methods

5.1 Method.fromString

The Method fromString static method is very simple. If the argument matches one of the defined methods return the corresponding enum variant, otherwise fail.

    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));
                }
            }
        }
    }

5.2 RequestURI.fromString

The RequestURI fromString static method converts the string specifying the URI contained in the HTTP Request into one of the variants of the enum type RequestURI.

It takes the Method of the Request as an argument so that it can identify the CONNECT case where the specified Request-URI is actually is an authority which may otherwise be indistinguishable from an absoluteURI in certain circumstances.

    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)
                                }
                            }
                        }
                    }
                }
            }
        }
    }

6.0 Source Files

6.1 headers.rs

// headers.rs

// part of httpd v0.5

use buffer::RequestBuffer;

pub struct Headers
{
    priv headers: ~[Header]
}

struct Header
{
    priv key:   ~str,
    priv value: ~str
}

impl Headers
{
    pub fn read(buffer: &mut RequestBuffer) -> Headers
    {
        let mut headers: ~[Header] = ~[];
        
        loop
        {
            let line = buffer.readLine();
        
            if (line.len() == 0)
            {
                break;
            }
            headers.push(toHeader(line));
        }
        Headers { headers: headers }
    }
    
    pub fn get(&self, key: &str) -> Option<&'self str>
    {
        let lcKey = str::to_lower(key);
        
        match self.headers.position(|h| h.key.to_lower() == lcKey)
        {
            Some(index) => 
            {
                let value: &str = self.headers[index].value;
                
                Some(value)
            },
            
            None        => None
        }
    }
}


fn toHeader(line: &str) -> Header
{
    let mut parts: ~[&str] = ~[];
    
    str::each_splitn_char(line, ':', 1, |s| { parts.push(s); true });
    if (vec::len(parts) != 2)
    {
        fail!(fmt!("Bad header: %s", line));
    }
    
    Header { key: parts[0].to_str(), value: parts[1].trim().to_str() }
}

6.2 request.rs

// request.rs

// part of httpd v0.5

use HTTP::Method;
use HTTP::RequestURI;

use buffer::RequestBuffer;

use headers::Headers;

//

pub struct Request
{
     priv method:  Method,
     priv uri:     RequestURI,
     priv headers: Headers
}

impl Request
{
    pub fn read(buffer: &mut RequestBuffer) -> Request
    {
        let (method, uri) = readRequestLine(buffer);
        let headers       = Headers::read(buffer);
        
        Request { method: method, uri: uri, headers: headers}
    }
    
    //
    
    pub fn getMethod(&self) -> Method
    {
         self.method
    }
    
    pub fn getURI(&self) -> &'self RequestURI
    {
        &self.uri
    }
    
    pub fn getHeader(&self, key: &str) -> Option<&'self str>
    {
        self.headers.get(key)
    }
}

fn readRequestLine(buffer: &mut RequestBuffer) -> (Method, RequestURI)
{
    let     line           = buffer.readLine();
    let mut parts: ~[&str] = ~[];
        
    str::each_word(line, |part| { parts.push(part); true });
    
    if (vec::len(parts) != 3)
    {
        fail!(fmt!("Invalid status line: %s", line));
    }
    
    let method = Method::fromString(parts[0]);
    let uri    = RequestURI::fromString(method, parts[1]);
    
    (method, uri)
}


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 21, 2013

Programming With Rust — Part Fourteen: Crates, Modules And Files

Whilest adding the RequestBuffer definitions directly to the existing httpd.rc file was OK for the purposes of experimentation it is not a good habit to get into.

If we keep lobbing everything into httpd.rc before very long it will have turned into an impenetrable “wall of code”.

We need to place sets of related definitions in separate files and then specify that they are part of the crate.

1.0 Crate Files And Modules

A crate comprises an anonymous module and the items it contains.

These items are defined in the crate file.

The items in the crate file can include module items.

A module item defines a module nested within the anonymous top-level module of the crate.

A module item can define a module and the items it contains explicitly or it can reference a module contained in another file.

A module item which references a module in another file is of the form

    "mod" module_name ';'

By default this identifies the module contained in a file of the same name with the suffix “.rs”.

For example, the module item

    mod buffer;

identifies the module buffer in the file buffer.rs

2.0 Source Files And Modules

A Rust source file implicitly defines a module which contains all the items within that file.

When specified as part of a crate the name of the implicitly defined module is determined by the module item in the crate file which references it.

3.0 Moving Items Into Separate Files

We effectively have two sets of items which we can move into separate files.

  • the items which define the RequestBuffer and implement it, and

  • the function definitions and static items for listening on a socket and accepting a connection

We will move these into the files

    buffer.rs

and

    server.rs

respectively.

3.1 buffer.rs

We need to make the RequestBuffer struct and the static method new and the method readLine public so they can be accessed from outside the module. [1].

3.2 server.rs

In server.rs we need to add

    use buffer::RequestBuffer;

and we are going to rename the main function to run and add parameters for address and port so they can be passed in rather than being hard-wired

httpd.rc

In httpd.rc nearly everything has gone.

We need to add

    ...
    mod buffer;
    mod server;
    ...

We are going to hang on the IPV4_LOOPBACK and PORT static items so we can pass them to the function server::run which we are going to invoke from our new main function.

4.0 The New httpd Crate Module Structure

crate_file

5.0 The Source Code For httpd v0.4

5.1 buffer.rs

// buffer.rs

// part of httpd v0.4


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

// RequestBuffer

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

//

static SIZE: uint = 4096;

// 

static CR: u8 = 13;

static LF: u8 = 10;

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

    pub 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);
        }
    }
}

5.2 server.rs


// server.rs

// part of httpd v0.4

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::socket_buf;

use std::sync::Mutex;

use std::uv_iotask;

use buffer::RequestBuffer;



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

fn new_connection_callback(newConn :TcpNewConnection, chan: SharedChan<Option>)
{
    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 ?");
}

static BACKLOG: uint = 5;


pub fn run(address: &str, port: uint)
{	
    tcp::listen(
        ip::v4::parse_addr(address),
        port,
        BACKLOG,
        &uv_iotask::spawn_iotask(task::task()),
        on_establish_callback,
        new_connection_callback);
}

5.3 httpd.rc

// httpd.rc

// v0.4

extern mod std;

mod buffer;
mod server;

static PORT: uint = 3534;

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

fn main()
{	
    server::run(IPV4_LOOPBACK, PORT);
}

Notes

  • I discovered quite inadvertently that it is not actually necessary to make the RequestBuffer type public explicitly.
    The implementation seems to do this implicitly. This behaviour does not seem to be documented.


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.

June 17, 2013

Programming With Rust — Part Eleven: Even More Things We Need To Know – The Portmanteau Edition

1.0 Machine Independent Integers

Rust supports explicitly sized, and hence machine independent, signed and unsigned integer types.

The supported signed integer types are

  • i8

  • i16

  • i32

  • i64

The supported unsigned integer types are, unsurprisingly

  • u8

  • u16

  • u32

  • u64

2.0 Vectors

A Rust vector type specifies a one-dimensional array of values of the same type.

The vector type can specify an exact size

   [u8, ..512]

in which case it can be allocated on the stack.

If the type does not specify an exact size then it cannot be allocated on the stack and the type declaration must identify where it should be allocated, which is either in the managed heap of the current task, like so

   @[u8]

or in the owned heap, like so

   ~[u8]

A value of a vector type can be specified by explicitly enumerating all of its elements

    let o_vec: ~[u8] = ~[0, 1, 2, 3];

or by specifying an initial value and a size.

   let s_vec: [u8, ..512] = [0u8, ..512];

Individual elements of a vector can be accessed by indexing as you might expect.

For example

   let element_zero: u8 = s_vec[0];

3.0 Structs

A Rust struct type defines a set of named values.

For example

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

This is an example of a struct item.

3.1 Creating Values

A value of a struct type is created using a struct expression which has a similar syntax to a struct item

    typename '{' [fieldname ':' expression] [',' fieldname ':' expression ]* '}'

For example you can create an value of type QName like this

    let rights = QName {prefix:~"dc", nsURI:~"http://purl.org/dc/elements/1.1/", localPart:~"rights"};

This creates an immutable value of type QName in the current stack frame.

You can also create a value of a struct type in the managed heap, for example

    let m_rights = @QName {prefix:~"dc", nsURI:~"http://purl.org/dc/elements/1.1/", localPart:~"rights"};

or in the owned heap

    let o_rights = ~QName {prefix:~"dc", nsURI:~"http://purl.org/dc/elements/1.1/", localPart:~"rights"};

3.2 Direct Field Access

You can access the fields of a value of struct type using dot (.) notation, like so

    ...

    let rights_prefix:    &str = rights.prefix;
    let rights_nsURI:     &str = rights.nsURI;
    let rights_localPart: &str = rights.localPart;

    ...

3.3 Access Control

The fields of a value of a struct type are public by default but they can be made private like so

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

The effect of this is to limit construction of values of the struct type, and direct field access, to code defined in the module in which the struct is defined.

4.0 Tuples

A Tuple is an immutable fixed-size collection of values.

A tuple value is written as a parentheses delimited list of comma separated values.

A tuple type is written as a parentheses delimited list of comma separated types.

For example, extending the field access example above, we can do this

    ...

    let rights_prefix:    &str = rights.prefix;
    let rights_nsURI:     &str = rights.nsURI;
    let rights_localPart: &str = rights.localPart;
    
    ...
    
    let rights_tuple:  (&str, &str, &str) = (rights_prefix, rights_nsURI, rights_localPart);

    ...

The only way to access the elements of a tuple is by pattern matching.

For example

   ...

    let (prefix, nsURI, localPart) = rights_tuple;
    
    io::println(fmt!("prefix     == %?", prefix));
    io::println(fmt!("nsURI      == %?", nsURI));
    io::println(fmt!("localPart  == %?", localPart));

    ...

prints

    prefix     == "dc"
    nsURI      == "http://purl.org/dc/elements/1.1/"
    localPart  == "rights"

You can achieve the same effect by using a match expression like this

   match rights_tuple
    {
        (prefix, nsURI, localPart) =>
        {
            io::println(fmt!("prefix     == %?", prefix));
            io::println(fmt!("nsURI      == %?", nsURI));
            io::println(fmt!("localPart  == %?", localPart));
        }
    }

5.0 Argument Passing, Value Returning, And Other Things Of That Ilk

Arguments are passed by value using a shallow copy, so if the argument is a composite value containing pointers then it is only a copy
of the value itself, any pointers it contains point to the same things as the original.

The same is true for return values and for assignment.

As we have already seen, if the value being passed, returned or assigned is an owned pointer then the copy becomes a move as the original is invalidated.

The use of a shallow copy for composite values means that the same is necessarily true if those values directly or indirectly contain one or more owned pointers.

For example, if we have a function defined like this

    fn start_element(name: QName)
    {
        ...
    }

and we invoke it with a stack allocated QName value as its argument like this

    let rights = QName { prefix:~"dc", nsURI:~"http://purl.org/dc/elements/1.1/", localPart:~"rights"};

    start_element(rights);
    
    ...

then after the call to start_element the local variable rights is invalid.

This is because a QName value contains three owned pointers, so when it is passed to the function using a shallow copy the ownership of those owned pointers must be transferred, so the original value must be invalidated as it is no longer usable.


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.

Programming With Rust — Part Ten: More Fun With Sockets

Currently our HTTP server is a little lacking in functionality although it is reasonably secure.

The next thing to do is to accept the incoming connection.

1.0 The accept Function

Given that there is a listen function you would expect that there would also be an accept function and indeed there is,

It is documented here and it looks like this

    fn accept(new_conn: TcpNewConnection) -> result::Result<TcpSocket, TcpErrData>

It takes a TcpNewConnection as an argument, which is handy because we get passed one of those, and it returns a

    result::Result<TcpSocket, TcpErrData>

which as we have already seen is a parameterized enum type.

In this case the value returned from a call to the accept function is going to be either

  • an instance of the Ok variant with an associated value of the type TcpSocket, or

  • an instance of the Err variant with an associated value of type TcpErrData

2.0 Working With Enums

To determine the outcome of our call to accept we will need to use a match expression.

A Rust match expression is akin to a C++ or Java switch statement but considerably more flexible than either of them.

A match expression can be used to match a value of an enum type againt its possible variants.

In the case of the value of type result::Result<TcpSocket, TcpErrData>
returned by the accept function we can do this

    match tcp::accept(newConn)
    {
        Ok(socket) => 
        {
            // connection accepted 
            // ...
        },
			   
        Err(error) => 
        {
            // whoops ! somethings gone wrong
            // ...
        }
    }

If the return value matches the Ok variant pattern then the TcpSocket value it contains is bound to the local variable socket.

Alternatively if it matches the Err variant pattern then TcpErrData value is bound to the local variable error.

3.0 Accepting The Connection

3.1 Stage One

To make it easier to show what is going in the code the two closures passed to the listen function are replaced by functions.

In this case there was nothing for them to ‘close over’ anyway so it does not really make much difference.

The three functions look like this,

    ...

    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)); 
        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);
    }

3.2 Stage Two

At this point it all gets a bit complicated.

Whilest calling the accept function is straight forward enough as is getting at the result, the documentation states

It is safe to call net::tcp::accept only within the context of the new_connect_cb callback provided as the final argument to the net::tcp::listen function.

The new_conn opaque value is provided only as the first argument to the new_connect_cb provided as a part of net::tcp::listen. It can be safely sent to another task but it must be used (via net::tcp::accept) before the new_connect_cb call it was provided to returns.

and then for good measure in the Returns section it goes on to say

On success, this function will return a net::tcp::TcpSocket as the Ok variant of a Result.
The net::tcp::TcpSocket is anchored within the task that accept was called within for its lifetime. On failure, this function will return a net::tcp::TcpErrData record as the Err variant of a Result.

So

  1. The accept function must be called before the new_connect_cb callback invoked by the listen function returns

  2. The TcpNewConnection value passed to the the new_connect_cb callback can be passed between Tasks

  3. The TcpSocket value returned from a successful call to the accept function cannot be passed between Tasks

There are two solutions to this little exercise in constraint programming, either

  • accept the connection in the new_connect_cb callback, which ensures the accept function completes before the callback returns but means the connection has to be handled in the callback as well

  • spawn a new task and accept the connection there but ensure that the new_connect_cb callback waits until the accept function has returned which will require some form of synchronization.

Since blocking the callback while we handle the connection presumably prevents any further connections being established which will make for a very serial server we will go with the second option.

4.0 Spawning A Task

We can spawn a task using the task:spawn function which is documented here and defined like this

    fn spawn(f: ~fn())

It takes an owned closure which does not take any arguments.

So to accept a connection in a new task we would need to do something like this

    ...

    task::spawn(
        ||
        {
            match tcp::accept(newConn)
            {
                Ok(socket) => 
                {
                    // ...
                },
			   
                Err(error) => 
                {
                    // ...
                }
            }
        });

    ...

5.0 Things We Need To Know Part N: The do Expression

A function which takes a closure as its last argument can also be invoked using a do expression with the closure argument appearing outside the function call.

The example of spawning a task above becomes

    ...

    do task::spawn()
        ||
        {
            match tcp::accept(newConn)
            {
                Ok(socket) => 
                {
                    // ...
                },
			   
                Err(error) => 
                {
                    // ...
                }
            }
        );

    ...

If the argument list to the function is empty it can be omitted. This is also true for the closure.

Taking advantage of this the example then becomes

    ...

    do task::spawn
        {
            match tcp::accept(newConn)
            {
                Ok(socket) => 
                {
                    // ...
                },
			   
                Err(error) => 
                {
                    // ...
                }
            }
        );

    ...

This pattern is used quite heavily so it is important to be able to recognize it when you see it.

6.0 Things We Need to Know Part N + 1: Method Invocation Very Very Briefly

Methods can be defined on most Rust types.

Methods are invoked using the dot notation, that is

    value.method()

7.0 Task Synchronization

We need to be able to wait in the new_connection_callback function until the accept function has completed in the newly spawned task.

More rummaging around in the documentation turns up the std::sync module.

This module defines the useful looking Mutex struct.

The function Mutex returns a Mutex with an associated CondVar and CondVar supports signal and wait methods.

Mutex supports the lock_cond method which locks itself and then invokes a function with its associated CondVar as its argument.

To use a Mutex/CondVar for Task synchronization, in the function new_connection_callback we need to do something like this

    ...

    let mx = Mutex();
    
    do mx.lock_cond
        |cv|
        {
            // spawn task
            // ...
            cv.wait();
        }
        
    ...

and in the spawned task we need to do something like this

    ...

    match tcp::accept(newConn)
    {
        Ok(socket) => 
        {
            // call lock_cond and signal cv
                        
        },
			   
        Err(error) => 
        {
            // call lock_cond and signal cv
        }
    }

    ...

The question is how do we get hold of the Mutex in the spawned task so that we can lock it ?

The Mutex cannot be moved irrespective of where it is allocated because it is referenced by the call to lock_cond which is not going to return until the associated CondVar is signalled.

We need something which can be moved into the spawned task and enables us to act on the original Mutex/CondVar.

Some more rummaging in the documentation turns up the Mutex clone method which looks as though it is what we want.

If in new_connection_callback we create a clone of the original Mutex in the owned heap then the owned closure passed to task::spawn can take ownership of it.

So in new_connection_callback we need to do this.

    ...

    let mx = Mutex();
    
    do mx.lock_cond
        |cv|
        {
            let mxc = ~mx.clone();
            
            // spawn task
            // ...
            cv.wait();
        }
        
    ...

and in the spawned task we can do this

    ...

    match tcp::accept(newConn)
    {
        Ok(socket) => 
        {
            do mxc.lock_cond
                |cv|
                {
                    cv.signal();
                }
        },
			   
        Err(error) => 
        {
            do mxc.lock_cond
                |cv|
                {
                    cv.signal();
                }
        }
    }

    ...

8.0 Accepting The Connection Revisited

So combining all the above we end up with this

    ...
 
    fn new_connection_callback(newConn :TcpNewConnection, chan: SharedChan<Option>)
    {
        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();
                                }
                        },
                        Err(error) => 
                        {
                            io::println("accept failed");
                            do mxc.lock_cond
                                |cv|
                                {
                                    cv.signal();
                                }
                        }
                    }
                }
                cv.wait();
            }
        fail!(~"Now what ?");
    }

9.0 Running The Code

If we run the new version of httpd and then use a web browser to connect to 127.0.0.1:3534 we get this (output slightly re-formatted for clarity)

 
    ./httpd
    on_establish_callback({x: {data: (0x10070a570 as *())}})
    new_connection_callback(NewTcpConn((0x100832c00 as *())), {x: {data: (0x10070a570 as *())}})
    accept succeeded
    rust: task failed at 'Now what ?', httpd.rc:71
    Assertion failed: (false && "Rust task failed after reentering the Rust stack"), \
        function upcall_call_shim_on_rust_stack, \
        file /Users/simon/Src/lang/rust-0.6/src/rt/rust_upcall.cpp, line 92.
    Abort trap

10.0 The Source Code For httpd v0.2

    // httpd.rc

    // v0.2

    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::sync::Mutex;

    use std::uv_iotask;

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

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


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

    fn new_connection_callback(newConn :TcpNewConnection, chan: SharedChan<Option>)
    {
        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();
                                }
                        },
                        Err(error) => 
                        {
                            io::println("accept failed");
                            do mxc.lock_cond
                                |cv|
                                {
                                    cv.signal();
                                }
                        }
                    }
                }
                cv.wait();
            }
        fail!(~"Now what ?");
    }

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

Update: 27.07.2014

The socket functions as described above no longer exist in Rust.

See here for some discussion of the equivalent functionality in the current version of Rust


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 14, 2013

Programming With Rust — Part Nine: Let’s Build A Program

1.0 Defining A Main Function

We need somewhere to call the listen function from.

For now we will call it directly from the main function of our Rust program,

There are a couple of ways to define one of these.

One way is to define a function called main which takes no arguments and does not return a value, like so


    fn main()
    {	
        tcp::listen(
            ip::v4::parse_addr("127.0.0.1"),
            5,
            3534,
            &uv_iotask::spawn_iotask(task::task()),
            |chan|
            {
                io::println(fmt!("on_establish_callback(%?)", chan));
            },
            |newConn, chan|
            {
                io::println(fmt!("new_connection_callback(%?, %?)", newConn, chan));
                fail!(~"Now what ?");
            });
    }

2.0 Building A Rust Executable

In Rust terminology an executable is a crate.

The Rust compiler takes a single crate in source form and from it produces a single crate in binary form.

The source which defines a crate is contained in a single file, the crate file.

A crate in binary form can be either an executable or a library.

2.1 Linking Against Other Crates

A crate file specifies which other crates containing libraries, if any, the crate being defined should be linked against.

Every crate is automatically linked against the crate containing the core library.

In our case we also need to link against the crate containing the std library.

We specify this dependency like this

    extern mod std;

This is an example of an extern_mod_decl which is a kind of view_item.

2.2 Name Bindings

To make use of the definitions in the externally linked crates we set up some name bindings like so

    ...

    use core::task;

    use std::net::ip;
    use std::net::tcp;

    use std::uv_iotask;
    
    ...

2.3 Compiling The Program

To compile our crate file we simply do this

    rustc httpd.rc

3.0 Running The Program

The result of the compilation is an executable called httpd.

If we just run it we get this

    ./httpd
    on_establish_callback({x: {data: (0x1007098f0 as *())}})

and the program waits for a connection.

If we run it and then use a web browser to connect to 127.0.0.1:3534 we get this (output slightly re-formatted for clarity)

    ./httpd
    on_establish_callback({x: {data: (0x10070a470 as *())}})
    new_connection_callback(NewTcpConn((0x100832c00 as *())), {x: {data: (0x10070a470 as *())}})
    rust: task failed at 'Now what ?', httpd.rc:34
    Assertion failed: (false && "Rust task failed after reentering the Rust stack"), \
        function upcall_call_shim_on_rust_stack, \
        file /Users/simon/Src/lang/rust-0.6/src/rt/rust_upcall.cpp, line 92.
    Abort trap

and the program exits.

4.0 Defining Some Simple Constants

At the moment we have a couple of integer constants sitting in the middle of the code which as everyone knows is bad, bad, bad.

We can fix this by defining them as constants like so

    ...

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

These are both examples of a static_item.

Like other items, static_items are processed at compile time and the values they define are stored in the program’s static memory.

We also have a string literal in the middle of the code.

We can define it as a constant like this

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

In fact the string literal is already a constant stored in static memory.

What we are defining here is a borrowed pointer.

The construct

   'static

is a named lifetime and it tells the compiler how long we want to borrow the pointer for.[1]

5.0 The Source Code For httpd v0.1

    // httpd.rc

    // v 0.1

    extern mod std;

    use core::task;

    use std::net::ip;
    use std::net::tcp;

    use std::uv_iotask;

    static BACKLOG: uint = 5;
    static PORT:    uint = 3534;
    
    static IPV4_LOOPBACK: &'static str = "127.0.0.1";

    fn main()
    {	
        tcp::listen(
            ip::v4::parse_addr(IPV4_LOOPBACK),
            PORT,
            BACKLOG,
            &uv_iotask::spawn_iotask(task::task()),
            |chan|
            {
                io::println(fmt!("on_establish_callback(%?)", chan));
            },
            |newConn, chan|
            {
                io::println(fmt!("new_connection_callback(%?, %?)", newConn, chan));
                fail!(~"Now what ?");
            });
    }

Notes

  1. Given the context I am not sure why the compiler cannot infer the required lifetime, but it doesn’t, so you have to tell it or it gets upset.


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.

Programming With Rust — Part Eight: Invoking The Listen Function

After all that we should now be in a position to write some code to invoke the listen function.

1.0 The Arguments

So let’s see. As arguments to the listen function we need

1.1 host_ip

We can get an IpAddress by parsing a string representing the IPv4 loopback address using the function std::net::ip::v4::parse_addr.

The parse_address function takes a borrowed pointer to a string, so for now we can hardwire the loopback address as a string literal.

1.2 port

The port number is easy. Just pick a number greater than 1024 and less than 65536 that nobody else is using.

1.3 backlog

The canonical value for the backlog argument to any listen function in any programming language is 5.[1]

1.4 iotask

IoTasks seem to be in fairly short supply.

The only way to obtain one seems to be to call the function std::uv_iotask::spawn_iotask.

Not sure what the implications of doing this are. The documentation for that particular function is less than forthcoming.

We will just have to do it and see what happens.

1.5 on_establish_cb And new_connect_cb

We can supply lambda expressions for the two callbacks. This will automatically result in the creation of the necessary owned closures.

2.0 The Return Type

The return type of the listen function is defined to be

    result::Result<(), TcpListenErrData>

The Result type is defined in the module result like this,

    pub enum Result<T, U> {
        /// Contains the successful result value
        Ok(T),
        /// Contains the error value
        Err(U)
    }

so it is a generic enum type.

The value returned from a call to the listen function is going to be either

  • an instance of the Ok variant with an associated value of the unit type, that is, nothing, or

  • an instance of the Err variant with an associated value of type TcpListenErrData

3.0 The Code

What all that looks like in practice is this

    ...

    tcp::listen(
        ip::v4::parse_addr("127.0.0.1"),
        3534,
        5,
        &uv_iotask::spawn_iotask(task::task()),
        |chan|
        {
            io::println(fmt!("on_establish_callback(%?)", chan));
        },
        |newConn, chan|
        {
            io::println(fmt!("new_connection_callback(%?, %?)", newConn, chan));
            fail!(~"Now what ?");
        });

    ...

The function io:println takes a borrowed pointer to a string and prints the string to stdout followed by a newline.

fmt! is like sprintf and fail! stops the Task.

For the moment we are going to ignore the return value.

Notes

  1. Its a tradition, or an old charter, or something.

Update: 27.07.2014

The socket functions as described above no longer exist in Rust.

See here for some discussion of the equivalent functionality in the current version of Rust


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.

Older Posts »

Blog at WordPress.com.

%d bloggers like this: