3939import java .util .concurrent .CompletableFuture ;
4040import java .util .concurrent .ExecutorService ;
4141import java .util .concurrent .Executors ;
42+ import java .util .function .BiFunction ;
4243import java .util .function .Supplier ;
4344import land .oras .auth .AuthProvider ;
4445import land .oras .auth .AuthStoreAuthenticationProvider ;
@@ -526,9 +527,16 @@ public void deleteBlob(ContainerRef containerRef) {
526527
527528 @ Override
528529 public void pullArtifact (ContainerRef containerRef , Path path , boolean overwrite ) {
530+ withMirrorFallback (containerRef , (reg , ref ) -> {
531+ reg .pullArtifactDirect (ref , path , overwrite );
532+ return null ;
533+ });
534+ }
535+
536+ private void pullArtifactDirect (ContainerRef containerRef , Path path , boolean overwrite ) {
529537 ContainerRef ref = containerRef .forRegistry (this ).checkBlocked (this );
530538 if (ref .isInsecure (this ) && !this .isInsecure ()) {
531- asInsecure ().pullArtifact (containerRef , path , overwrite );
539+ asInsecure ().pullArtifactDirect (containerRef , path , overwrite );
532540 return ;
533541 }
534542 // Only collect layer that are files
@@ -945,9 +953,13 @@ private HttpClient.ResponseWrapper<String> headBlob(ContainerRef containerRef) {
945953 */
946954 @ Override
947955 public byte [] getBlob (ContainerRef containerRef ) {
956+ return withMirrorFallback (containerRef , (reg , ref ) -> reg .getBlobDirect (ref ));
957+ }
958+
959+ private byte [] getBlobDirect (ContainerRef containerRef ) {
948960 ContainerRef ref = containerRef .forRegistry (this ).checkBlocked (this );
949961 if (ref .isInsecure (this ) && !this .isInsecure ()) {
950- return asInsecure ().getBlob (containerRef );
962+ return asInsecure ().getBlobDirect (containerRef );
951963 }
952964 URI uri = URI .create ("%s://%s" .formatted (getScheme (), ref .getBlobsPath (this )));
953965 HttpClient .ResponseWrapper <String > response = client .get (
@@ -964,9 +976,16 @@ public byte[] getBlob(ContainerRef containerRef) {
964976
965977 @ Override
966978 public void fetchBlob (ContainerRef containerRef , Path path ) {
979+ withMirrorFallback (containerRef , (reg , ref ) -> {
980+ reg .fetchBlobDirect (ref , path );
981+ return null ;
982+ });
983+ }
984+
985+ private void fetchBlobDirect (ContainerRef containerRef , Path path ) {
967986 ContainerRef ref = containerRef .forRegistry (this ).checkBlocked (this );
968987 if (ref .isInsecure (this ) && !this .isInsecure ()) {
969- asInsecure ().fetchBlob (containerRef , path );
988+ asInsecure ().fetchBlobDirect (containerRef , path );
970989 return ;
971990 }
972991 URI uri = URI .create ("%s://%s" .formatted (getScheme (), ref .getBlobsPath (this )));
@@ -983,9 +1002,13 @@ public void fetchBlob(ContainerRef containerRef, Path path) {
9831002
9841003 @ Override
9851004 public InputStream fetchBlob (ContainerRef containerRef ) {
1005+ return withMirrorFallback (containerRef , (reg , ref ) -> reg .fetchBlobDirect (ref ));
1006+ }
1007+
1008+ private InputStream fetchBlobDirect (ContainerRef containerRef ) {
9861009 ContainerRef ref = containerRef .forRegistry (this ).checkBlocked (this );
9871010 if (ref .isInsecure (this ) && !this .isInsecure ()) {
988- return asInsecure ().fetchBlob (containerRef );
1011+ return asInsecure ().fetchBlobDirect (containerRef );
9891012 }
9901013 URI uri = URI .create ("%s://%s" .formatted (getScheme (), ref .getBlobsPath (this )));
9911014 HttpClient .ResponseWrapper <InputStream > response = client .download (
@@ -1106,14 +1129,50 @@ boolean exists(ContainerRef containerRef) {
11061129 }
11071130
11081131 /**
1109- * Get a manifest response
1132+ * Execute an operation with mirror fallback. Mirrors are tried in order; if all fail the
1133+ * operation is retried against the original registry.
1134+ * @param containerRef The original container reference used to look up mirrors
1135+ * @param operation A function (registry, ref) → result; called for each candidate
1136+ * @return The result from the first successful invocation
1137+ */
1138+ private <T > T withMirrorFallback (ContainerRef containerRef , BiFunction <Registry , ContainerRef , T > operation ) {
1139+ List <RegistriesConf .MirrorConfig > mirrors = registriesConf .getMirrors (containerRef );
1140+ for (RegistriesConf .MirrorConfig mirror : mirrors ) {
1141+ String mirrorLocation = mirror .location ();
1142+ if (mirrorLocation == null || mirrorLocation .isBlank ()) continue ;
1143+ ContainerRef mirrorRef = registriesConf .rewriteForMirror (containerRef , mirror );
1144+ // Use only the host[:port] part for copy() — path prefix is already baked into mirrorRef
1145+ // by rewriteForMirror, so passing the full location would double-apply the path.
1146+ String mirrorHost = mirrorLocation .contains ("/" )
1147+ ? mirrorLocation .substring (0 , mirrorLocation .indexOf ('/' ))
1148+ : mirrorLocation ;
1149+ Registry mirrorRegistry = copy (mirrorHost );
1150+ if (mirror .isInsecure () && !isInsecure ()) {
1151+ mirrorRegistry = mirrorRegistry .asInsecure ();
1152+ }
1153+ try {
1154+ LOG .debug ("Trying mirror {} for {}" , mirrorLocation , containerRef );
1155+ return operation .apply (mirrorRegistry , mirrorRef );
1156+ } catch (OrasException e ) {
1157+ LOG .warn ("Mirror {} failed for {}: {}" , mirrorLocation , containerRef , e .getMessage ());
1158+ }
1159+ }
1160+ return operation .apply (this , containerRef );
1161+ }
1162+
1163+ /**
1164+ * Get a manifest response, trying configured mirrors before the original registry.
11101165 * @param containerRef The container
11111166 * @return The response
11121167 */
11131168 private HttpClient .ResponseWrapper <String > getManifestResponse (ContainerRef containerRef ) {
1169+ return withMirrorFallback (containerRef , (reg , ref ) -> reg .getManifestResponseDirect (ref ));
1170+ }
1171+
1172+ private HttpClient .ResponseWrapper <String > getManifestResponseDirect (ContainerRef containerRef ) {
11141173 ContainerRef ref = containerRef .forRegistry (this ).checkBlocked (this );
11151174 if (ref .isInsecure (this ) && !this .isInsecure ()) {
1116- return asInsecure ().getManifestResponse (containerRef );
1175+ return asInsecure ().getManifestResponseDirect (containerRef );
11171176 }
11181177 URI uri = URI .create ("%s://%s" .formatted (getScheme (), ref .getManifestsPath (this )));
11191178 HttpClient .ResponseWrapper <String > response =
0 commit comments