Skip to content

Commit 102bee9

Browse files
authored
Add Streaming Rest Call - returning the response input stream without the wrappers (#379)
1 parent 41d391f commit 102bee9

File tree

10 files changed

+202
-28
lines changed

10 files changed

+202
-28
lines changed

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -851,16 +851,37 @@ org.apache.http.Header[] headers = response.getAllHeaders();
851851
org.apache.http.StatusLine statusLine = response.getStatusLine();
852852
853853
// A convenience method for verifying success
854-
assert response.isSuccessResponse()
854+
assert response.isSuccessResponse();
855855
856856
// Get the response raw body
857-
String rawBody = response.rawBody();
857+
String rawBody = response.getRawBody();
858858
859859
// If the the response raw body has a JSON format, populate an object with the body content,
860860
// by providing a object's class.
861861
List<Map<String, String>> parsedBody = response.parseBody(List.class);
862862
```
863863

864+
Executing an Artifactory streaming REST API
865+
866+
```groovy
867+
ArtifactoryRequest repositoryRequest = new ArtifactoryRequestImpl().apiUrl("api/repositories")
868+
.method(ArtifactoryRequest.Method.GET)
869+
.responseType(ArtifactoryRequest.ContentType.JSON);
870+
ArtifactoryStreamingResponse response = artifactory.streamingRestCall(repositoryRequest);
871+
872+
// Get the response headers
873+
org.apache.http.Header[] headers = response.getAllHeaders();
874+
875+
// Get the response status information
876+
org.apache.http.StatusLine statusLine = response.getStatusLine();
877+
878+
// A convenience method for verifying success
879+
assert response.isSuccessResponse();
880+
881+
// Get the response raw body using input stream
882+
String rawBody = IOUtils.toString(response.getInputStream(), StandardCharsets.UTF_8);
883+
```
884+
864885
## Building and Testing the Sources
865886

866887
The code is built using Gradle and includes integration tests.

api/src/main/java/org/jfrog/artifactory/client/Artifactory.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.jfrog.artifactory.client;
22

33
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import org.apache.http.HttpResponse;
5+
import org.apache.http.client.methods.HttpUriRequest;
46

57
import java.io.IOException;
68
import java.io.InputStream;
@@ -42,10 +44,14 @@ public interface Artifactory extends ApiInterface, AutoCloseable {
4244

4345
ArtifactoryResponse restCall(ArtifactoryRequest artifactoryRequest) throws IOException;
4446

47+
ArtifactoryStreamingResponse streamingRestCall(ArtifactoryRequest artifactoryRequest) throws IOException;
48+
4549
InputStream getInputStream(String path) throws IOException;
4650

4751
InputStream getInputStreamWithHeaders(String path, Map<String, String> headers) throws IOException;
4852

53+
HttpResponse execute(HttpUriRequest request) throws IOException;
54+
4955
default public <T> T get(String path, Class<? extends T> object, Class<T> interfaceObject) throws IOException {
5056
return null;
5157
}
Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
11
package org.jfrog.artifactory.client;
22

3-
import org.apache.http.Header;
4-
import org.apache.http.StatusLine;
53

64
import java.io.IOException;
75

86
/**
97
* ArtifactoryResponse object returned from {@link Artifactory#restCall(ArtifactoryRequest)}.
108
* acts as a wrapper for {@link org.apache.http.HttpResponse} but removes the need to handle response stream.
119
*/
12-
public interface ArtifactoryResponse {
13-
14-
Header[] getAllHeaders();
15-
16-
StatusLine getStatusLine();
10+
public interface ArtifactoryResponse extends BaseArtifactoryResponse {
1711

1812
String getRawBody();
1913

2014
<T> T parseBody(Class<T> toType) throws IOException;
2115

22-
boolean isSuccessResponse();
23-
}
16+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.jfrog.artifactory.client;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
6+
7+
/**
8+
* ArtifactoryStreamingResponse object returned from {@link Artifactory#streamingRestCall(ArtifactoryRequest)}.
9+
* acts as a wrapper for {@link org.apache.http.HttpResponse}.
10+
*/
11+
public interface ArtifactoryStreamingResponse extends BaseArtifactoryResponse, AutoCloseable {
12+
InputStream getInputStream() throws IOException;
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.jfrog.artifactory.client;
2+
import org.apache.http.Header;
3+
import org.apache.http.StatusLine;
4+
5+
public interface BaseArtifactoryResponse {
6+
7+
Header[] getAllHeaders();
8+
9+
StatusLine getStatusLine();
10+
11+
boolean isSuccessResponse();
12+
13+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.jfrog.artifactory.client.impl;
2+
3+
import org.apache.http.Header;
4+
import org.apache.http.HttpResponse;
5+
import org.apache.http.StatusLine;
6+
7+
public abstract class AbstractArtifactoryResponseImpl {
8+
9+
private final HttpResponse httpResponse;
10+
11+
public AbstractArtifactoryResponseImpl(HttpResponse httpResponse) {
12+
this.httpResponse = httpResponse;
13+
}
14+
15+
public HttpResponse getHttpResponse() {
16+
return httpResponse;
17+
}
18+
19+
public Header[] getAllHeaders() {
20+
return this.httpResponse.getAllHeaders();
21+
}
22+
23+
public StatusLine getStatusLine() {
24+
return this.httpResponse.getStatusLine();
25+
}
26+
27+
}

services/src/main/groovy/org/jfrog/artifactory/client/impl/ArtifactoryImpl.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,24 @@ public ArtifactorySystem system() {
132132
*/
133133
@Override
134134
public ArtifactoryResponse restCall(ArtifactoryRequest artifactoryRequest) throws IOException {
135+
HttpResponse httpResponse = handleArtifactoryRequest(artifactoryRequest);
136+
return new ArtifactoryResponseImpl(httpResponse);
137+
}
138+
139+
/**
140+
* Create a REST call to artifactory with a generic request
141+
*
142+
* @param artifactoryRequest that should be sent to artifactory
143+
* @return {@link ArtifactoryStreamingResponse} Artifactory response in accordance with the request,
144+
* which includes a reference to the inputStream.
145+
*/
146+
@Override
147+
public ArtifactoryStreamingResponse streamingRestCall(ArtifactoryRequest artifactoryRequest) throws IOException {
148+
HttpResponse httpResponse = handleArtifactoryRequest(artifactoryRequest);
149+
return new ArtifactoryStreamingResponseImpl(httpResponse);
150+
}
151+
152+
private HttpResponse handleArtifactoryRequest(ArtifactoryRequest artifactoryRequest) throws IOException {
135153
HttpRequestBase httpRequest;
136154

137155
String requestPath = "/" + artifactoryRequest.getApiUrl();
@@ -194,7 +212,7 @@ public ArtifactoryResponse restCall(ArtifactoryRequest artifactoryRequest) throw
194212
}
195213

196214
HttpResponse httpResponse = execute(httpRequest);
197-
return new ArtifactoryResponseImpl(httpResponse);
215+
return httpResponse;
198216
}
199217

200218
private void setEntity(HttpEntityEnclosingRequestBase httpRequest, Object body, ContentType contentType) throws JsonProcessingException {
@@ -369,6 +387,7 @@ public String delete(String path) throws IOException {
369387
return Util.responseToString(httpResponse);
370388
}
371389

390+
@Override
372391
public HttpResponse execute(HttpUriRequest request) throws IOException {
373392
HttpClientContext clientContext = HttpClientContext.create();
374393
if (clientContext.getAttribute(PreemptiveAuthInterceptor.ORIGINAL_HOST_CONTEXT_PARAM) == null) {

services/src/main/groovy/org/jfrog/artifactory/client/impl/ArtifactoryResponseImpl.java

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
package org.jfrog.artifactory.client.impl;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
4-
import org.apache.http.Header;
54
import org.apache.http.HttpEntity;
65
import org.apache.http.HttpResponse;
7-
import org.apache.http.StatusLine;
86
import org.apache.http.util.EntityUtils;
97
import org.jfrog.artifactory.client.ArtifactoryResponse;
108
import org.jfrog.artifactory.client.impl.util.Util;
119

1210
import java.io.IOException;
1311

14-
public class ArtifactoryResponseImpl implements ArtifactoryResponse {
12+
public class ArtifactoryResponseImpl extends AbstractArtifactoryResponseImpl implements ArtifactoryResponse {
1513

1614
private static final ObjectMapper objectMapper = new ObjectMapper();
1715

18-
private HttpResponse httpResponse;
1916
private String rawBody;
2017

2118
ArtifactoryResponseImpl(HttpResponse httpResponse) throws IOException {
22-
this.httpResponse = httpResponse;
19+
super(httpResponse);
2320

2421
HttpEntity entity = httpResponse.getEntity();
2522

@@ -34,16 +31,6 @@ public class ArtifactoryResponseImpl implements ArtifactoryResponse {
3431
}
3532
}
3633

37-
@Override
38-
public Header[] getAllHeaders() {
39-
return this.httpResponse.getAllHeaders();
40-
}
41-
42-
@Override
43-
public StatusLine getStatusLine() {
44-
return this.httpResponse.getStatusLine();
45-
}
46-
4734
@Override
4835
public String getRawBody() {
4936
return this.rawBody;
@@ -62,7 +49,6 @@ public <T> T parseBody(Class<T> toType) throws IOException {
6249
@Override
6350
public boolean isSuccessResponse() {
6451
int status = getStatusLine().getStatusCode();
65-
6652
return status >= 200 && status < 300;
6753
}
6854
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.jfrog.artifactory.client.impl;
2+
3+
import org.apache.commons.io.IOUtils;
4+
import org.apache.http.HttpEntity;
5+
import org.apache.http.HttpResponse;
6+
import org.apache.http.HttpStatus;
7+
import org.jfrog.artifactory.client.ArtifactoryStreamingResponse;
8+
9+
import java.io.IOException;
10+
import java.io.InputStream;
11+
12+
public class ArtifactoryStreamingResponseImpl extends AbstractArtifactoryResponseImpl implements ArtifactoryStreamingResponse {
13+
14+
public ArtifactoryStreamingResponseImpl(HttpResponse httpResponse) {
15+
super(httpResponse);
16+
}
17+
18+
@Override
19+
public InputStream getInputStream() throws IOException {
20+
InputStream is = null;
21+
HttpEntity entity = getHttpResponse().getEntity();
22+
if (entity != null) {
23+
is = entity.getContent();
24+
}
25+
return is;
26+
}
27+
28+
@Override
29+
public boolean isSuccessResponse() {
30+
int status = getStatusLine().getStatusCode();
31+
return (status == HttpStatus.SC_OK ||
32+
status == HttpStatus.SC_PARTIAL_CONTENT);
33+
}
34+
35+
@Override
36+
public void close() throws Exception {
37+
IOUtils.close(getInputStream());
38+
}
39+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.jfrog.artifactory.client;
2+
3+
import org.apache.commons.io.IOUtils;
4+
import org.jfrog.artifactory.client.impl.ArtifactoryRequestImpl;
5+
import org.testng.annotations.Test;
6+
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.nio.charset.StandardCharsets;
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
13+
import static org.testng.Assert.*;
14+
15+
public class StreamingRestCallTest extends ArtifactoryTestsBase {
16+
17+
@Test
18+
public void testDownloadWithHeadersByStreamingRestCall() throws IOException {
19+
InputStream inputStream = this.getClass().getResourceAsStream("/sample.txt");
20+
assertNotNull(inputStream);
21+
artifactory.repository(localRepository.getKey()).upload(PATH, inputStream).withProperty("color", "blue")
22+
.withProperty("color", "red").doUpload();
23+
24+
Map<String, String> headers = new HashMap<>();
25+
headers.put("Range", "bytes=0-10");
26+
ArtifactoryRequest request = new ArtifactoryRequestImpl()
27+
.apiUrl(localRepository.getKey() + "/" + PATH)
28+
.method(ArtifactoryRequest.Method.GET)
29+
.setHeaders(headers)
30+
.requestType(ArtifactoryRequest.ContentType.JSON);
31+
32+
ArtifactoryStreamingResponse response = artifactory.streamingRestCall(request);
33+
assertTrue(response.isSuccessResponse());
34+
35+
inputStream = response.getInputStream();
36+
String actual = textFrom(inputStream);
37+
assertEquals(actual, textFrom(this.getClass().getResourceAsStream("/sample.txt")).substring(0, 11));
38+
}
39+
40+
@Test
41+
public void testErrorStreamingRestCall() throws IOException {
42+
ArtifactoryRequest request = new ArtifactoryRequestImpl()
43+
.apiUrl(localRepository.getKey() + "/" + PATH + "shouldNotExist")
44+
.method(ArtifactoryRequest.Method.GET)
45+
.requestType(ArtifactoryRequest.ContentType.JSON);
46+
ArtifactoryStreamingResponse response = artifactory.streamingRestCall(request);
47+
assertFalse(response.isSuccessResponse());
48+
assertEquals(response.getStatusLine().getStatusCode(), 404);
49+
String raw = IOUtils.toString(response.getInputStream(), StandardCharsets.UTF_8);
50+
assertEquals(raw, "{\n" +
51+
" \"errors\" : [ {\n" +
52+
" \"status\" : 404,\n" +
53+
" \"message\" : \"File not found.\"\n" +
54+
" } ]\n" +
55+
"}");
56+
}
57+
}

0 commit comments

Comments
 (0)