// Copyright (c) 2016 The Rouille developers | |
// Licensed under the Apache License, Version 2.0 | |
// <LICENSE-APACHE or | |
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT | |
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, | |
// 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; | |
extern crate postgres; | |
extern crate serde; | |
#[macro_use] | |
extern crate serde_derive; | |
use std::sync::Mutex; | |
use postgres::Connection; | |
use postgres::TlsMode; | |
use postgres::transaction::Transaction; | |
use rouille::Request; | |
use rouille::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. | |
{ | |
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::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 itself. | |
// To do so, we first create a variable that will receive the content of the note. | |
let mut content: Option<String> = 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::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::input::plain_text_body(&request)); | |
// To do so, we first create a variable that will receive the content. | |
let mut id: Option<i32> = 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() | |
) | |
} |