Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement experimental registry HTTP API from RFC #8890

Closed
wants to merge 86 commits into from
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
ac37cc5
Lift make_dep_prefix so it can be shared
Nov 19, 2020
c92f34c
Implement registry HTTP API from RFC
Nov 19, 2020
37bd64d
Improve support for non-changelog HTTP registries
Nov 20, 2020
f04eee1
Share try_old_curl
Nov 20, 2020
6881d67
Tidy up significantly
Nov 20, 2020
c2b7ec7
Detect rfc+http as HTTP registry
Nov 21, 2020
e67ea79
Double-check anything that's not synchronized
Nov 21, 2020
4c1af67
Add more debug output
Nov 21, 2020
011e483
Handle directories that don't exist
Nov 21, 2020
ff86ee2
Allow empty lines in changelog
Nov 21, 2020
2220c19
A note about non-Range servers
Nov 21, 2020
3ec6f87
Add rudimentary testing for HTTP registry
Nov 21, 2020
184fa41
No changelog means no version means no cache
Nov 21, 2020
9527f00
Unify unknown and unsupported changelog states
Nov 21, 2020
572ceeb
More tests for HTTP registry
Nov 21, 2020
d9aed8a
Don't write JSON-encoded name to changelog
Nov 23, 2020
45dbbb7
All headers end with CRLF
Nov 23, 2020
3604da8
Don't overwrite last-updated if nothing changed
Nov 23, 2020
8f99f76
Adopt more tests from registry.rs
Nov 23, 2020
f9b1262
Push forward the greedy route
Nov 25, 2020
42453f5
Progress on greedy prefetching
Nov 25, 2020
5568cb6
Document fields
Nov 25, 2020
e29b21e
Boolean logic is hard
Nov 25, 2020
202258e
Fix interactions between prefetching and loads
Nov 25, 2020
acd3ee7
Give example crate for load source failure
Nov 25, 2020
b52740d
Make prefetch respect patches
Nov 25, 2020
5799fc8
Fix up expected output in path test
Nov 25, 2020
d0d4ccb
Improve docs for prefetching methods/types
Nov 25, 2020
e970a0c
Update tests to match new error messages
Nov 25, 2020
e1018d0
Fix overeager test fixing
Nov 25, 2020
8ccaf4f
Fix resolver-tests crate compile failure
Nov 26, 2020
9ad0ff6
Avoid UncanonicalizedIter during prefetch
Nov 26, 2020
6b1bd18
Keep better track of what has been downloaded
Nov 26, 2020
5d092de
Don't yield the same package many times
Nov 26, 2020
0e2ead6
New time format
Nov 30, 2020
f11440e
Avoid walking in circles
Nov 30, 2020
e1f96cc
Only allow HTTP registry under -Z http-registry
Nov 30, 2020
5345b83
Add prefetch progress tracking
Nov 30, 2020
cd7a553
Use Cargo.lock to seed prefetching
Nov 30, 2020
f97db19
Remove old note about pipelining
Nov 30, 2020
9a11d49
Only prefetch once per invocation
Dec 1, 2020
7535830
Fix tests to pass -Z flag and use 'nightly'
Dec 1, 2020
a385413
Fix more tests from hoisting prefetching phase
Dec 1, 2020
de13064
Use the right API def for resolver-tests
Dec 1, 2020
6c2e28f
Move to sparse+http and add index= support
Dec 1, 2020
15aa11f
Move RegistryData::load to &mut self
Dec 1, 2020
bad9d3c
Look for LAST_UPDATED in the right place
Dec 1, 2020
ee6abeb
Correct handling of 404s during prefetch
Dec 1, 2020
a9f80f8
Handle offline errors earlier
Dec 1, 2020
38ad840
Actually show a progress bar
Dec 1, 2020
508ffa6
Mention -Zhttp-registries in help output
Dec 2, 2020
517a3e2
Fix botched manual diff
Dec 2, 2020
c57c92c
Merge remote-tracking branch 'upstream/master' into http-registry
Dec 2, 2020
666895b
Better name for set of already-checked files
Dec 2, 2020
b41dfbb
Make sparse+ work in more places
Dec 2, 2020
3a71c4c
Keep iterating while there's work
Dec 2, 2020
165f01d
Don't fetch transitive dev-dependencies
Dec 2, 2020
1bcbc63
Add early top for dependency version walk
Dec 2, 2020
ced491a
Improve trace output and some comments
Dec 2, 2020
8098e60
Remove invalid optimization
Dec 2, 2020
3f746b3
Fetch config.json from the right directory
Dec 3, 2020
3a8b69d
Fix up alt_registry tests with registry+ prefix
Dec 3, 2020
7caae82
Merge remote-tracking branch 'upstream/master' into http-registry
Dec 3, 2020
fa296f0
Treat 403 as 404 from a privacy-sensitive server
Dec 3, 2020
88649de
Be more helpful about HTTP status code errors
Dec 3, 2020
bda120a
Remove the changelog optimization
Dec 3, 2020
7d1fef8
nits
Dec 4, 2020
cc87623
Avoid index updates if we can update just one file
Dec 4, 2020
c56c4d2
Avoid hitting assertion failures
Dec 4, 2020
7efa4da
All index paths should be lowercase
Dec 4, 2020
3c5c89b
Default to empty prefetch
Dec 9, 2020
a5e8b9f
Avoid extra indentation
Dec 9, 2020
a1383e0
Only be conditional on fields the server provided
Dec 9, 2020
5750fdf
Fix cut-off comment
Dec 9, 2020
e89e37c
Remove index files we get ~404 for
Dec 9, 2020
ad63b9d
Be more helpful about HTTP status code errors
Dec 9, 2020
8b8e3df
Spelling is hard
Dec 9, 2020
917f5d0
Assert we have valid utf-8 paths
Dec 9, 2020
cd5281d
Avoid duplicating crate download code
Dec 9, 2020
3382ddf
Avoid adding a new SourceId
Dec 9, 2020
7871824
Use masquerade_as_nightly
Dec 14, 2020
ee333d3
Suggest correct -Z name
Dec 14, 2020
f1f63cc
Make sparse+ an impl detail of RegistrySource
Dec 14, 2020
94ba59e
Add test for nightly-only
Dec 14, 2020
f9e35b3
Merge branch 'master' into http-registry
Dec 14, 2020
365e9f2
Do not prefetch w/o -Z http-registry
Dec 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions crates/cargo-test-support/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ use flate2::Compression;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::io::BufReader;
use std::net::{SocketAddr, TcpListener};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use tar::{Builder, Header};
use url::Url;

