Skip to content

Commit 412b633

Browse files
committed
HTTP registry implementation
1 parent 109bfbd commit 412b633

File tree

18 files changed

+1807
-323
lines changed

18 files changed

+1807
-323
lines changed

crates/cargo-test-support/src/registry.rs

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ use std::collections::BTreeMap;
77
use std::fmt::Write as _;
88
use std::fs::{self, File};
99
use std::io::{BufRead, BufReader, Write};
10-
use std::net::TcpListener;
10+
use std::net::{SocketAddr, TcpListener};
1111
use std::path::{Path, PathBuf};
12+
use std::sync::atomic::{AtomicBool, Ordering};
13+
use std::sync::Arc;
1214
use std::thread;
1315
use tar::{Builder, Header};
1416
use url::Url;
@@ -368,6 +370,165 @@ pub fn alt_init() {
368370
RegistryBuilder::new().alternative(true).build();
369371
}
370372

373+
pub struct RegistryServer {
374+
done: Arc<AtomicBool>,
375+
server: Option<thread::JoinHandle<()>>,
376+
addr: SocketAddr,
377+
}
378+
379+
impl RegistryServer {
380+
pub fn addr(&self) -> SocketAddr {
381+
self.addr
382+
}
383+
}
384+
385+
impl Drop for RegistryServer {
386+
fn drop(&mut self) {
387+
self.done.store(true, Ordering::SeqCst);
388+
// NOTE: we can't actually await the server since it's blocked in accept()
389+
let _ = self.server.take();
390+
}
391+
}
392+
393+
#[must_use]
394+
pub fn serve_registry(registry_path: PathBuf) -> RegistryServer {
395+
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
396+
let addr = listener.local_addr().unwrap();
397+
let done = Arc::new(AtomicBool::new(false));
398+
let done2 = done.clone();
399+
400+
let t = thread::spawn(move || {
401+
let mut line = String::new();
402+
'server: while !done2.load(Ordering::SeqCst) {
403+
let (socket, _) = listener.accept().unwrap();
404+
// Let's implement a very naive static file HTTP server.
405+
let mut buf = BufReader::new(socket);
406+
407+
// First, the request line:
408+
// GET /path HTTPVERSION
409+
line.clear();
410+
if buf.read_line(&mut line).unwrap() == 0 {
411+
// Connection terminated.
412+
continue;
413+
}
414+
415+
assert!(line.starts_with("GET "), "got non-GET request: {}", line);
416+
let path = PathBuf::from(
417+
line.split_whitespace()
418+
.skip(1)
419+
.next()
420+
.unwrap()
421+
.trim_start_matches('/'),
422+
);
423+
424+
let file = registry_path.join(path);
425+
if file.exists() {
426+
// Grab some other headers we may care about.
427+
let mut if_modified_since = None;
428+
let mut if_none_match = None;
429+
loop {
430+
line.clear();
431+
if buf.read_line(&mut line).unwrap() == 0 {
432+
continue 'server;
433+
}
434+
435+
if line == "\r\n" {
436+
// End of headers.
437+
line.clear();
438+
break;
439+
}
440+
441+
let value = line
442+
.splitn(2, ':')
443+
.skip(1)
444+
.next()
445+
.map(|v| v.trim())
446+
.unwrap();
447+
448+
if line.starts_with("If-Modified-Since:") {
449+
if_modified_since = Some(value.to_owned());
450+
} else if line.starts_with("If-None-Match:") {
451+
if_none_match = Some(value.trim_matches('"').to_owned());
452+
}
453+
}
454+
455+
// Now grab info about the file.
456+
let data = fs::read(&file).unwrap();
457+
let etag = Sha256::new().update(&data).finish_hex();
458+
let last_modified = format!("{:?}", file.metadata().unwrap().modified().unwrap());
459+
460+
// Start to construct our response:
461+
let mut any_match = false;
462+
let mut all_match = true;
463+
if let Some(expected) = if_none_match {
464+
if etag != expected {
465+
all_match = false;
466+
} else {
467+
any_match = true;
468+
}
469+
}
470+
if let Some(expected) = if_modified_since {
471+
// NOTE: Equality comparison is good enough for tests.
472+
if last_modified != expected {
473+
all_match = false;
474+
} else {
475+
any_match = true;
476+
}
477+
}
478+
479+
// Write out the main response line.
480+
if any_match && all_match {
481+
buf.get_mut()
482+
.write_all(b"HTTP/1.1 304 Not Modified\r\n")
483+
.unwrap();
484+
} else {
485+
buf.get_mut().write_all(b"HTTP/1.1 200 OK\r\n").unwrap();
486+
}
487+
// TODO: Support 451 for crate index deletions.
488+
489+
// Write out other headers.
490+
buf.get_mut()
491+
.write_all(format!("Content-Length: {}\r\n", data.len()).as_bytes())
492+
.unwrap();
493+
buf.get_mut()
494+
.write_all(format!("ETag: \"{}\"\r\n", etag).as_bytes())
495+
.unwrap();
496+
buf.get_mut()
497+
.write_all(format!("Last-Modified: {}\r\n", last_modified).as_bytes())
498+
.unwrap();
499+
500+
// And finally, write out the body.
501+
buf.get_mut().write_all(b"\r\n").unwrap();
502+
buf.get_mut().write_all(&data).unwrap();
503+
} else {
504+
loop {
505+
line.clear();
506+
if buf.read_line(&mut line).unwrap() == 0 {
507+
// Connection terminated.
508+
continue 'server;
509+
}
510+
511+
if line == "\r\n" {
512+
break;
513+
}
514+
}
515+
516+
buf.get_mut()
517+
.write_all(b"HTTP/1.1 404 Not Found\r\n\r\n")
518+
.unwrap();
519+
buf.get_mut().write_all(b"\r\n").unwrap();
520+
}
521+
buf.get_mut().flush().unwrap();
522+
}
523+
});
524+
525+
RegistryServer {
526+
addr,
527+
server: Some(t),
528+
done,
529+
}
530+
}
531+
371532
/// Creates a new on-disk registry.
372533
pub fn init_registry(registry_path: PathBuf, dl_url: String, api_url: Url, api_path: PathBuf) {
373534
// Initialize a new registry.

src/cargo/core/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@ unstable_cli_options!(
650650
no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
651651
panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
652652
host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"),
653+
http_registry: bool = ("Support HTTP-based crate registries"),
653654
target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
654655
rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"),
655656
separate_nightlies: bool = (HIDDEN),
@@ -875,6 +876,7 @@ impl CliUnstable {
875876
"multitarget" => self.multitarget = parse_empty(k, v)?,
876877
"rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
877878
"terminal-width" => self.terminal_width = Some(parse_usize_opt(v)?),
879+
"http-registry" => self.http_registry = parse_empty(k, v)?,
878880
"namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES),
879881
"weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES),
880882
"credential-process" => self.credential_process = parse_empty(k, v)?,

src/cargo/core/package.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,6 @@ impl<'cfg> PackageSet<'cfg> {
405405
) -> CargoResult<PackageSet<'cfg>> {
406406
// We've enabled the `http2` feature of `curl` in Cargo, so treat
407407
// failures here as fatal as it would indicate a build-time problem.
408-
//
409-
// Note that the multiplexing support is pretty new so we're having it
410-
// off-by-default temporarily.
411-
//
412-
// Also note that pipelining is disabled as curl authors have indicated
413-
// that it's buggy, and we've empirically seen that it's buggy with HTTP
414-
// proxies.
415408
let mut multi = Multi::new();
416409
let multiplexing = config.http_config()?.multiplexing.unwrap_or(true);
417410
multi
@@ -700,7 +693,7 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> {
700693
return Ok(Some(pkg));
701694
}
702695

703-
// Ask the original source fo this `PackageId` for the corresponding
696+
// Ask the original source for this `PackageId` for the corresponding
704697
// package. That may immediately come back and tell us that the package
705698
// is ready, or it could tell us that it needs to be downloaded.
706699
let mut sources = self.set.sources.borrow_mut();
@@ -757,7 +750,7 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> {
757750
// initiate dozens of connections to crates.io, but rather only one.
758751
// Once the main one is opened we realized that pipelining is possible
759752
// and multiplexing is possible with static.crates.io. All in all this
760-
// reduces the number of connections done to a more manageable state.
753+
// reduces the number of connections down to a more manageable state.
761754
try_old_curl!(handle.pipewait(true), "pipewait");
762755

763756
handle.write_function(move |buf| {

src/cargo/core/registry.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ impl<'cfg> PackageRegistry<'cfg> {
180180
}
181181

182182
self.load(namespace, kind)?;
183+
184+
// This isn't strictly necessary since it will be called later.
185+
// However it improves error messages for sources that issue errors
186+
// in `block_until_ready` because the callers here have context about
187+
// which deps are being resolved.
183188
self.block_until_ready()?;
184189
Ok(())
185190
}
@@ -273,7 +278,7 @@ impl<'cfg> PackageRegistry<'cfg> {
273278
// First up we need to actually resolve each `deps` specification to
274279
// precisely one summary. We're not using the `query` method below as it
275280
// internally uses maps we're building up as part of this method
276-
// (`patches_available` and `patches). Instead we're going straight to
281+
// (`patches_available` and `patches`). Instead we're going straight to
277282
// the source to load information from it.
278283
//
279284
// Remember that each dependency listed in `[patch]` has to resolve to

src/cargo/core/source/source_id.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ impl SourceId {
135135
Ok(SourceId::new(SourceKind::Registry, url, None)?
136136
.with_precise(Some("locked".to_string())))
137137
}
138+
"sparse" => {
139+
let url = string.into_url()?;
140+
Ok(SourceId::new(SourceKind::Registry, url, None)?
141+
.with_precise(Some("locked".to_string())))
142+
}
138143
"path" => {
139144
let url = url.into_url()?;
140145
SourceId::new(SourceKind::Path, url, None)
@@ -301,7 +306,7 @@ impl SourceId {
301306
self,
302307
yanked_whitelist,
303308
config,
304-
))),
309+
)?)),
305310
SourceKind::LocalRegistry => {
306311
let path = match self.inner.url.to_file_path() {
307312
Ok(p) => p,

src/cargo/ops/registry.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ fn registry(
459459
}
460460
let api_host = {
461461
let _lock = config.acquire_package_cache_lock()?;
462-
let mut src = RegistrySource::remote(sid, &HashSet::new(), config);
462+
let mut src = RegistrySource::remote(sid, &HashSet::new(), config)?;
463463
// Only update the index if the config is not available or `force` is set.
464464
if force_update {
465465
src.invalidate_cache()
@@ -528,8 +528,11 @@ pub fn http_handle_and_timeout(config: &Config) -> CargoResult<(Easy, HttpTimeou
528528
specified"
529529
)
530530
}
531-
if !config.network_allowed() {
532-
bail!("can't make HTTP request in the offline mode")
531+
if config.offline() {
532+
bail!(
533+
"attempting to make an HTTP request, but --offline was \
534+
specified"
535+
)
533536
}
534537

535538
// The timeout option for libcurl by default times out the entire transfer,

src/cargo/sources/path.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -498,9 +498,7 @@ impl<'cfg> Debug for PathSource<'cfg> {
498498

499499
impl<'cfg> Source for PathSource<'cfg> {
500500
fn query(&mut self, dep: &Dependency, f: &mut dyn FnMut(Summary)) -> Poll<CargoResult<()>> {
501-
if !self.updated {
502-
return Poll::Pending;
503-
}
501+
self.update()?;
504502
for s in self.packages.iter().map(|p| p.summary()) {
505503
if dep.matches(s) {
506504
f(s.clone())
@@ -514,9 +512,7 @@ impl<'cfg> Source for PathSource<'cfg> {
514512
_dep: &Dependency,
515513
f: &mut dyn FnMut(Summary),
516514
) -> Poll<CargoResult<()>> {
517-
if !self.updated {
518-
return Poll::Pending;
519-
}
515+
self.update()?;
520516
for s in self.packages.iter().map(|p| p.summary()) {
521517
f(s.clone())
522518
}
@@ -537,7 +533,7 @@ impl<'cfg> Source for PathSource<'cfg> {
537533

538534
fn download(&mut self, id: PackageId) -> CargoResult<MaybePackage> {
539535
trace!("getting packages; id={}", id);
540-
536+
self.update()?;
541537
let pkg = self.packages.iter().find(|pkg| pkg.package_id() == id);
542538
pkg.cloned()
543539
.map(MaybePackage::Ready)

0 commit comments

Comments
 (0)