11package org .cloudfoundry .multiapps .controller .web .upload ;
22
33import java .io .BufferedInputStream ;
4- import java .io .IOException ;
54import java .io .InputStream ;
65import java .math .BigInteger ;
76import java .net .URI ;
8- import java .net .URLDecoder ;
9- import java .net .http .HttpClient ;
10- import java .net .http .HttpRequest ;
11- import java .net .http .HttpResponse ;
12- import java .net .http .HttpResponse .BodyHandlers ;
13- import java .nio .charset .StandardCharsets ;
147import java .text .MessageFormat ;
158import java .time .Duration ;
169import java .time .LocalDateTime ;
1710import java .time .temporal .ChronoUnit ;
18- import java .util .Arrays ;
19- import java .util .Base64 ;
2011import java .util .UUID ;
2112import java .util .concurrent .ExecutorService ;
2213import java .util .concurrent .atomic .AtomicLong ;
2314import java .util .concurrent .locks .Lock ;
2415import java .util .concurrent .locks .ReentrantLock ;
2516import jakarta .inject .Inject ;
2617import jakarta .inject .Named ;
27- import org .apache .commons .io .IOUtils ;
2818import org .cloudfoundry .multiapps .common .SLException ;
2919import org .cloudfoundry .multiapps .common .util .MiscUtil ;
3020import org .cloudfoundry .multiapps .controller .api .model .UserCredentials ;
3323import org .cloudfoundry .multiapps .controller .core .helpers .DescriptorParserFacadeFactory ;
3424import org .cloudfoundry .multiapps .controller .core .util .ApplicationConfiguration ;
3525import org .cloudfoundry .multiapps .controller .core .util .FileUtils ;
36- import org .cloudfoundry .multiapps .controller .core .util .UriUtil ;
3726import org .cloudfoundry .multiapps .controller .persistence .model .AsyncUploadJobEntry ;
3827import org .cloudfoundry .multiapps .controller .persistence .model .FileEntry ;
3928import org .cloudfoundry .multiapps .controller .persistence .model .ImmutableAsyncUploadJobEntry ;
4029import org .cloudfoundry .multiapps .controller .persistence .model .ImmutableFileEntry ;
4130import org .cloudfoundry .multiapps .controller .persistence .services .AsyncUploadJobService ;
4231import org .cloudfoundry .multiapps .controller .persistence .services .FileService ;
43- import org .cloudfoundry .multiapps .controller .web . Constants ;
32+ import org .cloudfoundry .multiapps .controller .process . stream . CountingInputStream ;
4433import org .cloudfoundry .multiapps .controller .web .Messages ;
34+ import org .cloudfoundry .multiapps .controller .web .upload .client .DeployFromUrlRemoteClient ;
35+ import org .cloudfoundry .multiapps .controller .web .upload .client .FileFromUrlData ;
4536import org .cloudfoundry .multiapps .controller .web .util .SecurityContextUtil ;
4637import org .cloudfoundry .multiapps .mta .handlers .ArchiveHandler ;
4738import org .cloudfoundry .multiapps .mta .handlers .DescriptorParserFacade ;
4839import org .cloudfoundry .multiapps .mta .model .DeploymentDescriptor ;
4940import org .slf4j .Logger ;
5041import org .slf4j .LoggerFactory ;
51- import org .springframework .http .HttpHeaders ;
52- import org .springframework .http .HttpStatus ;
5342
5443@ Named
5544public class AsyncUploadJobExecutor {
5645
5746 private static final int INPUT_STREAM_BUFFER_SIZE = 16 * 1024 ;
5847
59- private static final Duration HTTP_CONNECT_TIMEOUT = Duration .ofMinutes (10 );
60- private static final String USERNAME_PASSWORD_URL_FORMAT = "{0}:{1}" ;
61- private static final int ERROR_RESPONSE_BODY_MAX_LENGTH = 4 * 1024 ;
62-
6348 private static final long WAIT_TIME_BETWEEN_ASYNC_JOB_UPDATES_IN_MILLIS = Duration .ofSeconds (3 )
6449 .toMillis ();
6550
@@ -72,19 +57,20 @@ public class AsyncUploadJobExecutor {
7257 private final AsyncUploadJobService asyncUploadJobService ;
7358 private final FileService fileService ;
7459 private final DescriptorParserFacadeFactory descriptorParserFactory ;
75- private final HttpClient httpClient ;
60+ private final DeployFromUrlRemoteClient deployFromUrlRemoteClient ;
7661
7762 @ Inject
7863 public AsyncUploadJobExecutor (ExecutorService asyncFileUploadExecutor , ExecutorService deployFromUrlExecutor ,
7964 ApplicationConfiguration applicationConfiguration , AsyncUploadJobService asyncUploadJobService ,
80- FileService fileService , DescriptorParserFacadeFactory descriptorParserFactory ) {
65+ FileService fileService , DescriptorParserFacadeFactory descriptorParserFactory ,
66+ DeployFromUrlRemoteClient deployFromUrlRemoteClient ) {
8167 this .asyncFileUploadExecutor = asyncFileUploadExecutor ;
8268 this .deployFromUrlExecutor = deployFromUrlExecutor ;
8369 this .applicationConfiguration = applicationConfiguration ;
8470 this .asyncUploadJobService = asyncUploadJobService ;
8571 this .fileService = fileService ;
8672 this .descriptorParserFactory = descriptorParserFactory ;
87- httpClient = buildHttpClient () ;
73+ this . deployFromUrlRemoteClient = deployFromUrlRemoteClient ;
8874 }
8975
9076 public AsyncUploadJobEntry executeUploadFromUrl (String spaceGuid , String namespace , String urlWithoutUserInfo , String decodedUrl ,
@@ -184,78 +170,28 @@ private void startSyncUploadFromUrlUpload(UploadFromUrlContext uploadFromUrlCont
184170 }
185171
186172 private FileEntry doUploadMtarFromUrl (UploadFromUrlContext uploadFromUrlContext , Lock lock ) throws Exception {
187- if (!UriUtil .isUrlSecure (uploadFromUrlContext .getFileUrl ())) {
188- throw new SLException (Messages .MTAR_ENDPOINT_NOT_SECURE );
189- }
190- UriUtil .validateUrl (uploadFromUrlContext .getFileUrl ());
191-
192- HttpResponse <InputStream > response = callRemoteEndpointWithRetry (uploadFromUrlContext .getFileUrl (),
193- uploadFromUrlContext .getUserCredentials ());
194- long fileSize = response .headers ()
195- .firstValueAsLong (Constants .CONTENT_LENGTH )
196- .orElseThrow (() -> new SLException (Messages .FILE_URL_RESPONSE_DID_NOT_RETURN_CONTENT_LENGTH ));
197-
198- long maxUploadSize = applicationConfiguration .getMaxUploadSize ();
199- if (fileSize > maxUploadSize ) {
200- throw new SLException (MessageFormat .format (Messages .MAX_UPLOAD_SIZE_EXCEEDED , maxUploadSize ));
201- }
202-
173+ FileFromUrlData fileFromUrlData = deployFromUrlRemoteClient .downloadFileFromUrl (uploadFromUrlContext );
203174 String fileName = extractFileName (uploadFromUrlContext .getFileUrl ());
204175 FileUtils .validateFileHasExtension (fileName );
205176 resetCounterOnRetry (uploadFromUrlContext .getJobEntry ()
206177 .getId (), lock );
207178 // Normal stream returned from the http response always returns 0 when InputStream::available() is executed which seems to break
208179 // JClods library: https://issues.apache.org/jira/browse/JCLOUDS-1623
209- try (CountingInputStream source = new CountingInputStream (response . body (), uploadFromUrlContext .getCounterRef ());
180+ try (CountingInputStream source = new CountingInputStream (fileFromUrlData . fileInputStream (), uploadFromUrlContext .getCounterRef ());
210181 BufferedInputStream bufferedContent = new BufferedInputStream (source , INPUT_STREAM_BUFFER_SIZE )) {
211- LOGGER .debug (Messages .UPLOADING_MTAR_STREAM_FROM_REMOTE_ENDPOINT , response .uri ());
182+ LOGGER .debug (Messages .UPLOADING_MTAR_STREAM_FROM_REMOTE_ENDPOINT , fileFromUrlData .uri ());
212183 return fileService .addFile (ImmutableFileEntry .builder ()
213184 .space (uploadFromUrlContext .getJobEntry ()
214185 .getSpaceGuid ())
215186 .namespace (uploadFromUrlContext .getJobEntry ()
216187 .getNamespace ())
217188 .name (fileName )
218- .size (BigInteger .valueOf (fileSize ))
189+ .size (BigInteger .valueOf (fileFromUrlData . fileSize () ))
219190 .build (), bufferedContent );
220191 }
221192 }
222193
223- public HttpResponse <InputStream > callRemoteEndpointWithRetry (String decodedUrl , UserCredentials userCredentials )
224- throws Exception {
225- return resilientOperationExecutor .execute ((CheckedSupplier <HttpResponse <InputStream >>) () -> {
226- var request = buildFetchFileRequest (decodedUrl , userCredentials );
227- LOGGER .debug (Messages .CALLING_REMOTE_MTAR_ENDPOINT , getMaskedUri (urlDecodeUrl (decodedUrl )));
228- var response = httpClient .send (request , BodyHandlers .ofInputStream ());
229- if (response .statusCode () / 100 != 2 ) {
230- String error = readErrorBodyFromResponse (response );
231- LOGGER .error (error );
232- if (response .statusCode () == HttpStatus .UNAUTHORIZED .value ()) {
233- String errorMessage = MessageFormat .format (Messages .DEPLOY_FROM_URL_WRONG_CREDENTIALS ,
234- UriUtil .stripUserInfo (decodedUrl ));
235- throw new SLException (errorMessage );
236- }
237- throw new SLException (MessageFormat .format (Messages .ERROR_FROM_REMOTE_MTAR_ENDPOINT , getMaskedUri (urlDecodeUrl (decodedUrl )),
238- response .statusCode (), error ));
239- }
240- return response ;
241- });
242- }
243-
244- private String getMaskedUri (String url ) {
245- if (url .contains ("@" )) {
246- return url .substring (url .lastIndexOf ("@" ))
247- .replace ("@" , "..." );
248- } else {
249- return url ;
250- }
251- }
252-
253- private String urlDecodeUrl (String url ) {
254- return URLDecoder .decode (url , StandardCharsets .UTF_8 );
255- }
256-
257194 private void resetCounterOnRetry (String jobGuid , Lock lock ) {
258-
259195 try {
260196 lock .lock ();
261197 AsyncUploadJobEntry asyncUploadJobEntry = asyncUploadJobService .createQuery ()
@@ -267,47 +203,6 @@ private void resetCounterOnRetry(String jobGuid, Lock lock) {
267203 } finally {
268204 lock .unlock ();
269205 }
270-
271- }
272-
273- protected HttpClient buildHttpClient () {
274- return HttpClient .newBuilder ()
275- .version (HttpClient .Version .HTTP_2 )
276- .connectTimeout (HTTP_CONNECT_TIMEOUT )
277- .followRedirects (HttpClient .Redirect .NORMAL )
278- .build ();
279- }
280-
281- private HttpRequest buildFetchFileRequest (String decodedUrl , UserCredentials userCredentials ) {
282- var builder = HttpRequest .newBuilder ()
283- .GET ()
284- .timeout (Duration .ofMinutes (15 ));
285- var uri = URI .create (decodedUrl );
286- var userInfo = uri .getUserInfo ();
287- if (userCredentials != null ) {
288- builder .uri (uri );
289- String userCredentialsUrlFormat = MessageFormat .format (USERNAME_PASSWORD_URL_FORMAT , userCredentials .getUsername (),
290- userCredentials .getPassword ());
291- String encodedAuth = Base64 .getEncoder ()
292- .encodeToString (userCredentialsUrlFormat .getBytes ());
293- builder .header (HttpHeaders .AUTHORIZATION , "Basic " + encodedAuth );
294- } else if (userInfo != null ) {
295- builder .uri (URI .create (decodedUrl .replace (uri .getRawUserInfo () + "@" , "" )));
296- String encodedAuth = Base64 .getEncoder ()
297- .encodeToString (userInfo .getBytes ());
298- builder .header (HttpHeaders .AUTHORIZATION , "Basic " + encodedAuth );
299- } else {
300- builder .uri (uri );
301- }
302- return builder .build ();
303- }
304-
305- private String readErrorBodyFromResponse (HttpResponse <InputStream > response ) throws IOException {
306- try (InputStream is = response .body ()) {
307- byte [] buffer = new byte [ERROR_RESPONSE_BODY_MAX_LENGTH ];
308- int read = IOUtils .read (is , buffer );
309- return new String (Arrays .copyOf (buffer , read ));
310- }
311206 }
312207
313208 private String extractFileName (String url ) {
0 commit comments