Expand Down Expand Up @@ -213,6 +218,231 @@ pub fn init() {
);
}

#[derive(Debug, Copy, Clone)]
pub enum RegistryServerConfiguration {
NoChangelog,
WithChangelog,
ChangelogNoRange,
}

pub struct RegistryServer {
done: Arc<AtomicBool>,
server: Option<thread::JoinHandle<()>>,
addr: SocketAddr,
}

impl RegistryServer {
pub fn addr(&self) -> SocketAddr {
self.addr
}
}

impl Drop for RegistryServer {
fn drop(&mut self) {
self.done.store(true, Ordering::SeqCst);
// NOTE: we can't actually await the server since it's blocked in accept()
let _ = self.server.take().unwrap();
}
}

#[must_use]
pub fn serve_registry(
registry_path: PathBuf,
config: RegistryServerConfiguration,
) -> RegistryServer {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
jonhoo marked this conversation as resolved.
Show resolved Hide resolved
let addr = listener.local_addr().unwrap();
let done = Arc::new(AtomicBool::new(false));
let done2 = done.clone();

let t = thread::spawn(move || {
let support_range = !matches!(config, RegistryServerConfiguration::ChangelogNoRange);

let mut line = String::new();
'server: while !done2.load(Ordering::SeqCst) {
let (socket, _) = listener.accept().unwrap();
// Let's implement a very naive static file HTTP server.
let mut buf = BufReader::new(socket);

// First, the request line:
// GET /path HTTPVERSION
line.clear();
if buf.read_line(&mut line).unwrap() == 0 {
// Connection terminated.
continue;
}

assert!(line.starts_with("GET "), "got non-GET request: {}", line);
let path = PathBuf::from(
line.split_whitespace()
.skip(1)
.next()
.unwrap()
.trim_start_matches('/'),
);

let file = registry_path.join(path);
let mut exists = file.exists();
if file.ends_with("changelog")
&& matches!(config, RegistryServerConfiguration::NoChangelog)
{
exists = false;
}

if exists {
// Grab some other headers we may care about.
let mut range = None;
let mut if_modified_since = None;
let mut if_none_match = None;
loop {
line.clear();
if buf.read_line(&mut line).unwrap() == 0 {
continue 'server;
}

if line == "\r\n" {
// End of headers.
line.clear();
break;
}

let value = line
.splitn(2, ':')
.skip(1)
.next()
.map(|v| v.trim())
.unwrap();

if line.starts_with("Range:") {
let value = value.strip_prefix("bytes=").unwrap_or(value);
if !value.is_empty() {
let mut parts = value.split('-');
let start = parts.next().unwrap().parse::<usize>().unwrap();
let end = parts.next().unwrap();
let end = if end.is_empty() {
None
} else {
Some(end.parse::<usize>().unwrap())
};
range = Some((start, end));
}
} else if line.starts_with("If-Modified-Since:") {
if_modified_since = Some(value.to_owned());
} else if line.starts_with("If-None-Match:") {
if_none_match = Some(value.trim_matches('"').to_owned());
}
}

// Now grab info about the file.
let data = fs::read(&file).unwrap();
let etag = Sha256::new().update(&data).finish_hex();
let last_modified = format!("{:?}", file.metadata().unwrap().modified().unwrap());

// Start to construct our response:
let mut any_match = false;
let mut all_match = true;
if let Some(expected) = if_none_match {
if etag != expected {
all_match = false;
} else {
any_match = true;
}
}
if let Some(expected) = if_modified_since {
// NOTE: Equality comparison is good enough for tests.
if last_modified != expected {
all_match = false;
} else {
any_match = true;
}
}
if any_match {
assert!(range.is_none());
}

// Write out the main response line.
let data_len = data.len();
let mut data = &data[..];
if any_match && all_match {
buf.get_mut()
.write_all(b"HTTP/1.1 304 Not Modified\r\n")
.unwrap();
} else if range.is_none() || !support_range {
buf.get_mut().write_all(b"HTTP/1.1 200 OK\r\n").unwrap();
} else if let Some((start, end)) = range {
if start >= data.len()
|| end.unwrap_or(0) >= data.len()
|| end.unwrap_or(start) <= start
{
buf.get_mut()
.write_all(b"HTTP/1.1 416 Range Not Satisfiable\r\n")
.unwrap();
} else {
buf.get_mut()
.write_all(b"HTTP/1.1 206 Partial Content\r\n")
.unwrap();

// Slice the data as requested and include a header indicating that.
// Note that start and end are both inclusive!
data = &data[start..=end.unwrap_or(data_len - 1)];
buf.get_mut()
.write_all(
format!(
"Content-Range: bytes {}-{}/{}\r\n",
start,
end.unwrap_or(data_len - 1),
data_len
)
.as_bytes(),
)
.unwrap();
}
}
// TODO: Support 451 for crate index deletions.

// Write out other headers.
buf.get_mut()
.write_all(format!("Content-Length: {}\r\n", data.len()).as_bytes())
.unwrap();
buf.get_mut()
.write_all(format!("ETag: \"{}\"\r\n", etag).as_bytes())
.unwrap();
buf.get_mut()
.write_all(format!("Last-Modified: {}\r\n", last_modified).as_bytes())
.unwrap();

// And finally, write out the body.
buf.get_mut().write_all(b"\r\n").unwrap();
buf.get_mut().write_all(data).unwrap();
} else {
loop {
line.clear();
if buf.read_line(&mut line).unwrap() == 0 {
// Connection terminated.
continue 'server;
}

if line == "\r\n" {
break;
}
}

buf.get_mut()
.write_all(b"HTTP/1.1 404 Not Found\r\n\r\n")
.unwrap();
buf.get_mut().write_all(b"\r\n").unwrap();
}
buf.get_mut().flush().unwrap();
}
});

