Skip to content
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
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,10 @@ Ohkami::new((
```

`.howls()` (`tls` feature only) is used to run Ohkami with TLS (HTTPS) support
with [`rustls`](https://github.com/rustls) ecosystem (described in `tls` feature section).
with `tokio` and [`rustls`](https://github.com/rustls) ecosystem
(currently `rt_tokio` only / described in `tls` feature section).

`howl(s)` supports graceful shutdown by `Ctrl-C` or `SIGTERM` signal on native runtimes.
`howl(s)` supports graceful shutdown by `Ctrl-C` ( `SIGINT` ) on native runtimes.

<br>

Expand Down Expand Up @@ -309,9 +310,6 @@ async fn main() {

### `"ws"` : WebSocket

Ohkami only handles `ws://`.\
Use some reverse proxy to do with `wss://`.

```rust,no_run
use ohkami::{Ohkami, Route};
use ohkami::ws::{WebSocketContext, WebSocket, Message};
Expand Down Expand Up @@ -443,7 +441,7 @@ $ openssl req -x509 -newkey rsa:4096 -nodes -keyout server.key -out server.crt -
[dependencies]
ohkami = { version = "0.24", features = ["rt_tokio", "tls"] }
tokio = { version = "1", features = ["full"] }
rustls = { version = "0.22", features = ["ring"] }
rustls = { version = "0.23", features = ["ring"] }
rustls-pemfile = "2.2"
```

Expand Down
1 change: 1 addition & 0 deletions examples/websocket/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/*.pem
7 changes: 5 additions & 2 deletions examples/websocket/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ version = "0.1.0"
edition = "2024"

[dependencies]
ohkami = { workspace = true }
tokio = { workspace = true }
ohkami = { workspace = true }
tokio = { workspace = true }
rustls = { optional = true, version = "0.23", features = ["ring"] }
rustls-pemfile = { optional = true, version = "2.2" }

[features]
tls = ["ohkami/tls", "dep:rustls", "dep:rustls-pemfile"]
DEBUG = ["ohkami/DEBUG"]
16 changes: 16 additions & 0 deletions examples/websocket/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# WebSocket Example

## Feature flags description

- `DEBUG`: Enables Ohkami's debug logging.
- `tls`: Enables TLS support ( https://, wss:// ).

## Prerequisites

If you want to run this example with TLS support, you need to have
[`mkcert`](https://github.com/FiloSottile/mkcert) and run:

```sh
# assuming you have mkcert installed and `mkcert -install` has already executed:
mkcert -key-file key.pem -cert-file cert.pem localhost 127.0.0.1
```
49 changes: 46 additions & 3 deletions examples/websocket/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,54 @@ async fn main() {
}
}

Ohkami::new((Logger,
"/".Mount("./template").omit_extensions(&[".html"]),
#[cfg(feature="tls")]
let tls_config = {
use rustls::ServerConfig;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use std::fs::File;
use std::io::BufReader;

// Initialize rustls crypto provider
rustls::crypto::ring::default_provider().install_default()
.expect("Failed to install rustls crypto provider");

// Load certificates and private key
let cert_file = File::open("./cert.pem").expect("Failed to open certificate file");
let key_file = File::open("./key.pem").expect("Failed to open private key file");

let cert_chain = rustls_pemfile::certs(&mut BufReader::new(cert_file))
.map(|cd| cd.map(CertificateDer::from))
.collect::<Result<Vec<_>, _>>()
.expect("Failed to read certificate chain");

let key = rustls_pemfile::read_one(&mut BufReader::new(key_file))
.expect("Failed to read private key")
.map(|p| match p {
rustls_pemfile::Item::Pkcs1Key(k) => PrivateKeyDer::Pkcs1(k),
rustls_pemfile::Item::Pkcs8Key(k) => PrivateKeyDer::Pkcs8(k),
_ => panic!("Unexpected private key type"),
})
.expect("Failed to read private key");

// Build TLS configuration
ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(cert_chain, key)
.expect("Failed to build TLS configuration")
};

let o = Ohkami::new((
Logger,
"/".Mount("./template").omit_extensions(&["html"]),
"/echo1".GET(echo_text),
"/echo2/:name".GET(echo_text_2),
"/echo3/:name".GET(echo_text_3),
"/echo4/:name".GET(echo4),
)).howl("localhost:3030").await
));

#[cfg(not(feature="tls"))]
o.howl("localhost:3030").await;

#[cfg(feature="tls")]
o.howls("localhost:3030", tls_config).await;
}
65 changes: 17 additions & 48 deletions examples/websocket/template/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@
</style>
</head>
<body>
<h1>Echo Text</h1>
<h1>Echo Text Sample</h1>

<ul>
<li>Press <code>connect</code> and see dev console.</li>
<li>Type some text and press <code>send</code>.</li>
<li>Finally send <code>close</code> to finish the connection.</li>
</ul>

<main>
<div class="echo-text">
Expand All @@ -49,53 +55,16 @@ <h1>Echo Text</h1>
<button id="echo4-send-button"></button>
</div>
</main>

<script>
function connect_input_and_button_to(ws_url, input_id, button_id) {
let ws = null;

const input = document.getElementById(input_id);
input.spellcheck = false;
input.disabled = true;

const button = document.getElementById(button_id);
button.textContent = "connect";

button.addEventListener(
"click", (e) => {
if (button.textContent == "connect") {
ws = new WebSocket(ws_url);
ws.addEventListener("open", (e) => {
console.log(e);
ws.send("test");
});
ws.addEventListener("message", (e) => {
console.log("ws got message: ", e.data);
});
ws.addEventListener("close", (e) => {
console.log("close:", e);

input.value = "";
input.disabled = true;

button.textContent = "connect";
});

input.disabled = false;

button.textContent = "send";
} else {
console.log("sending:", input.value);
ws.send(input.value);
}
}
);
}

connect_input_and_button_to("ws://localhost:3030/echo1", "echo1-text-input", "echo1-send-button");
connect_input_and_button_to("ws://localhost:3030/echo2/ohkami", "echo2-text-input", "echo2-send-button");
connect_input_and_button_to("ws://localhost:3030/echo3/ohkami", "echo3-text-input", "echo3-send-button");
connect_input_and_button_to("ws://localhost:3030/echo4/ohkami", "echo4-text-input", "echo4-send-button");

<script type="module">
import { connect_input_and_button_to } from "/main.js";

const protocol = document.location.protocol === 'https:' ? 'wss:' : 'ws:'

connect_input_and_button_to(`${protocol}//localhost:3030/echo1`, "echo1-text-input", "echo1-send-button");
connect_input_and_button_to(`${protocol}//localhost:3030/echo2/ohkami`, "echo2-text-input", "echo2-send-button");
connect_input_and_button_to(`${protocol}//localhost:3030/echo3/ohkami`, "echo3-text-input", "echo3-send-button");
connect_input_and_button_to(`${protocol}//localhost:3030/echo4/ohkami`, "echo4-text-input", "echo4-send-button");
</script>
</body>
</html>
40 changes: 40 additions & 0 deletions examples/websocket/template/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export function connect_input_and_button_to(ws_url, input_id, button_id) {
let ws = null;

const input = document.getElementById(input_id);
input.spellcheck = false;
input.disabled = true;

const button = document.getElementById(button_id);
button.textContent = "connect";

button.addEventListener(
"click", (e) => {
if (button.textContent == "connect") {
ws = new WebSocket(ws_url);
ws.addEventListener("open", (e) => {
console.log(e);
ws.send("test");
});
ws.addEventListener("message", (e) => {
console.log("ws got message: ", e.data);
});
ws.addEventListener("close", (e) => {
console.log("close:", e);

input.value = "";
input.disabled = true;

button.textContent = "connect";
});

input.disabled = false;

button.textContent = "send";
} else {
console.log("sending:", input.value);
ws.send(input.value);
}
}
);
}
6 changes: 3 additions & 3 deletions ohkami/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ mime_guess = { version = "2.0", optional = true }
ctrlc = { version = "3.4", optional = true }
num_cpus = { version = "1.17", optional = true }
futures-util = { version = "0.3", optional = true, default-features = false }
mews = { version = "0.2", optional = true }
mews = { version = "0.4", optional = true }
rustls = { version = "0.23.23", optional = true }
tokio-rustls = { version = "0.26.2", optional = true }

Expand All @@ -65,7 +65,7 @@ rt_smol = ["__rt_native__",
rt_nio = ["__rt_native__",
"dep:nio",
"dep:tokio","tokio/io-util",
"mews?/rt_nio",
#"mews?/rt_nio",
]
rt_glommio = ["__rt_native__",
"dep:glommio",
Expand All @@ -85,7 +85,7 @@ rt_lambda = ["__rt__",
nightly = []
openapi = ["dep:ohkami_openapi", "ohkami_macros/openapi"]
sse = ["ohkami_lib/stream"]
ws = ["ohkami_lib/stream", "dep:mews"]
ws = ["ohkami_lib/stream", "dep:mews", "dep:futures-util", "futures-util/io", "futures-util/unstable","futures-util/bilock"]
tls = ["rt_tokio", "dep:rustls", "dep:tokio-rustls"] # currently depending on tokio-rustls and works only on tokio

##### internal #####
Expand Down
2 changes: 1 addition & 1 deletion ohkami/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ pub use ohkami::{Ohkami, Route};
pub mod fang;
pub use fang::{handler, Fang, FangProc, FangAction};

#[cfg(all(feature="__rt_native__", feature="rt_tokio", feature="tls"))]
#[cfg(feature="tls")]
mod tls;

pub mod header;
Expand Down
23 changes: 17 additions & 6 deletions ohkami/src/ohkami/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,9 +612,9 @@ impl Ohkami {
};

let session = Session::new(
router.clone(),
connection,
addr.ip()
addr.ip(),
router.clone(),
);

let wg = wg.add();
Expand Down Expand Up @@ -714,14 +714,25 @@ impl Ohkami {
crate::INFO!("start serving on {}", listener.local_addr().unwrap());

while let Some(accept) = ctrl_c.until_interrupt(listener.accept()).await {
let Ok((tcp_stream, addr)) = accept else { continue };
crate::DEBUG!("accept: {accept:?}");

let Ok(tls_stream) = tls_acceptor.accept(tcp_stream).await else { continue };
let Ok((connection, addr)) = accept else { continue };

let connection = match ctrl_c.until_interrupt(tls_acceptor.accept(connection)).await {
None => break,
Some(Ok(tls_stream)) => TlsStream(tls_stream),
Some(Err(e)) => {
crate::ERROR!("TLS accept error: {e}");
continue;
}
};

crate::DEBUG!("accepted TLS connection: {connection:?}");

let session = Session::new(
connection,
addr.ip(),
router.clone(),
TlsStream(tls_stream),
addr.ip()
);

let wg = wg.add();
Expand Down
2 changes: 1 addition & 1 deletion ohkami/src/response/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ pub(super) enum Upgrade {
None,

#[cfg(feature="ws")]
WebSocket(mews::WebSocket),
WebSocket(mews::WebSocket<crate::session::Connection>),
}
#[cfg(feature="__rt_native__")]
impl Upgrade {
Expand Down
Loading
Loading