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

Support websocket #37

Merged
merged 37 commits into from
Dec 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3fc9a79
Add: `websocket` feature
kanarus Oct 30, 2023
016f996
Add: draft section `web socket` in README
kanarus Oct 31, 2023
e6098bd
@2023-10-31 18:48+9:00
kanarus Oct 31, 2023
23cb498
@2023-11-01 15:57+9:00
kanarus Nov 1, 2023
058cc7b
@2023-11-02 00:46+9:00
kanarus Nov 1, 2023
603b860
@2023-11-02 15:01+9:00
kanarus Nov 2, 2023
b068780
@2023-11-02 15:21+9:00
kanarus Nov 2, 2023
4cd17dd
@2023-11-03 00:38+9:00
kanarus Nov 2, 2023
d52e6fe
TODO: test of sign (sha1, base64)
kanarus Nov 3, 2023
e325b36
TODO: test sha1
kanarus Nov 3, 2023
4bfde18
TODO: test sha1
kanarus Nov 4, 2023
e59d233
passed
kanarus Nov 5, 2023
81dbfc3
TODO: parse web socket message
kanarus Nov 6, 2023
3403dc1
@2023-11-06 23:32+9:00
kanarus Nov 6, 2023
50cb41c
@2023-11-07 10:21+9:00
kanarus Nov 7, 2023
303a7ac
@2023-11-07 12:48+9:00
kanarus Nov 7, 2023
66ed48a
@2023-11-07 17:01+9:00
kanarus Nov 7, 2023
dee766d
@2023-11-07 23:33+9:00
kanarus Nov 7, 2023
7503eb3
@2023-11-07 23:13+9:00
kanarus Nov 7, 2023
cb6690b
@2023-11-08 22:14+9:00
kanarus Nov 8, 2023
6c10467
@2023-11-10 00:35+9:00
kanarus Nov 9, 2023
ff7a5b2
@2023-11-10 23:34+9:00
kanarus Nov 10, 2023
13ae665
@2023-11-11 01:02+9:00
kanarus Nov 10, 2023
4738f5b
@2023-11-11 15:50+9:00
kanarus Nov 11, 2023
4446f51
@2023-11-11 16:34+9:00
kanarus Nov 11, 2023
b5e8040
@2023-11-11 21:03+9:00
kanarus Nov 11, 2023
bf09249
@2023-11-12 02:14+9:00
kanarus Nov 11, 2023
5ef55e4
@2023-11-14 23:54+9:00
kanarus Nov 14, 2023
b832f5a
@2023-11-15 16:50+9:00
kanarus Nov 15, 2023
49b2fd0
fin impl
kanarus Nov 19, 2023
8a66b4b
@2023-11-19 14:45+9:00
kanarus Nov 19, 2023
2507e50
TODO: abstract `Stream`
kanarus Nov 22, 2023
2b7e55d
@2023-11-23 00:36+9:00
kanarus Nov 22, 2023
355e087
@2023-11-25 20:18+9:00
kanarus Nov 25, 2023
0cb6c30
@2023-11-26 00:51+9:00
kanarus Nov 25, 2023
0389c86
@2023-11-26 00:54+9:00
kanarus Nov 25, 2023
3e4ab49
impl mask
kanarus Nov 26, 2023
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
33 changes: 7 additions & 26 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,25 @@ on:
- main

jobs:
check-stable:
test:
runs-on: ubuntu-latest

strategy:
matrix:
rt: [tokio, async-std]
rt: ["tokio", "async-std"]
x: [",websocket", ""]
toolchain: ["stable", "nightly"]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: ${{ matrix.toolchain }}
profile: minimal
override: true

- uses: actions-rs/cargo@v1
with:
command: check
args: --features rt_${{ matrix.rt }}

check-nighlt:
runs-on: ubuntu-latest

strategy:
matrix:
rt: [tokio, async-std]

steps:
- uses: actions/checkout@v3

- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true

- uses: actions-rs/cargo@v1
with:
command: check
args: --features nightly,rt_${{ matrix.rt }}
args: --features rt_${{ matrix.rt }}${{ matrix.x }}${{ matrix.toolchain == "nightly" && ",nightly" || "" }},DEBUG
33 changes: 7 additions & 26 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,25 @@ on:
- main

jobs:
test-stable:
test:
runs-on: ubuntu-latest