RegistryServer {
addr,
server: Some(t),
done,
}
}

pub fn init_registry(registry_path: PathBuf, dl_url: String, api_url: Url, api_path: PathBuf) {
// Initialize a new registry.
repo(&registry_path)
Expand Down Expand Up @@ -454,6 +684,26 @@ impl Package {
t!(fs::create_dir_all(dst.parent().unwrap()));
t!(fs::write(&dst, prev + &line[..] + "\n"));

// Update changelog.
let dst = registry_path.join("changelog");
t!(fs::create_dir_all(dst.parent().unwrap()));
let mut epoch = 1;
if dst.exists() {
// Fish out the current epoch.
let prev = fs::read_to_string(&dst).unwrap_or_default();
let e = prev.split_whitespace().next().unwrap();
if !e.is_empty() {
epoch = e.parse::<usize>().unwrap();
}
}
let mut changelog = t!(fs::OpenOptions::new().append(true).create(true).open(dst));
t!(writeln!(
changelog,
"{} 2020-11-20 16:54:07 {}",
epoch, self.name
));
t!(changelog.flush());

// Add the new file to the index.
if !self.local {
let repo = t!(git2::Repository::open(&registry_path));
Expand Down
20 changes: 1 addition & 19 deletions src/cargo/core/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::time::{Duration, Instant};

use anyhow::Context;
use bytesize::ByteSize;
use curl::easy::{Easy, HttpVersion};
use curl::multi::{EasyHandle, Multi};
use lazycell::LazyCell;
use log::{debug, warn};
use log::debug;
use semver::Version;
use serde::Serialize;

Expand Down Expand Up @@ -579,23 +578,6 @@ impl<'cfg> PackageSet<'cfg> {
}
}

// When dynamically linked against libcurl, we want to ignore some failures
// when using old versions that don't support certain features.
macro_rules! try_old_curl {
($e:expr, $msg:expr) => {
let result = $e;
if cfg!(target_os = "macos") {
if let Err(e) = result {
warn!("ignoring libcurl {} error: {}", $msg, e);
}
} else {
result.with_context(|| {
anyhow::format_err!("failed to enable {}, is curl not built right?", $msg)
})?;
}
};
}

impl<'a, 'cfg> Downloads<'a, 'cfg> {
/// Starts to download the package for the `id` specified.
///
Expand Down
Loading