gitdataai/libs/git/http/mod.rs
ZhenYi cef4ff1289 fix(git): harden HTTP and SSH git transports for robustness
HTTP:
- Return Err(...) instead of Ok(HttpResponse::...) for error cases so
  actix returns correct HTTP status codes instead of 200
- Add 30s timeout on info_refs and handle_git_rpc git subprocess calls
- Add 1MB pre-PACK limit to prevent memory exhaustion on receive-pack
- Enforce branch protection rules (forbid push/force-push/deletion/tag)
- Simplify graceful shutdown (remove manual signal handling)

SSH:
- Fix build_git_command: use block match arms so chained .arg() calls
  are on the Command, not the match expression's () result
- Add MAX_RETRIES=5 to forward() data-pump loop to prevent infinite
  spin on persistent network failures
- Fall back to raw path if canonicalize() fails instead of panicking
- Add platform-specific git config paths (/dev/null on unix, NUL on win)
- Start rate limiter cleanup background task so HashMap doesn't grow
  unbounded over time
- Derive Clone on RateLimiter so SshRateLimiter::start_cleanup works
2026-04-16 20:11:18 +08:00

110 lines
3.1 KiB
Rust

use actix_web::{App, HttpServer, web};
use config::AppConfig;
use db::cache::AppCache;
use db::database::AppDatabase;
use slog::{Logger, error, info};
use std::sync::Arc;
pub mod auth;
pub mod handler;
pub mod lfs;
pub mod lfs_routes;
pub mod rate_limit;
pub mod routes;
pub mod utils;
#[derive(Clone)]
pub struct HttpAppState {
pub db: AppDatabase,
pub cache: AppCache,
pub sync: crate::ssh::ReceiveSyncService,
pub rate_limiter: Arc<rate_limit::RateLimiter>,
pub logger: Logger,
}
pub fn git_http_cfg(cfg: &mut web::ServiceConfig) {
cfg.route(
"/{namespace}/{repo_name}.git/info/refs",
web::get().to(routes::info_refs),
)
.route(
"/{namespace}/{repo_name}.git/git-upload-pack",
web::post().to(routes::upload_pack),
)
.route(
"/{namespace}/{repo_name}.git/git-receive-pack",
web::post().to(routes::receive_pack),
)
.route(
"/{namespace}/{repo_name}.git/info/lfs/objects/batch",
web::post().to(lfs_routes::lfs_batch),
)
.route(
"/{namespace}/{repo_name}.git/info/lfs/objects/{oid}",
web::put().to(lfs_routes::lfs_upload),
)
.route(
"/{namespace}/{repo_name}.git/info/lfs/objects/{oid}",
web::get().to(lfs_routes::lfs_download),
)
.route(
"/{namespace}/{repo_name}.git/info/lfs/locks",
web::post().to(lfs_routes::lfs_lock_create),
)
.route(
"/{namespace}/{repo_name}.git/info/lfs/locks",
web::get().to(lfs_routes::lfs_lock_list),
)
.route(
"/{namespace}/{repo_name}.git/info/lfs/locks/{id}",
web::get().to(lfs_routes::lfs_lock_get),
)
.route(
"/{namespace}/{repo_name}.git/info/lfs/locks/{id}",
web::delete().to(lfs_routes::lfs_lock_delete),
);
}
pub async fn run_http(config: AppConfig, logger: Logger) -> anyhow::Result<()> {
let (db, app_cache) = tokio::join!(AppDatabase::init(&config), AppCache::init(&config),);
let db = db?;
let app_cache = app_cache?;
let redis_pool = app_cache.redis_pool().clone();
let sync = crate::ssh::ReceiveSyncService::new(redis_pool, logger.clone());
let rate_limiter = Arc::new(rate_limit::RateLimiter::new(
rate_limit::RateLimitConfig::default(),
));
let _cleanup = rate_limiter.clone().start_cleanup();
let state = HttpAppState {
db: db.clone(),
cache: app_cache.clone(),
sync,
rate_limiter,
logger: logger.clone(),
};
let logger_startup = logger.clone();
info!(&logger_startup, "Starting git HTTP server on 0.0.0.0:8021");
let server = HttpServer::new(move || {
App::new()
.app_data(web::Data::new(state.clone()))
.configure(git_http_cfg)
})
.bind("0.0.0.0:8021")?
.run();
// Await the server. Actix-web handles Ctrl+C gracefully by default:
// workers finish in-flight requests then exit (graceful shutdown).
let result = server.await;
if let Err(e) = result {
error!(&logger, "HTTP server error: {}", e);
}
info!(&logger, "Git HTTP server stopped");
Ok(())
}