Skip to content

Commit ff44046

Browse files
authored
Merge pull request #97 from w-henderson/hot-reload
Created Hot Reload plugin for Server
2 parents 126ee5f + 3b1a572 commit ff44046

File tree

18 files changed

+611
-20
lines changed

18 files changed

+611
-20
lines changed

.github/workflows/plugins.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
on:
2+
push:
3+
paths:
4+
- "plugins/**"
5+
- ".github/**"
6+
7+
name: Plugins
8+
9+
jobs:
10+
check:
11+
name: Check Plugins
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout sources
15+
uses: actions/checkout@v2
16+
17+
- name: Install stable toolchain
18+
uses: actions-rs/toolchain@v1
19+
with:
20+
profile: minimal
21+
toolchain: stable
22+
override: true
23+
24+
- name: Check PHP plugin
25+
uses: actions-rs/cargo@v1
26+
with:
27+
command: check
28+
args: --manifest-path plugins/php/Cargo.toml
29+
30+
- name: Check Hot Reload plugin
31+
uses: actions-rs/cargo@v1
32+
with:
33+
command: check
34+
args: --manifest-path plugins/hot-reload/Cargo.toml

build.bat

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
:: - Linux with all features (TLS and plugins)
77
:: - PHP plugin for Windows
88
:: - PHP plugin for Linux
9+
:: - Hot Reload plugin for Windows
10+
:: - Hot Reload plugin for Linux
911
::
1012
:: Requires Rust to be installed both normally and in WSL.
1113

@@ -55,6 +57,20 @@ robocopy target/release ../../dist libphp.so > nul
5557
cd ../../dist
5658
rename libphp.so php_plugin_linux.so
5759

60+
echo Building Hot Reload plugin for Windows...
61+
cd ../plugins/hot-reload
62+
cargo build --release -q
63+
robocopy target/release ../../dist hot_reload.dll > nul
64+
cd ../../dist
65+
rename hot_reload.dll hot_reload_plugin_windows.dll
66+
67+
echo Building Hot Reload plugin for Linux...
68+
cd ../plugins/hot-reload
69+
wsl $HOME/.cargo/bin/cargo build --release -q
70+
robocopy target/release ../../dist libhot_reload.so > nul
71+
cd ../../dist
72+
rename libhot_reload.so hot_reload_plugin_linux.so
73+
5874
cd ..
5975

6076
echo Build complete.

docs/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- [Configuration](server/configuration.md)
1414
- [Using PHP](server/using-php.md)
1515
- [Using HTTPS](server/https.md)
16+
- [Using Hot Reload](server/hot-reload.md)
1617
- [Creating a Plugin](server/creating-a-plugin.md)
1718
- [Humphrey WebSocket](websocket/index.md)
1819
- [Synchronous](websocket/sync/index.md)

docs/src/introduction.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ This book is up-to-date with the following crate versions.
2727
| Crate | Version |
2828
| ----- | ------- |
2929
| Humphrey Core | 0.6.0 |
30-
| Humphrey Server | 0.5.0 |
30+
| Humphrey Server | 0.6.0 |
3131
| Humphrey WebSocket | 0.4.0 |
3232
| Humphrey JSON | 0.2.0 |
3333
| Humphrey Auth | 0.1.3 |

