HTTP Respond
This commit is contained in:
+2
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nostr-relay"
|
||||
version = "0.1.0"
|
||||
version = "0.0.2"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
@@ -18,3 +18,4 @@ log = "0.4.27"
|
||||
env_logger = "0.11.8"
|
||||
uuid = { version = "1.6.2", features = ["v4"] }
|
||||
sqlx = { version = "0.7.1", features = ["runtime-tokio-rustls", "sqlite"] }
|
||||
httparse = "1.10.1"
|
||||
|
||||
+140
-2
@@ -21,6 +21,8 @@ use tokio::{
|
||||
};
|
||||
use tokio_tungstenite::{WebSocketStream, accept_async, tungstenite::protocol::Message};
|
||||
use uuid::Uuid;
|
||||
use httparse::{Request, EMPTY_HEADER};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct NostrEvent {
|
||||
@@ -63,6 +65,30 @@ pub struct Filter {
|
||||
pub until: Option<u64>, // Unix 时间戳,秒
|
||||
pub limit: Option<u64>, // 最大返回数
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
struct Limitation {
|
||||
max_event_tags: u32,
|
||||
max_event_time_newer_than_now: u32,
|
||||
max_event_time_older_than_now: u32,
|
||||
max_filters: u32,
|
||||
max_limit: u32,
|
||||
max_message_length: u32,
|
||||
max_subid_length: u32,
|
||||
max_subscriptions: u32,
|
||||
min_prefix: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ServerInfo {
|
||||
contact: &'static str,
|
||||
description: &'static str,
|
||||
limitation: Limitation,
|
||||
name: &'static str,
|
||||
pubkey: &'static str,
|
||||
software: &'static str,
|
||||
supported_nips: Vec<u32>,
|
||||
version: &'static str,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
@@ -76,17 +102,129 @@ async fn main() {
|
||||
let listener = TcpListener::bind(&addr).await.expect("Failed to bind");
|
||||
info!("Listening on: {}", addr);
|
||||
let (tx, _) = broadcast::channel::<String>(100);
|
||||
|
||||
while let Ok((stream, addr)) = listener.accept().await {
|
||||
let tx = tx.clone();
|
||||
let rx = tx.subscribe();
|
||||
let pool = pool.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
handle_connection(stream, tx, rx, pool).await;
|
||||
if let Err(e) = handle_connection_multiplex(stream, tx, rx, pool).await {
|
||||
error!("Error handling connection: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_connection(
|
||||
async fn handle_connection_multiplex(
|
||||
mut stream: TcpStream,
|
||||
tx: broadcast::Sender<String>,
|
||||
rx: broadcast::Receiver<String>,
|
||||
pool: Arc<SqlitePool>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// 分配足够大的缓冲区,一次读完整个头
|
||||
let mut buf = vec![0u8; 4096];
|
||||
let n = stream.peek(&mut buf).await?;
|
||||
let req_bytes = &buf[..n];
|
||||
|
||||
let mut headers: [httparse::Header; 32] = [EMPTY_HEADER; 32];
|
||||
let mut req = Request::new(&mut headers);
|
||||
let status = req.parse(req_bytes)?;
|
||||
if status.is_partial() {
|
||||
anyhow::bail!("Request headers too large");
|
||||
}
|
||||
let method = req.method.unwrap_or("");
|
||||
let header_map = req
|
||||
.headers
|
||||
.iter()
|
||||
.filter_map(|h| {
|
||||
let name = h.name.to_ascii_lowercase();
|
||||
let val = std::str::from_utf8(h.value).ok()?;
|
||||
Some((name, val))
|
||||
})
|
||||
.collect::<std::collections::HashMap<_, _>>();
|
||||
|
||||
if method == "GET"
|
||||
&& header_map
|
||||
.get("upgrade")
|
||||
.map(|&v| v.eq_ignore_ascii_case("websocket"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
handle_ws_connection(stream, tx, rx, pool).await;
|
||||
}else {
|
||||
if let Some(accept) = header_map.get("accept") {
|
||||
if accept.contains("application/json") {
|
||||
handle_http_info(stream).await?;
|
||||
} else if accept.contains("text/html") {
|
||||
handle_http(stream).await?;
|
||||
} else {
|
||||
handle_http_info(stream).await?;
|
||||
}
|
||||
} else {
|
||||
handle_http_info(stream).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_http_info(mut stream: TcpStream) -> Result<(), anyhow::Error> {
|
||||
let mut buffer = vec![0; 1024];
|
||||
stream.read(&mut buffer).await?;
|
||||
let info = ServerInfo {
|
||||
contact: "https://www.moec.top/",
|
||||
description: "Powered by laoXong.",
|
||||
limitation: Limitation {
|
||||
max_event_tags: 5000,
|
||||
max_event_time_newer_than_now: 900,
|
||||
max_event_time_older_than_now: 315576000,
|
||||
max_filters: 100,
|
||||
max_limit: 100,
|
||||
max_message_length: 524288,
|
||||
max_subid_length: 100,
|
||||
max_subscriptions: 20,
|
||||
min_prefix: 10,
|
||||
},
|
||||
name: "A rust nostr relay by laoXong",
|
||||
pubkey: "63abd4f817e39cca4e6abb6e6cf3e133bb718cf8ec28b38c1645e84d7a6190c6",
|
||||
software: "https://git.moe.gift/laoxong/nostr-relay",
|
||||
supported_nips: vec![1, 2, 5],
|
||||
version: env!("CARGO_PKG_VERSION"),
|
||||
};
|
||||
|
||||
// 序列化为 JSON
|
||||
let json = serde_json::to_string(&info)
|
||||
.expect("Failed to serialize server info");
|
||||
let response = format!(
|
||||
"HTTP/1.1 200 OK\r\n\
|
||||
Content-Type: application/nostr+json\r\n\
|
||||
Access-Control-Allow-Origin: *\r\n\
|
||||
\r\n\
|
||||
{}",
|
||||
json);
|
||||
stream.write_all(response.as_bytes()).await?;
|
||||
stream.flush().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_http(mut stream: TcpStream) -> Result<(), anyhow::Error> {
|
||||
let mut buffer = vec![0; 1024];
|
||||
stream.read(&mut buffer).await?;
|
||||
let response = format!(
|
||||
"HTTP/1.1 200 OK\r\n\
|
||||
Content-Type: text/html\r\n\
|
||||
Access-Control-Allow-Origin: *\r\n\
|
||||
\r\n\
|
||||
A Simple Nostr Relay by laoXong Version:{}",
|
||||
env!("CARGO_PKG_VERSION"));
|
||||
stream.write_all(response.as_bytes()).await?;
|
||||
stream.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn handle_ws_connection(
|
||||
stream: TcpStream,
|
||||
tx: broadcast::Sender<String>,
|
||||
mut rx: broadcast::Receiver<String>,
|
||||
|
||||
Reference in New Issue
Block a user