blob: 665a8dbf13422edfdb3e5284dcb77340764cad7e [file] [log] [blame]
// 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()
)
}