docs/src/server/hot-reload.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Hot Reload
2+
Humphrey supports hot reload through a first-party plugin, provided that the server was compiled with the `plugins` feature enabled and the plugin is installed.
3+
4+
The Hot Reload plugin is able to automatically reload webpages when the source code changes. It is not recommended for use in production, but is useful for development. It should also be noted that, when using a front-end framework such as React, the framework's built-in HMR (hot module reloading) functionality should be used instead of this plugin.
5+
6+
HTML pages are reloaded by requesting the updated page through a `fetch` call, then writing this to the page. This avoids the need for the page to be reloaded manually. CSS and JavaScript are reloaded by requesting the updated data, then replacing the old script or stylesheet. Images are reloaded in the same way. Other resources are currently unable to be dynamically reloaded.
7+
8+
When JavaScript is reloaded, the updated script will be executed upon load in the same context as the old script. This means that any `const` declarations may cause errors, but this is unavoidable as without executing the new script, none of the changes can be used. For this reason, the Hot Reload plugin is more suitable for design changes than for functionality changes.
9+
10+
**Warning:** Hot Reload disables caching so that changes are immediately visible.
11+
12+
## Configuration
13+
In the plugins section of the configuration file, add the following:
14+
15+
```conf
16+
hot-reload {
17+
library "path/to/hot-reload.dll" # Path to the compiled library
18+
ws_route "/ws" # Route to the WebSocket endpoint
19+
}
20+
```
21+
22+
Specifying the WebSocket route is optional. If not specified, the default is `/__hot-reload-ws` in order to avoid conflicts with other configured WebSocket endpoints.

examples/plugin/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2018"
88
[dependencies]
99
humphrey = { path = "../../humphrey" }
1010
humphrey_server = { path = "../../humphrey-server", features = ["plugins"] }
11+
humphrey_ws = { path = "../../humphrey-ws" }
1112

1213
[lib]
1314
crate-type = ["cdylib", "rlib"]

examples/plugin/src/lib.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use humphrey::http::headers::HeaderType;
22
use humphrey::http::{Request, Response, StatusCode};
3+
use humphrey::stream::Stream;
34

45
use humphrey_server::config::RouteConfig;
56
use humphrey_server::declare_plugin;
67
use humphrey_server::plugins::plugin::Plugin;
78
use humphrey_server::server::server::AppState;
89

10+
use humphrey_ws::{websocket_handler, Message, WebsocketStream};
11+
912
use std::sync::Arc;
1013

1114
#[derive(Debug, Default)]
@@ -41,7 +44,37 @@ impl Plugin for ExamplePlugin {
4144
None
4245
}
4346

44-
fn on_response(&self, response: &mut Response, state: Arc<AppState>) {
47+
fn on_websocket_request(
48+
&self,
49+
request: &mut Request,
50+
stream: Stream,
51+
state: Arc<AppState>,
52+
_: Option<&RouteConfig>,
53+
) -> Option<Stream> {
54+
state.logger.info(&format!(
55+
"Example plugin read a WebSocket request from {}",
56+
request.address
57+
));
58+
59+
// If the requested resource is "/override" then override the response (which would ordinarily be closing the WebSocket connection). For this example, we'll just complete the WebSocket handshake and send a message back to the client.
60+
if &request.uri == "/override" {
61+
state
62+
.logger
63+
.info("Example plugin overrode a WebSocket connection");
64+
65+
websocket_handler(|mut stream: WebsocketStream, _| {
66+
stream
67+
.send(Message::new(b"Response overridden by example plugin :)"))
68+
.ok();
69+
})(request.clone(), stream, state);
70+
71+
return None;
72+
}
73+
74+
Some(stream)
75+
}
76+
77+
fn on_response(&self, response: &mut Response, state: Arc<AppState>, _: &RouteConfig) {
4578
// Insert a header to the response
4679
response.headers.add("X-Example-Plugin", "true");
4780

humphrey-server/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "humphrey_server"
3-
version = "0.5.2"
3+
version = "0.6.0"
44
edition = "2018"
55
license = "MIT"
66
homepage = "https://github.com/w-henderson/Humphrey"

humphrey-server/src/config/config.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub struct HostConfig {
5757
}
5858

5959
/// Represents the type of a route.
60-
#[derive(Copy, Clone, Debug, PartialEq)]
60+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
6161
pub enum RouteType {
6262
/// Serve a single file.
6363
File,
@@ -67,6 +67,8 @@ pub enum RouteType {
6767
Proxy,
6868
/// Redirect clients to another server.
6969
Redirect,
70+
/// Proxies WebSocket requests to this route to another server.
71+
ExclusiveWebSocket,
7072
}
7173

7274
/// Represents configuration for a specific route.
@@ -506,6 +508,14 @@ fn parse_route(
506508
});
507509
} else if !conf.contains_key("websocket") {
508510
return Err("Invalid route configuration, every route must contain either the `file`, `directory`, `proxy` or `redirect` field, unless it defines a WebSocket proxy with the `websocket` field");
511+
} else {
512+
routes.push(RouteConfig {
513+
route_type: RouteType::ExclusiveWebSocket,
514+
matches: wild.to_string(),
515+
path: None,
516+
load_balancer: None,
517+
websocket_proxy,
518+
});
509519
}
510520
}
511521

