1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// 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_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<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_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<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()
)
}