Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impl: fallback behavior #732

Merged
merged 3 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading