Skip to content

Commit

Permalink
Add versioning (#1121)
Browse files Browse the repository at this point in the history
  • Loading branch information
al8n authored Jun 9, 2024
1 parent 8ac5845 commit 6f4c001
Show file tree
Hide file tree
Showing 16 changed files with 299 additions and 233 deletions.
2 changes: 1 addition & 1 deletion apps/freenet-ping/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>

// create a websocket connection to host.
let uri = format!(
"ws://{}/contract/command?encodingProtocol=native",
"ws://{}/v1/contract/command?encodingProtocol=native",
args.host
);
let (stream, _resp) = tokio_tungstenite::connect_async(&uri).await.map_err(|e| {
Expand Down
16 changes: 3 additions & 13 deletions crates/core/src/client_events/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ use crate::{

use super::{ClientError, ClientEventsProxy, ClientId, HostResult, OpenRequest};

mod v1;

#[derive(Clone)]
struct WebSocketRequest(mpsc::Sender<ClientConnection>);

Expand All @@ -52,19 +54,7 @@ const PARALLELISM: usize = 10; // TODO: get this from config, or whatever optima

impl WebSocketProxy {
pub fn as_router(server_routing: Router) -> (Self, Router) {
let (proxy_request_sender, proxy_server_request) = mpsc::channel(PARALLELISM);

let router = server_routing
.route("/contract/command", get(websocket_commands))
.layer(Extension(WebSocketRequest(proxy_request_sender)))
.layer(axum::middleware::from_fn(connection_info));
(
WebSocketProxy {
proxy_server_request,
response_channels: HashMap::new(),
},
router,
)
WebSocketProxy::as_router_v1(server_routing)
}

async fn internal_proxy_recv(
Expand Down
19 changes: 19 additions & 0 deletions crates/core/src/client_events/websocket/v1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use super::*;

impl WebSocketProxy {
pub fn as_router_v1(server_routing: Router) -> (Self, Router) {
let (proxy_request_sender, proxy_server_request) = mpsc::channel(PARALLELISM);

let router = server_routing
.route("/v1/contract/command", get(websocket_commands))
.layer(Extension(WebSocketRequest(proxy_request_sender)))
.layer(axum::middleware::from_fn(connection_info));
(
WebSocketProxy {
proxy_server_request,
response_channels: HashMap::new(),
},
router,
)
}
}
3 changes: 2 additions & 1 deletion crates/core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,9 @@ impl ConfigArgs {
Err(err) => {
#[cfg(not(any(test, debug_assertions)))]
{
if peer_id.is_none() {
if peer_id.is_none() && mode == OperationMode::Network {
tracing::error!(file = ?gateways_file, "Failed to read gateways file: {err}");

return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Cannot initialize node without gateways",
Expand Down
85 changes: 8 additions & 77 deletions crates/core/src/server/http_gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use crate::server::HostCallbackResult;

use super::{errors::WebSocketApiError, path_handlers, AuthToken, ClientConnection};

mod v1;

#[derive(Clone)]
pub(super) struct HttpGatewayRequest(mpsc::Sender<ClientConnection>);

Expand All @@ -39,91 +41,20 @@ pub(super) struct HttpGateway {
response_channels: HashMap<ClientId, mpsc::UnboundedSender<HostCallbackResult>>,
}

#[derive(Clone)]
struct Config {
localhost: bool,
}

impl HttpGateway {
/// Returns the uninitialized axum router to compose with other routing handling or websockets.
pub fn as_router(socket: &SocketAddr) -> (Self, Router) {
let localhost = match socket.ip() {
IpAddr::V4(ip) if ip.is_loopback() => true,
IpAddr::V6(ip) if ip.is_loopback() => true,
_ => false,
};
let contract_web_path = std::env::temp_dir().join("freenet").join("webs");
std::fs::create_dir_all(contract_web_path).unwrap();

let (proxy_request_sender, request_to_server) = mpsc::channel(1);

let config = Config { localhost };

let router = Router::new()
.route("/", get(home))
.route("/contract/web/:key/", get(web_home))
.with_state(config)
.route("/contract/web/:key/*path", get(web_subpages))
.layer(Extension(HttpGatewayRequest(proxy_request_sender)));

(
Self {
proxy_server_request: request_to_server,
attested_contracts: HashMap::new(),
response_channels: HashMap::new(),
},
router,
)
Self::as_router_v1(socket)
}
}

async fn home() -> axum::response::Response {
axum::response::Response::default()
}

async fn web_home(
Path(key): Path<String>,
Extension(rs): Extension<HttpGatewayRequest>,
axum::extract::State(config): axum::extract::State<Config>,
) -> Result<axum::response::Response, WebSocketApiError> {
use headers::{Header, HeaderMapExt};

let domain = config
.localhost
.then_some("localhost")
.expect("non-local connections not supported yet");
let token = AuthToken::generate();

let auth_header = headers::Authorization::<headers::authorization::Bearer>::name().to_string();
let cookie = cookie::Cookie::build((auth_header, format!("Bearer {}", token.as_str())))
.domain(domain)
.path(format!("/contract/web/{key}"))
.same_site(cookie::SameSite::Strict)
.max_age(cookie::time::Duration::days(1))
.secure(!config.localhost)
.http_only(false)
.build();

let token_header = headers::Authorization::bearer(token.as_str()).unwrap();
let contract_idx = path_handlers::contract_home(key, rs, token).await?;
let mut response = contract_idx.into_response();
response.headers_mut().typed_insert(token_header);
response.headers_mut().insert(
headers::SetCookie::name(),
headers::HeaderValue::from_str(&cookie.to_string()).unwrap(),
);

Ok(response)
#[derive(Clone)]
struct Config {
localhost: bool,
}

async fn web_subpages(
Path((key, last_path)): Path<(String, String)>,
) -> Result<axum::response::Response, WebSocketApiError> {
let full_path: String = format!("/contract/web/{}/{}", key, last_path);
path_handlers::variable_content(key, full_path)
.await
.map_err(|e| *e)
.map(|r| r.into_response())
async fn home() -> axum::response::Response {
axum::response::Response::default()
}

impl ClientEventsProxy for HttpGateway {
Expand Down
79 changes: 79 additions & 0 deletions crates/core/src/server/http_gateway/v1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use super::*;

impl HttpGateway {
/// Returns the uninitialized axum router to compose with other routing handling or websockets.
pub fn as_router_v1(socket: &SocketAddr) -> (Self, Router) {
let localhost = match socket.ip() {
IpAddr::V4(ip) if ip.is_loopback() => true,
IpAddr::V6(ip) if ip.is_loopback() => true,
_ => false,
};
let contract_web_path = std::env::temp_dir().join("freenet").join("webs");
std::fs::create_dir_all(contract_web_path).unwrap();

let (proxy_request_sender, request_to_server) = mpsc::channel(1);

let config = Config { localhost };

let router = Router::new()
.route("/v1", get(home))
.route("/v1/contract/web/:key/", get(web_home))
.with_state(config)
.route("/v1/contract/web/:key/*path", get(web_subpages))
.layer(Extension(HttpGatewayRequest(proxy_request_sender)));

(
Self {
proxy_server_request: request_to_server,
attested_contracts: HashMap::new(),
response_channels: HashMap::new(),
},
router,
)
}
}

async fn web_home(
Path(key): Path<String>,
Extension(rs): Extension<HttpGatewayRequest>,
axum::extract::State(config): axum::extract::State<Config>,
) -> Result<axum::response::Response, WebSocketApiError> {
use headers::{Header, HeaderMapExt};

let domain = config
.localhost
.then_some("localhost")
.expect("non-local connections not supported yet");
let token = AuthToken::generate();

let auth_header = headers::Authorization::<headers::authorization::Bearer>::name().to_string();
let cookie = cookie::Cookie::build((auth_header, format!("Bearer {}", token.as_str())))
.domain(domain)
.path(format!("/v1/contract/web/{key}"))
.same_site(cookie::SameSite::Strict)
.max_age(cookie::time::Duration::days(1))
.secure(!config.localhost)
.http_only(false)
.build();

let token_header = headers::Authorization::bearer(token.as_str()).unwrap();
let contract_idx = path_handlers::contract_home(key, rs, token).await?;
let mut response = contract_idx.into_response();
response.headers_mut().typed_insert(token_header);
response.headers_mut().insert(
headers::SetCookie::name(),
headers::HeaderValue::from_str(&cookie.to_string()).unwrap(),
);

Ok(response)
}

async fn web_subpages(
Path((key, last_path)): Path<(String, String)>,
) -> Result<axum::response::Response, WebSocketApiError> {
let full_path: String = format!("/v1/contract/web/{}/{}", key, last_path);
path_handlers::variable_content(key, full_path)
.await
.map_err(|e| *e)
.map(|r| r.into_response())
}
32 changes: 3 additions & 29 deletions crates/core/src/server/path_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use super::{
ClientConnection, HostCallbackResult,
};

mod v1;

const ALPHABET: &str = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

pub(super) async fn contract_home(
Expand Down Expand Up @@ -214,33 +216,5 @@ fn contract_web_path(key: &ContractKey) -> PathBuf {

#[inline]
fn get_file_path(uri: axum::http::Uri) -> Result<String, Box<WebSocketApiError>> {
let p = uri.path().strip_prefix("/contract/").ok_or_else(|| {
Box::new(WebSocketApiError::InvalidParam {
error_cause: format!("{uri} not valid"),
})
})?;
let path = p
.chars()
.skip_while(|c| ALPHABET.contains(*c))
.skip_while(|c| c == &'/')
.skip_while(|c| ALPHABET.contains(*c))
.skip_while(|c| c == &'/')
.collect::<String>();
Ok(path)
}

#[test]
fn get_path() {
let req_path = "/contract/HjpgVdSziPUmxFoBgTdMkQ8xiwhXdv1qn5ouQvSaApzD/web/state.html";
let base_dir =
PathBuf::from("/tmp/freenet/webs/HjpgVdSziPUmxFoBgTdMkQ8xiwhXdv1qn5ouQvSaApzD/web/");
let uri: axum::http::Uri = req_path.parse().unwrap();
let parsed = get_file_path(uri).unwrap();
let result = base_dir.join(parsed);
assert_eq!(
std::path::PathBuf::from(
"/tmp/freenet/webs/HjpgVdSziPUmxFoBgTdMkQ8xiwhXdv1qn5ouQvSaApzD/web/state.html"
),
result
);
v1::get_file_path(uri)
}
34 changes: 34 additions & 0 deletions crates/core/src/server/path_handlers/v1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use super::*;

#[inline]
pub(super) fn get_file_path(uri: axum::http::Uri) -> Result<String, Box<WebSocketApiError>> {
let p = uri.path().strip_prefix("/v1/contract/").ok_or_else(|| {
Box::new(WebSocketApiError::InvalidParam {
error_cause: format!("{uri} not valid"),
})
})?;
let path = p
.chars()
.skip_while(|c| ALPHABET.contains(*c))
.skip_while(|c| c == &'/')
.skip_while(|c| ALPHABET.contains(*c))
.skip_while(|c| c == &'/')
.collect::<String>();
Ok(path)
}

#[test]
pub(super) fn get_path() {
let req_path = "/v1/contract/HjpgVdSziPUmxFoBgTdMkQ8xiwhXdv1qn5ouQvSaApzD/web/state.html";
let base_dir =
PathBuf::from("/tmp/freenet/webs/HjpgVdSziPUmxFoBgTdMkQ8xiwhXdv1qn5ouQvSaApzD/web/");
let uri: axum::http::Uri = req_path.parse().unwrap();
let parsed = get_file_path(uri).unwrap();
let result = base_dir.join(parsed);
assert_eq!(
std::path::PathBuf::from(
"/tmp/freenet/webs/HjpgVdSziPUmxFoBgTdMkQ8xiwhXdv1qn5ouQvSaApzD/web/state.html"
),
result
);
}
31 changes: 3 additions & 28 deletions crates/fdev/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use freenet_stdlib::{

use crate::config::{BaseConfig, PutConfig, UpdateConfig};

mod v1;

#[derive(Debug, Clone, clap::Subcommand)]
pub(crate) enum PutType {
/// Puts a new contract
Expand Down Expand Up @@ -149,32 +151,5 @@ async fn execute_command(
address: IpAddr,
port: u16,
) -> Result<(), anyhow::Error> {
let mode = other.mode;

let target = match mode {
OperationMode::Local => {
if !address.is_loopback() {
return Err(anyhow::anyhow!(
"invalid ip: {address}, expecting a loopback ip address in local mode"
));
}
SocketAddr::new(address, port)
}
OperationMode::Network => SocketAddr::new(address, port),
};

let (stream, _) = tokio_tungstenite::connect_async(&format!(
"ws://{}/contract/command?encodingProtocol=native",
target
))
.await
.map_err(|e| {
tracing::error!(err=%e);
anyhow::anyhow!(format!("fail to connect to the host({target}): {e}"))
})?;

WebApi::start(stream)
.send(request)
.await
.map_err(Into::into)
v1::execute_command(request, other, address, port).await
}
Loading

0 comments on commit 6f4c001

Please sign in to comment.