humphrey-server/src/plugins/manager.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::config::RouteConfig;
66
use crate::plugins::plugin::{Plugin, PluginLoadResult};
77
use crate::server::server::AppState;
88
use humphrey::http::{Request, Response};
9+
use humphrey::stream::Stream;
910

1011
use libloading::Library;
1112
use std::collections::HashMap;
@@ -84,10 +85,32 @@ impl PluginManager {
8485
None
8586
}
8687

88+
/// Calls the `on_websocket_request` function on every plugin.
89+
/// If a plugin handles the stream, the function immediately returns.
90+
pub fn on_websocket_request(
91+
&self,
92+
request: &mut Request,
93+
mut stream: Stream,
94+
state: Arc<AppState>,
95+
route: Option<&RouteConfig>,
96+
) -> Option<Stream> {
97+
for plugin in &self.plugins {
98+
if let Some(returned_stream) =
99+
plugin.on_websocket_request(request, stream, state.clone(), route)
100+
{
101+
stream = returned_stream;
102+
} else {
103+
return None;
104+
}
105+
}
106+
107+
Some(stream)
108+
}
109+
87110
/// Calls the `on_response` function on every plugin.
88-
pub fn on_response(&self, response: &mut Response, state: Arc<AppState>) {
111+
pub fn on_response(&self, response: &mut Response, state: Arc<AppState>, route: &RouteConfig) {
89112
for plugin in &self.plugins {
90-
plugin.on_response(response, state.clone());
113+
plugin.on_response(response, state.clone(), route);
91114
}
92115
}
93116

humphrey-server/src/plugins/plugin.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use crate::config::RouteConfig;
99
use crate::server::server::AppState;
1010
use humphrey::http::{Request, Response};
11+
use humphrey::stream::Stream;
1112

1213
use std::any::Any;
1314
use std::collections::HashMap;
@@ -46,9 +47,23 @@ pub trait Plugin: Any + Send + Sync + Debug {
4647
None
4748
}
4849

50+
/// Called when a WebSocket request is received but before it is processed. May modify the request in-place.
51+
/// Unlike `on_request`, this method should return `None` if the WebSocket request is being handled by the plugin, and should return the stream back to Humphrey if the request is not being handled by the plugin.
52+
///
53+
/// **Important:** If the plugin returns `Some(stream)`, it must not modify or close the stream.
54+
fn on_websocket_request(
55+
&self,
56+
request: &mut Request,
57+
stream: Stream,
58+
state: Arc<AppState>,
59+
route: Option<&RouteConfig>,
60+
) -> Option<Stream> {
61+
Some(stream)
62+
}
63+
4964
/// Called when a response has been generated but not yet sent.
5065
/// May modify the response in-place.
51-
fn on_response(&self, response: &mut Response, state: Arc<AppState>) {}
66+
fn on_response(&self, response: &mut Response, state: Arc<AppState>, route: &RouteConfig) {}
5267

5368
/// Called when the plugin is about to be unloaded.
5469
/// Any clean-up should be done here.

0 commit comments

Comments
 (0)