diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-21 13:37:57 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-21 13:37:57 +0000 |
| commit | 9a8c551adfada379704d594a6ff3862f13857b8e (patch) | |
| tree | a902c6b313ab40a8914a34380ee194524dd67604 /src/http | |
| parent | 12756fa66e3ec7f9dd24c66598085772829a8063 (diff) | |
add git http handling
Diffstat (limited to 'src/http')
| -rw-r--r-- | src/http/mod.rs | 78 |
1 files changed, 76 insertions, 2 deletions
diff --git a/src/http/mod.rs b/src/http/mod.rs index 7c0e7bb..28ccd7b 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs | |||
| @@ -8,12 +8,13 @@ use std::future::Future; | |||
| 8 | use std::net::SocketAddr; | 8 | use std::net::SocketAddr; |
| 9 | use std::pin::Pin; | 9 | use std::pin::Pin; |
| 10 | 10 | ||
| 11 | use hyper::body::Incoming; | 11 | use hyper::body::{Bytes, Incoming}; |
| 12 | use hyper::header::{CONNECTION, SEC_WEBSOCKET_ACCEPT, UPGRADE}; | 12 | use hyper::header::{CONNECTION, SEC_WEBSOCKET_ACCEPT, UPGRADE}; |
| 13 | use hyper::server::conn::http1; | 13 | use hyper::server::conn::http1; |
| 14 | use hyper::service::Service; | 14 | use hyper::service::Service; |
| 15 | use hyper::{Request, Response}; | 15 | use hyper::{Method, Request, Response}; |
| 16 | use hyper_util::rt::TokioIo; | 16 | use hyper_util::rt::TokioIo; |
| 17 | use http_body_util::BodyExt; | ||
| 17 | use nostr_sdk::hashes::sha1::Hash as Sha1Hash; | 18 | use nostr_sdk::hashes::sha1::Hash as Sha1Hash; |
| 18 | use nostr_sdk::hashes::{Hash, HashEngine}; | 19 | use nostr_sdk::hashes::{Hash, HashEngine}; |
| 19 | use nostr_relay_builder::LocalRelay; | 20 | use nostr_relay_builder::LocalRelay; |
| @@ -21,6 +22,7 @@ use tokio::net::TcpListener; | |||
| 21 | use base64::Engine; | 22 | use base64::Engine; |
| 22 | 23 | ||
| 23 | use crate::config::Config; | 24 | use crate::config::Config; |
| 25 | use crate::git; | ||
| 24 | 26 | ||
| 25 | /// HTTP Service that serves both WebSocket (relay) and HTML landing page | 27 | /// HTTP Service that serves both WebSocket (relay) and HTML landing page |
| 26 | struct HttpService { | 28 | struct HttpService { |
| @@ -46,6 +48,78 @@ impl Service<Request<Incoming>> for HttpService { | |||
| 46 | 48 | ||
| 47 | fn call(&self, req: Request<Incoming>) -> Self::Future { | 49 | fn call(&self, req: Request<Incoming>) -> Self::Future { |
| 48 | let base = Response::builder().header("server", "ngit-grasp"); | 50 | let base = Response::builder().header("server", "ngit-grasp"); |
| 51 | let path = req.uri().path().to_string(); | ||
| 52 | let query = req.uri().query().map(|s| s.to_string()); | ||
| 53 | let method = req.method().clone(); | ||
| 54 | let git_data_path = self.config.git_data_path.clone(); | ||
| 55 | |||
| 56 | // Check for Git HTTP requests first | ||
| 57 | if let Some((npub, identifier, subpath)) = git::parse_git_url(&path) { | ||
| 58 | let npub = npub.to_string(); | ||
| 59 | let identifier = identifier.to_string(); | ||
| 60 | let subpath = subpath.to_string(); | ||
| 61 | |||
| 62 | tracing::debug!("Git request: {} {} (npub={}, id={}, subpath={})", | ||
| 63 | method, path, npub, identifier, subpath); | ||
| 64 | |||
| 65 | let repo_path = git::resolve_repo_path(&git_data_path, &npub, &identifier); | ||
| 66 | |||
| 67 | return Box::pin(async move { | ||
| 68 | let result = match (method.as_ref(), subpath.as_str()) { | ||
| 69 | // GET /info/refs?service=git-upload-pack or git-receive-pack | ||
| 70 | (m, sp) if m == Method::GET && sp.starts_with("info/refs") => { | ||
| 71 | // Parse query string for service parameter | ||
| 72 | let service = query.as_deref().unwrap_or("") | ||
| 73 | .strip_prefix("service=") | ||
| 74 | .and_then(git::protocol::GitService::from_query_param); | ||
| 75 | |||
| 76 | match service { | ||
| 77 | Some(svc) => { | ||
| 78 | git::handlers::handle_info_refs(repo_path, svc).await | ||
| 79 | } | ||
| 80 | None => { | ||
| 81 | Err(git::handlers::GitError::RepositoryNotFound) | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | // POST /git-upload-pack (clone/fetch) | ||
| 87 | (m, "git-upload-pack") if m == Method::POST => { | ||
| 88 | // Read request body | ||
| 89 | let body_bytes = req.collect().await | ||
| 90 | .map(|collected| collected.to_bytes()) | ||
| 91 | .unwrap_or_else(|_| Bytes::new()); | ||
| 92 | |||
| 93 | git::handlers::handle_upload_pack(repo_path, body_bytes).await | ||
| 94 | } | ||
| 95 | |||
| 96 | // POST /git-receive-pack (push) | ||
| 97 | (m, "git-receive-pack") if m == Method::POST => { | ||
| 98 | // Read request body | ||
| 99 | let body_bytes = req.collect().await | ||
| 100 | .map(|collected| collected.to_bytes()) | ||
| 101 | .unwrap_or_else(|_| Bytes::new()); | ||
| 102 | |||
| 103 | git::handlers::handle_receive_pack(repo_path, body_bytes).await | ||
| 104 | } | ||
| 105 | |||
| 106 | _ => { | ||
| 107 | Err(git::handlers::GitError::RepositoryNotFound) | ||
| 108 | } | ||
| 109 | }; | ||
| 110 | |||
| 111 | match result { | ||
| 112 | Ok(response) => Ok(response), | ||
| 113 | Err(e) => { | ||
| 114 | tracing::error!("Git handler error: {}", e); | ||
| 115 | Ok(Response::builder() | ||
| 116 | .status(e.status_code()) | ||
| 117 | .body(format!("Git error: {}", e)) | ||
| 118 | .unwrap()) | ||
| 119 | } | ||
| 120 | } | ||
| 121 | }); | ||
| 122 | } | ||
| 49 | 123 | ||
| 50 | // Check for NIP-11 relay information request (Accept: application/nostr+json) | 124 | // Check for NIP-11 relay information request (Accept: application/nostr+json) |
| 51 | if let Some(accept) = req.headers().get("accept") { | 125 | if let Some(accept) = req.headers().get("accept") { |