@@ -7,8 +7,10 @@ use std::collections::BTreeMap;
77use std:: fmt:: Write as _;
88use std:: fs:: { self , File } ;
99use std:: io:: { BufRead , BufReader , Write } ;
10- use std:: net:: TcpListener ;
10+ use std:: net:: { SocketAddr , TcpListener } ;
1111use std:: path:: { Path , PathBuf } ;
12+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
13+ use std:: sync:: Arc ;
1214use std:: thread;
1315use tar:: { Builder , Header } ;
1416use 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.
372533pub fn init_registry ( registry_path : PathBuf , dl_url : String , api_url : Url , api_path : PathBuf ) {
373534 // Initialize a new registry.
0 commit comments