Skip to content

Commit 8c33c9d

Browse files
committed
Use Uni async way to do checksum validation for local downloads
1 parent 352e7e7 commit 8c33c9d

File tree

3 files changed

+170
-14
lines changed

3 files changed

+170
-14
lines changed

src/main/java/org/commonjava/util/sidecar/jaxrs/FoloContentAccessResource.java

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import javax.ws.rs.core.Context;
4242
import javax.ws.rs.core.Response;
4343
import java.io.File;
44+
import java.io.IOException;
4445
import java.io.InputStream;
4546
import java.util.Optional;
4647

@@ -87,11 +88,39 @@ public Uni<Response> get( @Parameter( in = PATH, required = true ) @PathParam( "
8788
Optional<File> download = archiveService.getLocally( path );
8889
if ( download.isPresent() && download.get().isFile() )
8990
{
90-
InputStream inputStream = FileUtils.openInputStream( download.get() );
91-
final Response.ResponseBuilder builder = Response.ok( new TransferStreamingOutput( inputStream ) );
92-
logger.debug( "Download path: {} from historical archive.", path );
93-
publishTrackingEvent( path, id );
94-
return Uni.createFrom().item( builder.build() );
91+
Uni<Boolean> checksumValidation =
92+
proxyService.validateChecksum( id, packageType, type, name, path, request );
93+
return checksumValidation.onItem().transform( result -> {
94+
if ( result != null && result )
95+
{
96+
try
97+
{
98+
InputStream inputStream = FileUtils.openInputStream( download.get() );
99+
final Response.ResponseBuilder builder =
100+
Response.ok( new TransferStreamingOutput( inputStream ) );
101+
logger.debug( "Download path: {} from historical archive.", path );
102+
publishTrackingEvent( path, id );
103+
return Uni.createFrom().item( builder.build() );
104+
}
105+
catch ( IOException e )
106+
{
107+
logger.error( "IO error for local file, path {}.", path, e );
108+
}
109+
}
110+
else
111+
{
112+
try
113+
{
114+
logger.debug( "Checksum validation failed, download from proxy: {}.", path );
115+
return proxyService.doGet( id, packageType, type, name, path, request );
116+
}
117+
catch ( Exception e )
118+
{
119+
logger.error( "Error for proxy download, path {}.", path, e );
120+
}
121+
}
122+
return null;
123+
} ).flatMap( response -> response );
95124
}
96125
else
97126
{

src/main/java/org/commonjava/util/sidecar/services/ProxyService.java

Lines changed: 132 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import kotlin.Pair;
2222
import org.commonjava.util.sidecar.config.ProxyConfiguration;
2323
import org.commonjava.util.sidecar.interceptor.ExceptionHandler;
24+
import org.commonjava.util.sidecar.model.dto.HistoricalEntryDTO;
2425
import org.commonjava.util.sidecar.util.OtelAdapter;
2526
import org.commonjava.util.sidecar.util.ProxyStreamingOutput;
2627
import org.commonjava.util.sidecar.util.UrlUtils;
@@ -31,7 +32,11 @@
3132
import javax.enterprise.context.ApplicationScoped;
3233
import javax.inject.Inject;
3334
import javax.ws.rs.core.Response;
35+
import java.io.ByteArrayOutputStream;
36+
import java.io.IOException;
3437
import java.io.InputStream;
38+
import java.util.LinkedHashMap;
39+
import java.util.Map;
3540

3641
import static io.vertx.core.http.HttpMethod.HEAD;
3742
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
@@ -54,28 +59,34 @@ public class ProxyService
5459
@Inject
5560
OtelAdapter otel;
5661

62+
@Inject
63+
ReportService reportService;
64+
5765
public Uni<Response> doHead( String trackingId, String packageType, String type, String name, String path,
58-
HttpServerRequest request ) throws Exception
66+
HttpServerRequest request )
67+
throws Exception
5968
{
6069
String contentPath = UrlUtils.buildUrl( FOLO_TRACK_REST_BASE_PATH, trackingId, packageType, type, name, path );
6170
return doHead( contentPath, request );
6271
}
6372

64-
public Uni<Response> doHead( String path, HttpServerRequest request ) throws Exception
73+
public Uni<Response> doHead( String path, HttpServerRequest request )
74+
throws Exception
6575
{
6676
return normalizePathAnd( path, p -> classifier.classifyAnd( p, request, ( client, service ) -> wrapAsyncCall(
67-
client.head( p, request ).call(), request.method() ) ) );
77+
client.head( p, request ).call(), request.method() ) ) );
6878
}
6979

7080
public Uni<Response> doGet( String trackingId, String packageType, String type, String name, String path,
71-
HttpServerRequest request ) throws Exception
81+
HttpServerRequest request )
82+
throws Exception
7283
{
7384
String contentPath = UrlUtils.buildUrl( FOLO_TRACK_REST_BASE_PATH, trackingId, packageType, type, name, path );
7485
return doGet( contentPath, request );
7586
}
7687

7788
public Uni<Response> doGet( String path, HttpServerRequest request )
78-
throws Exception
89+
throws Exception
7990
{
8091
return normalizePathAnd( path, p -> classifier.classifyAnd( p, request, ( client, service ) -> wrapAsyncCall(
8192
client.get( p, request ).call(), request.method() ) ) );
@@ -113,16 +124,87 @@ public Uni<Response> doPut( String path, InputStream is, HttpServerRequest reque
113124
public Uni<Response> doDelete( String path, HttpServerRequest request ) throws Exception
114125
{
115126
return normalizePathAnd( path, p -> classifier.classifyAnd( p, request, ( client, service ) -> wrapAsyncCall(
116-
client.delete( p ).headersFrom( request ).call(), request.method() ) ) );
127+
client.delete( p ).headersFrom( request ).call(), request.method() ) ) );
117128
}
118129

