@@ -7,8 +7,10 @@ use std::collections::BTreeMap;
7
7
use std:: fmt:: Write as _;
8
8
use std:: fs:: { self , File } ;
9
9
use std:: io:: { BufRead , BufReader , Write } ;
10
- use std:: net:: TcpListener ;
10
+ use std:: net:: { SocketAddr , TcpListener } ;
11
11
use std:: path:: { Path , PathBuf } ;
12
+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
13
+ use std:: sync:: Arc ;
12
14
use std:: thread;
13
15
use tar:: { Builder , Header } ;
14
16
use url:: Url ;
@@ -368,6 +370,165 @@ pub fn alt_init() {
368
370
RegistryBuilder :: new ( ) . alternative ( true ) . build ( ) ;
369
371
}
370
372
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
+
371
532
/// Creates a new on-disk registry.
372
533
pub fn init_registry ( registry_path : PathBuf , dl_url : String , api_url : Url , api_path : PathBuf ) {
373
534
// Initialize a new registry.
0 commit comments