183 lines
6.0 KiB
Rust
183 lines
6.0 KiB
Rust
use std::{path::PathBuf, time::Duration};
|
|
|
|
use actix_web::{Error, HttpRequest, HttpResponse, web};
|
|
use tokio::time::timeout;
|
|
|
|
use crate::{
|
|
http::{
|
|
HttpAppState, auth::authorize_repo_access, handler::GitHttpHandler,
|
|
utils::get_repo_model,
|
|
},
|
|
sync::{
|
|
RepoReceiveSyncTask,
|
|
push_queue::{
|
|
PushQueueEvent, PushQueueWaitError, wait_for_push_queue_slot,
|
|
},
|
|
},
|
|
};
|
|
|
|
#[tracing::instrument(skip(req, state), fields(namespace = %path.0, repo = %path.1))]
|
|
pub async fn info_refs(
|
|
req: HttpRequest,
|
|
path: web::Path<(String, String)>,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
if !state.rate_limiter.is_read_allowed().await {
|
|
return Err(actix_web::error::ErrorTooManyRequests(
|
|
"Rate limit exceeded",
|
|
));
|
|
}
|
|
|
|
let service_param = req
|
|
.query_string()
|
|
.split('&')
|
|
.find(|s| s.starts_with("service="))
|
|
.and_then(|s| s.strip_prefix("service="))
|
|
.ok_or_else(|| {
|
|
actix_web::error::ErrorBadRequest("Missing service parameter")
|
|
})?;
|
|
|
|
if service_param != "git-upload-pack" && service_param != "git-receive-pack"
|
|
{
|
|
return Err(actix_web::error::ErrorBadRequest("Invalid service"));
|
|
}
|
|
|
|
let path_inner = path.into_inner();
|
|
let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
|
|
let is_write = service_param == "git-receive-pack";
|
|
authorize_repo_access(&req, &state.db, &model, is_write).await?;
|
|
|
|
let storage_path = PathBuf::from(&model.storage_path);
|
|
let handler = GitHttpHandler::new(storage_path, model, state.db.clone());
|
|
handler.info_refs(service_param).await
|
|
}
|
|
|
|
#[tracing::instrument(skip(req, payload, state), fields(namespace = %path.0, repo = %path.1))]
|
|
pub async fn upload_pack(
|
|
req: HttpRequest,
|
|
path: web::Path<(String, String)>,
|
|
payload: web::Payload,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
if !state.rate_limiter.is_read_allowed().await {
|
|
return Err(actix_web::error::ErrorTooManyRequests(
|
|
"Rate limit exceeded",
|
|
));
|
|
}
|
|
|
|
let path_inner = path.into_inner();
|
|
let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
|
|
authorize_repo_access(&req, &state.db, &model, false).await?;
|
|
|
|
let storage_path = PathBuf::from(&model.storage_path);
|
|
let handler = GitHttpHandler::new(storage_path, model, state.db.clone());
|
|
handler.upload_pack(payload).await
|
|
}
|
|
|
|
#[tracing::instrument(skip(req, payload, state), fields(namespace = %path.0, repo = %path.1))]
|
|
pub async fn receive_pack(
|
|
req: HttpRequest,
|
|
path: web::Path<(String, String)>,
|
|
payload: web::Payload,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
if !state.rate_limiter.is_write_allowed().await {
|
|
return Err(actix_web::error::ErrorTooManyRequests(
|
|
"Rate limit exceeded",
|
|
));
|
|
}
|
|
|
|
let path_inner = path.into_inner();
|
|
let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
|
|
authorize_repo_access(&req, &state.db, &model, true).await?;
|
|
|
|
let mut push_queue_lease = match wait_for_push_queue_slot(
|
|
state.sync.clone(),
|
|
model.id,
|
|
|event, request_id| {
|
|
let request_id = request_id.to_string();
|
|
let repo_name = model.name.clone();
|
|
let repo_id = model.id;
|
|
match event {
|
|
PushQueueEvent::Waiting(position) => {
|
|
tracing::info!(
|
|
repo = %repo_name,
|
|
repo_id = %repo_id,
|
|
request_id = %request_id,
|
|
position = position.position,
|
|
total = position.total,
|
|
"http_push_queue_waiting"
|
|
);
|
|
}
|
|
PushQueueEvent::Acquired => {
|
|
tracing::info!(
|
|
repo = %repo_name,
|
|
repo_id = %repo_id,
|
|
request_id = %request_id,
|
|
"http_push_queue_acquired"
|
|
);
|
|
}
|
|
}
|
|
},
|
|
)
|
|
.await
|
|
{
|
|
Ok(lease) => lease,
|
|
Err(PushQueueWaitError::Join(e)) => {
|
|
tracing::error!(
|
|
error = %e,
|
|
repo = %model.name,
|
|
repo_id = %model.id,
|
|
"http_push_queue_join_failed"
|
|
);
|
|
return Err(actix_web::error::ErrorServiceUnavailable(
|
|
"Push queue is temporarily unavailable. Please retry later.",
|
|
));
|
|
}
|
|
Err(PushQueueWaitError::Lock(e)) => {
|
|
tracing::error!(
|
|
error = %e,
|
|
repo = %model.name,
|
|
repo_id = %model.id,
|
|
"http_push_queue_lock_failed"
|
|
);
|
|
return Err(actix_web::error::ErrorServiceUnavailable(
|
|
"Push queue lock failed. Please retry later.",
|
|
));
|
|
}
|
|
Err(PushQueueWaitError::Timeout) => {
|
|
tracing::info!(
|
|
repo = %model.name,
|
|
repo_id = %model.id,
|
|
"http_push_queue_timeout"
|
|
);
|
|
return Ok(HttpResponse::ServiceUnavailable()
|
|
.insert_header(("Retry-After", "5"))
|
|
.content_type("text/plain; charset=utf-8")
|
|
.body("Push queue timed out. Please retry in a moment.\n"));
|
|
}
|
|
};
|
|
|
|
let storage_path = PathBuf::from(&model.storage_path);
|
|
let handler =
|
|
GitHttpHandler::new(storage_path, model.clone(), state.db.clone());
|
|
let result = handler.receive_pack(payload).await;
|
|
push_queue_lease.release().await;
|
|
|
|
if result.is_ok() {
|
|
let _ = tokio::spawn({
|
|
let sync = state.sync.clone();
|
|
let repo_uid = model.id;
|
|
async move {
|
|
let _ = timeout(
|
|
Duration::from_secs(5),
|
|
sync.send(RepoReceiveSyncTask { repo_uid }),
|
|
)
|
|
.await;
|
|
}
|
|
});
|
|
}
|
|
|
|
result
|
|
}
|