119130
public Uni<Response> wrapAsyncCall( WebClientAdapter.CallAdapter asyncCall, HttpMethod method )
120131
{
121-
Uni<Response> ret =
122-
asyncCall.enqueue().onItem().transform( ( resp ) -> convertProxyResp( resp, method ) );
132+
Uni<Response> ret = asyncCall.enqueue().onItem().transform( ( resp ) -> convertProxyResp( resp, method ) );
123133
return ret.onFailure().recoverWithItem( this::handleProxyException );
124134
}
125135

136+
public Uni<Boolean> validateChecksum( String trackingId, String packageType, String type, String name, String path,
137+
HttpServerRequest request )
138+
{
139+
Map<String, String> localChecksums = getChecksums( path );
140+
Uni<Boolean> resultUni = Uni.createFrom().item( false );
141+
142+
for ( String checksumType : localChecksums.keySet() )
143+
{
144+
String localChecksum = localChecksums.get( checksumType );
145+
if ( localChecksum == null )
146+
{
147+
continue;
148+
}
149+
String checksumUrl = path + "." + checksumType;
150+
resultUni = resultUni.onItem().call( () -> {
151+
try
152+
{
153+
return downloadAndCompareChecksum( trackingId, packageType, type, name, checksumUrl, localChecksum,
154+
request ).onItem().invoke( result -> {
155+
if ( result != null && result )
156+
{
157+
// This is just used to skip loop to avoid unnecessary checksum download
158+
logger.debug(
159+
"Found the valid checksum compare result, stopping further checks, remote path {}",
160+
checksumUrl );
161+
throw new FoundValidChecksumException();
162+
}
163+
} );
164+
}
165+
catch ( Exception e )
166+
{
167+
logger.error( "Checksum download compare error for path: {}", checksumUrl, e );
168+
}
169+
return null;
170+
} );
171+
}
172+
return resultUni.onFailure().recoverWithItem( false ).onItem().transform( result -> {
173+
// If catch FoundValidChecksumException,return true
174+
return true;
175+
} ); // If no valid checksum compare result found, return false
176+
}
177+
178+
private Uni<Boolean> downloadAndCompareChecksum( String trackingId, String packageType, String type, String name,
179+
String checksumUrl, String localChecksum,
180+
HttpServerRequest request )
181+
throws Exception
182+
{
183+
return doGet( trackingId, packageType, type, name, checksumUrl, request ).onItem().transform( response -> {
184+
if ( response.getStatus() == Response.Status.OK.getStatusCode() )
185+
{
186+
ProxyStreamingOutput streamingOutput = (ProxyStreamingOutput) response.getEntity();
187+
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream())
188+
{
189+
streamingOutput.write( outputStream );
190+
String remoteChecksum = outputStream.toString();
191+
return localChecksum.equals( remoteChecksum );
192+
}
193+
catch ( IOException e )
194+
{
195+
logger.error( "Error to read remote checksum, path:{}.", checksumUrl, e );
196+
return null;
197+
}
198+
}
199+
else
200+
{
201+
logger.error( "Failed to download remote checksum for {}: HTTP {}.", checksumUrl,
202+
response.getStatus() );
203+
return null;
204+
}
205+
} );
206+
}
207+
126208
/**
127209
* Send status 500 with error message body.
128210
* @param t error
@@ -162,4 +244,46 @@ private boolean isHeaderAllowed( Pair<? extends String, ? extends String> header
162244
String key = header.getFirst();
163245
return !FORBIDDEN_HEADERS.contains( key.toLowerCase() );
164246
}
247+
248+
private Map<String, String> getChecksums( String path )
249+
{
250+
Map<String, String> result = new LinkedHashMap<>();
251+
HistoricalEntryDTO entryDTO = reportService.getHistoricalContentMap().get( path );
252+
if ( entryDTO != null )
253+
{
254+
result.put( ChecksumType.SHA1.getValue(), entryDTO.getSha1() );
255+
result.put( ChecksumType.SHA256.getValue(), entryDTO.getSha256() );
256+
result.put( ChecksumType.MD5.getValue(), entryDTO.getMd5() );
257+
}
258+
259+
return result;
260+
}
261+
262+
enum ChecksumType
263+
{
264+
SHA1( "sha1" ),
265+
SHA256( "sha256" ),
266+
MD5( "md5" );
267+
268+
private final String value;
269+
270+
ChecksumType( String value )
271+
{
272+
this.value = value;
273+
}
274+
275+
public String getValue()
276+
{
277+
return value;
278+
}
279+
}
280+
281+
class FoundValidChecksumException
282+
extends RuntimeException
283+
{
284+
public FoundValidChecksumException()
285+
{
286+
super( "Found a valid checksum, stopping further checks." );
287+
}
288+
}
165289
}

src/main/java/org/commonjava/util/sidecar/services/ReportService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,10 @@ public void storeTrackedDownload( JsonObject message )
145145
Quarkus.asyncExit();
146146
}
147147
}
148-
149148
}
150149

150+
public HashMap<String, HistoricalEntryDTO> getHistoricalContentMap()
151+
{
152+
return historicalContentMap;
153+
}
151154
}

0 commit comments

Comments
 (0)