Skip to content

Commit

Permalink
Merge pull request #732 from loco-rs/fallback
Browse files Browse the repository at this point in the history
impl: fallback behavior
  • Loading branch information
kaplanelad authored Sep 5, 2024
2 parents 86d3e06 + 2fd465b commit f4a631b
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/e2e-cli-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
cargo install --path .
working-directory: ./loco-cli
- run: |
loco new -n saas -t saas --db sqlite --bg async --assets none
loco new -n saas -t saas --db sqlite --bg async --assets serverside
env:
ALLOW_IN_GIT_REPO: true
- run: |
Expand Down
37 changes: 37 additions & 0 deletions docs-site/content/docs/the-app/controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,43 @@ async fn current(
}
```

## Fallback

When choosing the SaaS starter (or any starter that is not API-first), you get a default fallback behavior with the _Loco welcome screen_. This is a development-only mode where a `404` request shows you a nice and friendly page that tells you what happened and what to do next.


You can disable or customize this behavior in your `development.yaml` file. You can set a few options:


```yaml
# the default pre-baked welcome screen
fallback:
enable: true
```
```yaml
# a different predefined 404 page
fallback:
enable: true
file: assets/404.html
```
```yaml
# a message, and customizing the status code to return 200 instead of 404
fallback:
enable: true
code: 200
not_found: cannot find this resource
```
For production, it's recommended to disable this.
```yaml
# disable. you can also remove the `fallback` section entirely to disable
fallback:
enable: false
```
## Remote IP
When your app is under a proxy or a load balancer (e.g. Nginx, ELB, etc.), it does not face the internet directly, which is why if you want to find out the connecting client IP, you'll get a socket which indicates an IP that is actually your load balancer instead.
Expand Down
6 changes: 6 additions & 0 deletions loco-cli/tests/cmd/starters/generate-starters.trycmd
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ $ ALLOW_IN_GIT_REPO="" LOCO_APP_NAME="test_saas_template" LOCO_FOLDER_NAME=saas_
🚂 Loco app generated successfully in:
[CWD]/test_saas_template

- database: You've selected `postgres` as your DB provider (you should have a postgres instance to connect to)

```

```console
Expand All @@ -12,6 +14,8 @@ $ ALLOW_IN_GIT_REPO="true" LOCO_APP_NAME="test_rest_api_template" LOCO_FOLDER_NA
🚂 Loco app generated successfully in:
[CWD]/test_rest_api_template



```

```console
Expand All @@ -20,4 +24,6 @@ $ ALLOW_IN_GIT_REPO="true" LOCO_APP_NAME="test_lightweight_template" LOCO_FOLDER
🚂 Loco app generated successfully in:
[CWD]/test_lightweight_template



```
17 changes: 17 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,25 @@ pub struct Middlewares {
pub secure_headers: Option<SecureHeadersConfig>,
/// Calculates a remote IP based on `X-Forwarded-For` when behind a proxy
pub remote_ip: Option<RemoteIPConfig>,
/// Configure fallback behavior when hitting a missing URL
pub fallback: Option<FallbackConfig>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FallbackConfig {
/// By default when enabled, returns a prebaked 404 not found page optimized
/// for development. For production set something else (see fields below)
pub enable: bool,
/// For the unlikely reason to return something different than `404`, you
/// can set it here
pub code: Option<u16>,
/// Returns content from a file pointed to by this field with a `404` status
/// code.
pub file: Option<String>,
/// Returns a "404 not found" with a single message string. This sets the
/// message.
pub not_found: Option<String>,
}
/// Static asset middleware configuration
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StaticAssetsMiddleware {
Expand Down
47 changes: 44 additions & 3 deletions src/controller/app_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
use std::{fmt, path::PathBuf, time::Duration};

use axum::{http, response::IntoResponse, Router as AXRouter};
use axum::{
http,
response::{Html, IntoResponse},
Router as AXRouter,
};
use hyper::StatusCode;
use lazy_static::lazy_static;
use regex::Regex;
use tower_http::{
Expand All @@ -25,14 +30,14 @@ use super::{
};
use crate::{
app::AppContext,
config,
config::{self, FallbackConfig},
controller::middleware::{
etag::EtagLayer,
remote_ip::RemoteIPLayer,
request_id::{request_id_middleware, LocoRequestId},
},
environment::Environment,
errors, Result,
errors, Error, Result,
};

lazy_static! {
Expand Down Expand Up @@ -297,6 +302,12 @@ impl AppRoutes {
tracing::info!("[Middleware] +secure headers");
}

if let Some(fallback) = &ctx.config.server.middlewares.fallback {
if fallback.enable {
app = Self::add_fallback(app, fallback)?;
}
}

app = Self::add_powered_by_header(app, &ctx.config.server);

app = Self::add_request_id_middleware(app);
Expand All @@ -305,6 +316,36 @@ impl AppRoutes {
Ok(router)
}

fn add_fallback(
app: AXRouter<AppContext>,
fallback: &FallbackConfig,
) -> Result<AXRouter<AppContext>> {
let app = if let Some(path) = &fallback.file {
app.fallback_service(ServeFile::new(path))
} else if let Some(not_found) = &fallback.not_found {
let not_found = not_found.to_string();
let code = fallback
.code
.map(StatusCode::from_u16)
.transpose()
.map_err(|e| Error::Message(format!("{e}")))?
.unwrap_or(StatusCode::NOT_FOUND);
app.fallback(move || async move { (code, not_found) })
} else {
//app.fallback(handler)
let code = fallback
.code
.map(StatusCode::from_u16)
.transpose()
.map_err(|e| Error::Message(format!("{e}")))?
.unwrap_or(StatusCode::NOT_FOUND);
let content = include_str!("fallback.html");
app.fallback(move || async move { (code, Html(content)) })
};
tracing::info!("[Middleware] +fallback");
Ok(app)
}

fn add_request_id_middleware(app: AXRouter<AppContext>) -> AXRouter<AppContext> {
let app = app.layer(axum::middleware::from_fn(request_id_middleware));
tracing::info!("[Middleware] +request id");
Expand Down
Loading

0 comments on commit f4a631b

Please sign in to comment.