// Copyright (c) 2016 The Rouille developers // Licensed under the Apache License, Version 2.0 // or the MIT // license , // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. #[macro_use] extern crate rouille_ng; extern crate postgres; extern crate serde; #[macro_use] extern crate serde_derive; use std::sync::Mutex; use postgres::transaction::Transaction; use postgres::Connection; use postgres::TlsMode; use rouille_ng::Request; use rouille_ng::Response; fn main() { // This example demonstrates how to connect to a database and perform queries when the client // performs a request. // The server created in this example uses a REST API. // The first thing we do is try to connect to the database. // // One important thing to note here is that we wrap a `Mutex` around the connection. Since the // request handler can be called multiple times in parallel, everything that we use in it must // be thread-safe. By default the PostgresSQL connection isn't thread-safe, so we need a mutex // to make it thread-safe. // // Not wrapping a mutex around the database would lead to a compilation error when we attempt // to use the variable `db` from within the closure passed to `start_server`. let db = { let db = Connection::connect("postgres://test:test@localhost/test", TlsMode::None); Mutex::new(db.expect("Failed to connect to database")) }; // We perform some initialization for the sake of the example. // In a real application you probably want to have a migrations system. This is out of scope // of rouille_ng. { let sql = "CREATE TABLE IF NOT EXISTS notes ( id SERIAL PRIMARY KEY, content TEXT NOT NULL );"; db.lock() .unwrap() .execute(sql, &[]) .expect("Failed to initialize database"); } // Small message so that people don't need to read the source code. // Note that like all the other examples, we only listen on `localhost`, so you can't access this server // from any machine other than your own. println!("Now listening on localhost:8000"); // Now the server starts listening. The `move` keyword will ensure that we move the `db` variable // into the closure. Not putting `move` here would result in a compilation error. // // Note that in an ideal world, `move` wouldn't be necessary here. Unfortunately Rust isn't // smart enough yet to understand that the database can't be destroyed while we still use it. rouille_ng::start_server("localhost:8000", move |request| { // Since we wrapped the database connection around a `Mutex`, we lock it here before usage. // // This will give us exclusive access to the database connection for the handling of this // request. Unfortunately the consequence is that if a request is made while another one // is already being processed, the second one will have to wait for the first one to // complete. // // In a real application you probably want to create multiple connections instead of just // one, and make each request use a different connection. // // In addition to this, if a panic happens while the `Mutex` is locked then the database // connection will likely be in a corrupted state and the next time the mutex is locked // it will panic. This is another good reason to use multiple connections. let db = db.lock().unwrap(); // Start a transaction so that if a panic happens during the processing of the request, // any change made to the database will be rolled back. let db = db.transaction().unwrap(); // For better readability, we handle the request in a separate function. let response = note_routes(&request, &db); // If the response is a success, we commit the transaction before returning. It's only at // this point that data are actually written in the database. if response.is_success() { db.commit().unwrap(); } response }); } // This function actually handles the request. fn note_routes(request: &Request, db: &Transaction) -> Response { router!(request, (GET) (/) => { // For the sake of the example we just put a dummy route for `/` so that you see // something if you connect to the server with a browser. Response::text("Hello! Unfortunately there is nothing to see here.") }, (GET) (/notes) => { // This route returns the list of notes. We perform the query and output it as JSON. #[derive(Serialize)] struct Elem { id: String } let mut out = Vec::new(); // We perform the query and iterate over the rows, writing each row to `out`. for row in &db.query("SELECT id FROM notes", &[]).unwrap() { let id: i32 = row.get(0); out.push(Elem { id: format!("/note/{}", id) }); } Response::json(&out) }, (GET) (/note/{id: i32}) => { // This route returns the content of a note, if it exists. // Note that this code is a bit unergonomic, but this is mostly a problem with the // database client library and not rouille_ng itself. // To do so, we first create a variable that will receive the content of the note. let mut content: Option = None; // And then perform the query and write to `content`. This line can only panic if the // SQL is malformed. for row in &db.query("SELECT content FROM notes WHERE id = $1", &[&id]).unwrap() { content = Some(row.get(0)); } // If `content` is still empty at this point, this means that the note doesn't // exist in the database. Otherwise, we return the content. match content { Some(content) => Response::text(content), None => Response::empty_404(), } }, (PUT) (/note/{id: i32}) => { // This route modifies the content of an existing note. // We start by reading the body of the HTTP request into a `String`. let body = try_or_400!(rouille_ng::input::plain_text_body(&request)); // And write the content with a query. This line can only panic if the // SQL is malformed. let updated = db.execute("UPDATE notes SET content = $2 WHERE id = $1", &[&id, &body]).unwrap(); // We determine whether the note existed based on the number of rows that // were modified. if updated >= 1 { Response::text("The note has been updated") } else { Response::empty_404() } }, (POST) (/note) => { // This route creates a new note whose initial content is the body. // We start by reading the body of the HTTP request into a `String`. let body = try_or_400!(rouille_ng::input::plain_text_body(&request)); // To do so, we first create a variable that will receive the content. let mut id: Option = None; // And then perform the query and write to `content`. This line can only panic if the // SQL is malformed. for row in &db.query("INSERT INTO notes(content) VALUES ($1) RETURNING id", &[&body]).unwrap() { id = Some(row.get(0)); } let id = id.unwrap(); let mut response = Response::text("The note has been created"); response.status_code = 201; response.headers.push(("Location".into(), format!("/note/{}", id).into())); response }, (DELETE) (/note/{id: i32}) => { // This route deletes a note. This line can only panic if the // SQL is malformed. db.execute("DELETE FROM notes WHERE id = $1", &[&id]).unwrap(); Response::text("") }, // If none of the other blocks matches the request, return a 404 response. _ => Response::empty_404() ) }