HTTP Respond

This commit is contained in:
2025-05-30 22:10:54 +08:00
parent 2785fa3932
commit 0e78099742
2 changed files with 142 additions and 3 deletions
+2 -1
View File
@@ -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
View File
@@ -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>,