Open SuperHacker-liuan opened 7 years ago
I believe the vast majority of this is possible today by taking advantage of launch fairings for each sub-application. Let me walk through a tiny example of what I have in mind with sub-applications dashboard
and home
.
First, we create a Cargo workspace. Each sub-application has its own crate with both a lib
and bin
. We also create one more crate with only a bin
to combine our sub applications.
We write each sub-application as if it was a regular Rocket application with one caveat: all configuration is done via a function that takes an instance of Rocket
and some arbitrary Config
structure and returns an instance of Result<Rocket, Rocket>
. That function is then used in main
. Each sub-application would thus look something like:
extern crate rocket;
use rocket::Rocket;
use rocket::fairing::AdHoc;
#[get("/hello")]
fn index() -> &'static str {
"Hello, world!"
}
// I use `mountpoint` here for illustration, but the idea is you'd pass any sub-app
// specific config like this.
pub fn app(rocket: Rocket, mountpoint: &str) -> Result<Rocket, Rocket> {
Ok(rocket.mount(mountpoint, routes![index]))
}
fn main() {
rocket::ignite()
.attach(AdHoc::on_attach(|rocket| app(rocket, "/")))
.launch();
}
Each sub-application is attached in the main application. As long as all apps use different types for their managed state (Rocket will error otherwise, so no concern of accidental collision is present), everything will work as expected. This would look like:
extern crate dashboard;
extern crate home;
fn main() {
rocket::ignite()
.attach(AdHoc::on_attach(|rocket| dashboard::app(rocket, "/dashboard")))
.attach(AdHoc::on_attach(|rocket| home::app(rocket, "/app")))
.launch();
}
I believe this accomplishes most of what you're looking for. The only missing feature from your request is a Redirect
that's aware of sub-applications, though I'm not sure I understand what you're really asking for there. In any case, it seems that it would be pretty simple to build outside of Rocket, given how I've laid out the structure here.
In all, I don't think anything needs to change in Rocket to support what you'd like. It seems to me that launch fairings give you 95% of what you're looking for, and the other 5% could be a tiny set of types, functions, and methods that make things slightly nicer to use. All of those things could live outside of Rocket.
@SergioBenitez
Below is /workspace/apps/home/src/lib.rs
, which is a sub-app.
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
extern crate rocket_contrib;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
use std::path::{Path, PathBuf};
use rocket::Rocket;
use rocket::response::{NamedFile, Redirect};
use rocket_contrib::Template;
#[get("/<file..>")]
fn statics(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("statics/").join(file)).ok()
}
#[get("/")]
fn works() -> &'static str {
"It works!"
}
#[derive(Serialize)]
struct TemplateContext {
name: String,
}
#[get("/template")]
fn template() -> Template {
let context = TemplateContext {
name: "template works!".to_string(),
};
Template::render("template", &context)
}
#[get("/redirect")]
fn redirect() -> Redirect {
Redirect::to("/")
}
pub fn deploy(rocket: Rocket, mountpoint: &str) -> Result<Rocket, Rocket> {
let rocket = rocket.mount(mountpoint, routes![works, statics, template, redirect])
.attach(Template::fairing());
Ok(rocket)
}
If which code run in standalone mode with mode.
// /workspace/apps/home/src/main.rs
extern crate rocket;
extern crate home;
use rocket::fairing::AdHoc;
fn main() {
rocket::ignite()
.attach(AdHoc::on_attach(|r| home::deploy(r, "/")))
.launch();
}
All test will be passed.
http://localhost:8000/ -> It works!
http://localhost:8000/redirect -> redirect to http://localhost:8000/
http://localhost:8000/somefile.txt -> get /workspace/apps/home/statics/somefile.txt
http://localhost:8000/template -> render /workspace/apps/home/templates.html.hbs
If I run it in deploy mode, cargo run
in /workspace/server/
// /workspace/server/src/main.rs
extern crate rocket;
extern crate home;
use rocket::fairing::AdHoc;
fn main() {
rocket::ignite()
.attach(AdHoc::on_attach(|rocket| home::deploy(rocket, "/home")))
.catch(errors::errors())
.launch();
}
Test result is: http://localhost:8000/home/ -> It works! http://localhost:8000/home/redirect -> redirect to http://localhost:8000/ Failed, actually it should redirect to http://localhost:8000/home/ http://localhost:8000/home/somefile.txt -> 404 Not Found http://localhost:8000/template -> 500 Internal Error
It means, one app has different behaviours in standalone(for developer team) mode and deploy(for production use) mode. We cannot deploy home
app in out of box mode. Developers have to use conditions everywhere in home
source code to deal with these two scenes.
More worth is. If boss decide to reuse home
app in another site, and deploy it at http://xxx.com/user, developer had to add third condition to adapt which requirement.
Some day, boss decide to make home
open source. Users around the world will deploy it in their own sites, but the app base will be named as room/
, center/
, etc./
, all of them have to change source code in hunders of thouands lines, which is horrible.
In standalone mode, Redirect::to("/")
means redirect to http://host.domain/
In deploy mode, the site base changed http://host.domain/home, but Redirect::to("/")
still redirct Location to http://host.domain/, which means Redirect
provide a redirect to the absolute path of the site.
In my opinion, to avoid path confusion in different deploy path(which happens when reused by different projects), developers should use relative path, other than absolute path.
I have just try, I can write Redirect::("./")
to fix this bug, I failed to comprehent semantic of Redirect::to()
in redirect
examples
I suggest to use relative redirect in redirect
example
When run in deploy mode, the working directory(WD) is /workspace/server/
, so the Path::new("statics/").join(file)
will be /workspace/server/statics/${file}
, rather than /workspace/apps/home/statics/${file}
.
It means, rocket lacks of a built-in context root(CR) setting. Every depeloper have to implement their own CR handling.
Same as above, built-in Template root path is ${WD}/${template_dir}, rather than ${CR}/${app_template_dir}. So Template render cannot found where the hbs file is, Cause a 500 Internal Error.
Even if developers have been implemented the CR handler, Template will still ignore it, unless developer change the rocket_contrib::Template
's implementation.
Rockt provides a standard(struct, API and etc.) to set the CR.
Avoid using std::path::Path
in apps, unless they need to access OS resources.
Comprehent WD and the relative path to WD is a almost impossible for many outsourcing developers. Especially developers from javaweb. They don't know what's the difference between middleware WD and app CR.
Make template render depends on CR API, other than middleware WD.
Provide specifications describing the standard(i.e. Fairings must depend on CR) to develop fairings for rocket middleware when Rocket/1.0 release. Just like Java servlet specification. This can avoid chaos from framwork libraries developer. Its an important step to make rocket ready for enterprise use. We know, Struts, Spring are all depends on java servlet. java servlet and interceptors, which are the standard to develop java web frameworks.
Maybe you can provide an example for how to do sub-app deploy on https://rocket.rs/ guide.
Another important feature is Context Isolating. Let's see what actually rocket do in deploy mode by using faring.
// /workspace/server/src/main.rs
rocket::ignite()
.attach(AdHoc::on_attach(|rocket| home::deploy(rocket, "/home")))
.catch(errors())
.launch();
// /workspcae/apps/home/src/lib.rs
let rocket = rocket.mount(mountpoint, routes![works, statics, template, redirect])
.attach(Template::fairing());
Ok(rocket)
// It means
rocket::ignite()
.mount("/home", routes![works, statics, template, redirect])
.attach(Template::fairing())
.catch(errors())
.launch();
apps/home
and server
share the same context. If I have multiple app, each need to attach its own framework fairings, their context will be polluted by each other.
What I really need in SPMA support is isolate the context (private configs / statics / templates / counters / session manager), and share the basic resource (connection pool / HTTP eventloop / HTTP port / error pages / access_logs).
Feature Requests
1. Why you believe this feature is necessary.
Single Port Multi-App (SPMA) is an important enterprise feature. For a large project in large enterprise, it is often need to split whole project into multiple sub-projects, each subproject will subcontract to another developing team. Each subproject has its own static files, templates, source codes, dependencies and etc, it act as an independent app, and provides some communication interfaces to other apps. The whole project will be a batch of apps work together with inter-app communication. Every app may be accessed from end-user, therefore they should share a same IP/hostname and HTTP port 80. Consider JavaEE supported by tomcat, A large site may be in the following directory structure.
Architecture and data in tomcat is as below.
Every app has its own data, static files, services, libs, pools(like redis) and etc. And they can also share a common used DBPool (like Oracle) in middle ware. Resource from middle ware can be accessed from each app, rosource from app are isolated to its own.
In this architecture, enterprise architectures can easily split whole project into multiple isolated apps, which can reduce a lot of cost and can prevent faliure cascade between apps.
2. A convincing use-case for this feature.
2.1 workspace style
A large project with many apps may be formed as a workspace. The directory structrue of the workspace may be as below.
In this style, architecture can collect apps from subcontract team, put them in
apps/
folder, and write some deploy code inserver/src/
orserver/Deploy.toml
. Finally runcargo build --release
inserver/
dir to launch the project. The launce code maybe in following style.The
app::context()
should mount Routes used by app own, and optional provide error catchers, farings and etc. It app::context() looks like aRocket::ignite()
, except it won'tlaunch()
.ContextRoot() need two important information, web base url (app0 means https://xxx.xxx/app0/), and app resource files' path (relative to server dir or absolute path in filesystem). Respath is important in finding statics, templates, configs and etc. Base url is useful in Cookie path and finding root(Redirect) path in app. Apps’ Redirect should have two type, they should explicit distingushed. Like below.
This design is important to avoid path confusion when move app to other sites. This feature should be confirmed before rocket 1.0 release.
3. Why this feature can't or shouldn't exist outside of Rocket.
Rocket provides a whole solution to deploy a http server, and willing to become a middleware (From Fairings guide ). So the SPMA feature, which is heavily use in enterprise projects, should not lacked. Every app should provide a way to run in standalone mode(currently supported) and deploy mode(support by SPMA). Deploy mode is useful in many scence such as virtual host providers (they may not provides many ports to launch, therefore deploy mode is necessary). In my comprehention, Rocket will act as a middleware like tomcat. Features currently implemented (0.3.2) is standard like java servlet. Some commercial Rocket container can be distributed based on Rocket, more and more frameworks can be used, apps can be compiled into a dynlilb, and dynamically deployed into the Rocket containers(dynamic deploy maybe version 2.0 feature)