strategy:
matrix:
rt: [tokio, async-std]
rt: ["tokio", "async-std"]
x: [",websocket", ""]
toolchain: ["stable", "nightly"]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: ${{ matrix.toolchain }}
profile: minimal
override: true

- uses: actions-rs/cargo@v1
with:
command: test
args: --features rt_${{ matrix.rt }},DEBUG

test-nightly:
runs-on: ubuntu-latest

strategy:
matrix:
rt: [tokio, async-std]

steps:
- uses: actions/checkout@v3

- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true

- uses: actions-rs/cargo@v1
with:
command: test
args: --features nightly,rt_${{ matrix.rt }},DEBUG
args: --features rt_${{ matrix.rt }}${{ matrix.x }}${{ matrix.toolchain == "nightly" && ",nightly" || "" }},DEBUG
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,42 @@ async fn main() {

<br/>

### web socket
Activate `websocket` feature.

```rust
use ohkami::prelude::*;
use ohkami::websocket::{WebSocketContext, Message};

fn handle_websocket(c: WebSocketContext) -> Response {
c.on_upgrade(|ws| async move {
while let Some(Ok(message)) = ws.recv().await {
match message {
Message::Text(text) => {
let response = Message::from(text);
if let Err(e) = ws.send(response).await {
tracing::error!("{e}");
break
}
}
Message::Close(_) => break,
other => tracing::warning!("Unsupported message type: {other}"),
}
}
}).await
}

#[tokio::main]
async fn main() {
Ohkami::new((
"/websocket"
.GET(handle_websocket)
)).howl(8080).await
}
```

<br/>

### testing
```rust
use ohkami::prelude::*;
Expand Down
13 changes: 11 additions & 2 deletions ohkami/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,17 @@ byte_reader = "1.1.2"
[features]
rt_tokio = ["dep:tokio"]
rt_async-std = ["dep:async-std"]
websocket = []
nightly = []

##### DEBUG #####
DEBUG = ["serde/derive", "tokio?/macros", "async-std?/attributes"]
# default = ["rt_tokio", "DEBUG"]
DEBUG = [
"websocket",
"serde/derive", "tokio?/macros", "async-std?/attributes"
]
default = [
"rt_tokio",
#"rt_async-std",
"DEBUG",
# "nightly"
]
7 changes: 7 additions & 0 deletions ohkami/src/layer0_lib/status.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
#[derive(PartialEq)]
pub enum Status {
SwitchingProtocols,

OK,
Created,
NoContent,

MovedPermanently,
Found,

BadRequest,
Unauthorized,
Forbidden,
NotFound,

InternalServerError,
NotImplemented,
} impl Status {
#[inline(always)] pub(crate) const fn as_str(&self) -> &'static str {
match self {
Self::SwitchingProtocols => "101 Switching Protocols",

Self::OK => "200 OK",
Self::Created => "201 Created",
Self::NoContent => "204 No Content",
Expand Down
1 change: 1 addition & 0 deletions ohkami/src/layer0_lib/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ pub trait IntoCows<'l> {
}
impl IntoCows<'static> for &'static str {fn into_cow(self) -> Cow<'static, str> {Cow::Borrowed(self)}}
impl IntoCows<'static> for String {fn into_cow(self) -> Cow<'static, str> {Cow::Owned(self)}}
impl IntoCows<'static> for Cow<'static, str> {fn into_cow(self) -> Cow<'static, str> {self}}
2 changes: 1 addition & 1 deletion ohkami/src/layer1_req_res/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod request; pub use request::*;
mod request; pub use request::*;
mod response; pub use response::*;


Expand Down
43 changes: 25 additions & 18 deletions ohkami/src/layer1_req_res/response/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@
#![allow(non_snake_case)]
#![allow(unused)] // until ....

use std::{collections::BTreeMap, sync::OnceLock};
use crate::{layer0_lib::now};
use std::{collections::BTreeMap, sync::OnceLock, borrow::Cow};
use crate::{layer0_lib::{now, IntoCows}};


struct Header(Option<&'static str>);
struct Header(Option<Cow<'static, str>>);

pub trait HeaderValue {
fn into_header_value(self) -> Option<&'static str>;
}
impl HeaderValue for &'static str {fn into_header_value(self) -> Option<&'static str> {Some(self)}}
impl HeaderValue for Option<&'static str> {fn into_header_value(self) -> Option<&'static str> {self}}

fn into_header_value(self) -> Option<Cow<'static, str>>;
} const _: () = {
impl<S: IntoCows<'static>> HeaderValue for S {
fn into_header_value(self) -> Option<Cow<'static, str>> {
Some(self.into_cow())
}
}
impl HeaderValue for Option<()> {
fn into_header_value(self) -> Option<Cow<'static, str>> {
None
}
}
};

macro_rules! ResponseHeaders {
($(
Expand All @@ -23,22 +31,23 @@ macro_rules! ResponseHeaders {
)*) => {
/// Headers in a response.
///
/// In current version, this expects values are `&'static str` or `None`.
/// Expected values: &'static str, String, Cow<'static, str>, or `None`
///
/// - `&'static str` sets the header value to it
/// - `None` removes the header value
/// - `None` clears value of the header
/// - others set the header to thet value
///
/// <br/>
///
/// - Content-Type
/// - Content-Length
/// - Access-Control-*
/// - headers related to WebSocket handshake
///
/// are managed by ohkami and MUST NOT be set by `.custom` ( `.custom` has to be used **ONLY** to set custom HTTP headers )
/// are managed by ohkami and MUST NOT be set by `.custom` ( `.custom` has to be used **ONLY** to set custom HTTP headers like `X-MyApp-Data: amazing` )
pub struct ResponseHeaders {
$( $group: bool, )*
$($( $name: Header, )*)*
custom: BTreeMap<&'static str, &'static str>,
custom: BTreeMap<&'static str, Cow<'static, str>>,
cors_str: &'static str,
}

Expand Down Expand Up @@ -76,8 +85,8 @@ macro_rules! ResponseHeaders {
$(
if self.$group {
$(
if let Some(value) = self.$name.0 {
h.push_str($key);h.push_str(value);h.push('\r');h.push('\n');
if let Some(value) = &self.$name.0 {
h.push_str($key);h.push_str(&value);h.push('\r');h.push('\n');
}
)*
}
Expand Down Expand Up @@ -141,9 +150,7 @@ impl ResponseHeaders {
pub fn custom(&mut self, key: &'static str, value: impl HeaderValue) -> &mut Self {
match value.into_header_value() {
Some(value) => {
self.custom.entry(key)
.and_modify(|v| *v = value)
.or_insert(value);
self.custom.insert(key, value);
self
}
None => {
Expand Down
66 changes: 29 additions & 37 deletions ohkami/src/layer2_context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,61 +73,53 @@ use crate::{
/// }
/// ```
pub struct Context {
#[cfg(feature="websocket")]
pub(crate) upgrade_id: Option<crate::x_websocket::UpgradeID>,

pub headers: ResponseHeaders,
}

impl Context {
#[inline(always)] pub(crate) fn new() -> Self {
Self { headers: ResponseHeaders::new() }
}
}
Self {
#[cfg(feature="websocket")]
upgrade_id: None,

impl Context {
#[inline] pub fn OK(&self) -> Response {
Response {
status: Status::OK,
headers: self.headers.to_string(),
content: None,
}
}
#[inline] pub fn Created(&self) -> Response {
Response {
status: Status::Created,
headers: self.headers.to_string(),
content: None,
}
}
#[inline] pub fn NoContent(&self) -> Response {
Response {
status: Status::NoContent,
headers: self.headers.to_string(),
content: None,
headers: ResponseHeaders::new(),
}
}
}

macro_rules! impl_error_response {
($( $name:ident ),*) => {
macro_rules! generate_response {
($( $status:ident ),* $(,)?) => {$(
impl Context {
$(
#[inline] pub fn $name(&self) -> Response {
Response {
status: Status::$name,
headers: self.headers.to_string(),
content: None,
}
#[inline] pub fn $status(&self) -> Response {
Response {
status: Status::$status,
headers: self.headers.to_string(),
content: None,
}
)*
}
}
};
} impl_error_response!(
)*};
} generate_response! {
SwitchingProtocols,

OK,
Created,
NoContent,

// MovedPermanently,
// Found,

BadRequest,
Unauthorized,
Forbidden,
NotFound,

InternalServerError,
NotImplemented
);
NotImplemented,
}

impl Context {
#[inline] pub fn redirect_to(&self, location: impl AsStr) -> Response {
Expand Down
Loading