solana/cargo-registry/src/main.rs

290 lines
9.8 KiB
Rust

//! The `registry_service` module implements the Solana cargo registry service.
use {
crate::{
client::Client,
crate_handler::{Error, Program, UnpackedCrate},
sparse_index::RegistryIndex,
},
hyper::{
body,
service::{make_service_fn, service_fn},
Method, Server,
},
log::*,
std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
sync::Arc,
},
};
mod client;
mod crate_handler;
mod response_builder;
mod sparse_index;
const PATH_PREFIX: &str = "/api/v1/crates";
pub struct CargoRegistryService {}
impl CargoRegistryService {
async fn handle_publish_request(
request: hyper::Request<hyper::Body>,
client: Arc<Client>,
index: Arc<RegistryIndex>,
) -> hyper::Response<hyper::Body> {
info!("Handling request to publish the crate");
let bytes = body::to_bytes(request.into_body()).await;
match bytes {
Ok(data) => {
let Ok(unpacked_crate) = UnpackedCrate::new(data) else {
return response_builder::error_response(
hyper::StatusCode::INTERNAL_SERVER_ERROR,
"Failed to parse the crate information",
);
};
let Ok(result) =
tokio::task::spawn_blocking(move || unpacked_crate.publish(client, index))
.await
else {
return response_builder::error_response(
hyper::StatusCode::INTERNAL_SERVER_ERROR,
"Internal error. Failed to wait for program deployment",
);
};
if result.is_ok() {
info!("Published the crate successfully. {:?}", result);
response_builder::success_response()
} else {
response_builder::error_response(
hyper::StatusCode::BAD_REQUEST,
format!("Failed to publish the crate. {:?}", result).as_str(),
)
}
}
Err(_) => response_builder::error_response(
hyper::StatusCode::BAD_REQUEST,
"Failed to receive the crate data from the client.",
),
}
}
fn get_crate_name_and_version(path: &str) -> Option<(&str, &str, &str)> {
path.rsplit_once('/').and_then(|(remainder, version)| {
remainder
.rsplit_once('/')
.map(|(remainder, name)| (remainder, name, version))
})
}
fn handle_download_crate_request(
path: &str,
_request: &hyper::Request<hyper::Body>,
client: Arc<Client>,
) -> hyper::Response<hyper::Body> {
let Some((path, crate_name, version)) = Self::get_crate_name_and_version(path) else {
return response_builder::error_in_parsing();
};
if path.len() != PATH_PREFIX.len() {
return response_builder::error_incorrect_length();
}
let package = Program::crate_name_to_program_id(crate_name)
.and_then(|id| UnpackedCrate::fetch(id, version, client).ok());
// Return the package to the caller in the response
if let Some((package, _meta)) = package {
response_builder::success_response_bytes(package.0)
} else {
response_builder::error_response(
hyper::StatusCode::BAD_REQUEST,
"Failed to find the package",
)
}
}
fn handle_yank_request(
path: &str,
_request: &hyper::Request<hyper::Body>,
) -> hyper::Response<hyper::Body> {
let Some((path, _crate_name, _version)) = Self::get_crate_name_and_version(path) else {
return response_builder::error_in_parsing();
};
if path.len() != PATH_PREFIX.len() {
return response_builder::error_incorrect_length();
}
response_builder::error_not_implemented()
}
fn handle_unyank_request(
path: &str,
_request: &hyper::Request<hyper::Body>,
) -> hyper::Response<hyper::Body> {
let Some((path, _crate_name, _version)) = Self::get_crate_name_and_version(path) else {
return response_builder::error_in_parsing();
};
if path.len() != PATH_PREFIX.len() {
return response_builder::error_incorrect_length();
}
response_builder::error_not_implemented()
}
fn get_crate_name(path: &str) -> Option<(&str, &str)> {
path.rsplit_once('/')
}
fn handle_get_owners_request(
path: &str,
_request: &hyper::Request<hyper::Body>,
) -> hyper::Response<hyper::Body> {
let Some((path, _crate_name)) = Self::get_crate_name(path) else {
return response_builder::error_in_parsing();
};
if path.len() != PATH_PREFIX.len() {
return response_builder::error_incorrect_length();
}
response_builder::error_not_implemented()
}
fn handle_add_owners_request(
path: &str,
_request: &hyper::Request<hyper::Body>,
) -> hyper::Response<hyper::Body> {
let Some((path, _crate_name)) = Self::get_crate_name(path) else {
return response_builder::error_in_parsing();
};
if path.len() != PATH_PREFIX.len() {
return response_builder::error_incorrect_length();
}
response_builder::error_not_implemented()
}
fn handle_delete_owners_request(
path: &str,
_request: &hyper::Request<hyper::Body>,
) -> hyper::Response<hyper::Body> {
let Some((path, _crate_name)) = Self::get_crate_name(path) else {
return response_builder::error_in_parsing();
};
if path.len() != PATH_PREFIX.len() {
return response_builder::error_incorrect_length();
}
response_builder::error_not_implemented()
}
fn handle_get_crates_request(
path: &str,
_request: &hyper::Request<hyper::Body>,
) -> hyper::Response<hyper::Body> {
// The endpoint for this type of request is `/api/v1/crates` (same as PATH_PREFIX).
// The `crates` substring has already been extracted out of the endpoint string.
// So the path should only contain `/api/v1". The caller already checked that the
// full path started with PATH_PREFIX. So it's sufficient to check that provided
// path is smaller than PATH_PREFIX.
if path.len() >= PATH_PREFIX.len() {
return response_builder::error_incorrect_length();
}
response_builder::error_not_implemented()
}
async fn handler(
index: Arc<sparse_index::RegistryIndex>,
request: hyper::Request<hyper::Body>,
client: Arc<Client>,
) -> Result<hyper::Response<hyper::Body>, Error> {
let path = request.uri().path();
if path.starts_with("/git") {
return Ok(response_builder::error_response(
hyper::StatusCode::BAD_REQUEST,
"This registry server does not support GIT index. Please use sparse index.",
));
}
if path.starts_with(index.index_root.as_str()) {
return Ok(index.handler(request, client.clone()));
}
if !path.starts_with(PATH_PREFIX) {
return Ok(response_builder::error_response(
hyper::StatusCode::BAD_REQUEST,
"Invalid path for the request",
));
}
let Some((path, endpoint)) = path.rsplit_once('/') else {
return Ok(response_builder::error_response(
hyper::StatusCode::BAD_REQUEST,
"Invalid endpoint in the path",
));
};
Ok(match *request.method() {
Method::PUT => match endpoint {
"new" => {
if path.len() != PATH_PREFIX.len() {
response_builder::error_incorrect_length()
} else {
Self::handle_publish_request(request, client.clone(), index.clone()).await
}
}
"unyank" => Self::handle_unyank_request(path, &request),
"owners" => Self::handle_add_owners_request(path, &request),
_ => response_builder::error_not_allowed(),
},
Method::GET => match endpoint {
"crates" => Self::handle_get_crates_request(path, &request),
"owners" => Self::handle_get_owners_request(path, &request),
"download" => Self::handle_download_crate_request(path, &request, client.clone()),
_ => response_builder::error_not_allowed(),
},
Method::DELETE => match endpoint {
"yank" => Self::handle_yank_request(path, &request),
"owners" => Self::handle_delete_owners_request(path, &request),
_ => response_builder::error_not_allowed(),
},
_ => response_builder::error_not_allowed(),
})
}
}
#[tokio::main]
async fn main() {
solana_logger::setup_with_default("solana=info");
let client = Arc::new(Client::new().expect("Failed to get RPC Client instance"));
let bind_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), client.port);
let index = Arc::new(sparse_index::RegistryIndex::new(
"/index",
&client.server_url,
));
let registry_service = make_service_fn(move |_| {
let client_inner = client.clone();
let index = index.clone();
async move {
Ok::<_, Error>(service_fn(move |request| {
CargoRegistryService::handler(index.clone(), request, client_inner.clone())
}))
}
});
let server = Server::bind(&bind_addr).serve(registry_service);
info!("Server running on http://{}", bind_addr);
let _ = server.await;
}