Skip to content

Commit

Permalink
Add Streaming Rest Call - returning the response input stream without…
Browse files Browse the repository at this point in the history
… the wrappers (#379)
  • Loading branch information
adigfrog authored Sep 20, 2023
1 parent 41d391f commit 102bee9
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 28 deletions.
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -851,16 +851,37 @@ org.apache.http.Header[] headers = response.getAllHeaders();
org.apache.http.StatusLine statusLine = response.getStatusLine();
// A convenience method for verifying success
assert response.isSuccessResponse()
assert response.isSuccessResponse();
// Get the response raw body
String rawBody = response.rawBody();
String rawBody = response.getRawBody();
// If the the response raw body has a JSON format, populate an object with the body content,
// by providing a object's class.
List<Map<String, String>> parsedBody = response.parseBody(List.class);
```

Executing an Artifactory streaming REST API

```groovy
ArtifactoryRequest repositoryRequest = new ArtifactoryRequestImpl().apiUrl("api/repositories")
.method(ArtifactoryRequest.Method.GET)
.responseType(ArtifactoryRequest.ContentType.JSON);
ArtifactoryStreamingResponse response = artifactory.streamingRestCall(repositoryRequest);
// Get the response headers
org.apache.http.Header[] headers = response.getAllHeaders();
// Get the response status information
org.apache.http.StatusLine statusLine = response.getStatusLine();
// A convenience method for verifying success
assert response.isSuccessResponse();
// Get the response raw body using input stream
String rawBody = IOUtils.toString(response.getInputStream(), StandardCharsets.UTF_8);
```

## Building and Testing the Sources

The code is built using Gradle and includes integration tests.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.jfrog.artifactory.client;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpUriRequest;

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -42,10 +44,14 @@ public interface Artifactory extends ApiInterface, AutoCloseable {

ArtifactoryResponse restCall(ArtifactoryRequest artifactoryRequest) throws IOException;

ArtifactoryStreamingResponse streamingRestCall(ArtifactoryRequest artifactoryRequest) throws IOException;

InputStream getInputStream(String path) throws IOException;

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

HttpResponse execute(HttpUriRequest request) throws IOException;

default public <T> T get(String path, Class<? extends T> object, Class<T> interfaceObject) throws IOException {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
package org.jfrog.artifactory.client;

import org.apache.http.Header;
import org.apache.http.StatusLine;

import java.io.IOException;

/**
* ArtifactoryResponse object returned from {@link Artifactory#restCall(ArtifactoryRequest)}.
* acts as a wrapper for {@link org.apache.http.HttpResponse} but removes the need to handle response stream.
*/
public interface ArtifactoryResponse {

Header[] getAllHeaders();

StatusLine getStatusLine();
public interface ArtifactoryResponse extends BaseArtifactoryResponse {

String getRawBody();

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

boolean isSuccessResponse();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.jfrog.artifactory.client;

import java.io.IOException;
import java.io.InputStream;


/**
* ArtifactoryStreamingResponse object returned from {@link Artifactory#streamingRestCall(ArtifactoryRequest)}.
* acts as a wrapper for {@link org.apache.http.HttpResponse}.
*/
public interface ArtifactoryStreamingResponse extends BaseArtifactoryResponse, AutoCloseable {
InputStream getInputStream() throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.jfrog.artifactory.client;
import org.apache.http.Header;
import org.apache.http.StatusLine;

public interface BaseArtifactoryResponse {

Header[] getAllHeaders();

StatusLine getStatusLine();

boolean isSuccessResponse();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.jfrog.artifactory.client.impl;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;

public abstract class AbstractArtifactoryResponseImpl {

private final HttpResponse httpResponse;

public AbstractArtifactoryResponseImpl(HttpResponse httpResponse) {
this.httpResponse = httpResponse;
}

public HttpResponse getHttpResponse() {
return httpResponse;
}

public Header[] getAllHeaders() {
return this.httpResponse.getAllHeaders();
}

public StatusLine getStatusLine() {
return this.httpResponse.getStatusLine();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,24 @@ public ArtifactorySystem system() {
*/
@Override
public ArtifactoryResponse restCall(ArtifactoryRequest artifactoryRequest) throws IOException {
HttpResponse httpResponse = handleArtifactoryRequest(artifactoryRequest);
return new ArtifactoryResponseImpl(httpResponse);
}

/**
* Create a REST call to artifactory with a generic request
*
* @param artifactoryRequest that should be sent to artifactory
* @return {@link ArtifactoryStreamingResponse} Artifactory response in accordance with the request,
* which includes a reference to the inputStream.
*/
@Override
public ArtifactoryStreamingResponse streamingRestCall(ArtifactoryRequest artifactoryRequest) throws IOException {
HttpResponse httpResponse = handleArtifactoryRequest(artifactoryRequest);
return new ArtifactoryStreamingResponseImpl(httpResponse);
}

private HttpResponse handleArtifactoryRequest(ArtifactoryRequest artifactoryRequest) throws IOException {
HttpRequestBase httpRequest;

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

HttpResponse httpResponse = execute(httpRequest);
return new ArtifactoryResponseImpl(httpResponse);
return httpResponse;
}

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

@Override
public HttpResponse execute(HttpUriRequest request) throws IOException {
HttpClientContext clientContext = HttpClientContext.create();
if (clientContext.getAttribute(PreemptiveAuthInterceptor.ORIGINAL_HOST_CONTEXT_PARAM) == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
package org.jfrog.artifactory.client.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.util.EntityUtils;
import org.jfrog.artifactory.client.ArtifactoryResponse;
import org.jfrog.artifactory.client.impl.util.Util;

import java.io.IOException;

public class ArtifactoryResponseImpl implements ArtifactoryResponse {
public class ArtifactoryResponseImpl extends AbstractArtifactoryResponseImpl implements ArtifactoryResponse {

private static final ObjectMapper objectMapper = new ObjectMapper();

private HttpResponse httpResponse;
private String rawBody;

ArtifactoryResponseImpl(HttpResponse httpResponse) throws IOException {
this.httpResponse = httpResponse;
super(httpResponse);

HttpEntity entity = httpResponse.getEntity();

Expand All @@ -34,16 +31,6 @@ public class ArtifactoryResponseImpl implements ArtifactoryResponse {
}
}

@Override
public Header[] getAllHeaders() {
return this.httpResponse.getAllHeaders();
}

@Override
public StatusLine getStatusLine() {
return this.httpResponse.getStatusLine();
}

@Override
public String getRawBody() {
return this.rawBody;
Expand All @@ -62,7 +49,6 @@ public <T> T parseBody(Class<T> toType) throws IOException {
@Override
public boolean isSuccessResponse() {
int status = getStatusLine().getStatusCode();

return status >= 200 && status < 300;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.jfrog.artifactory.client.impl;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.jfrog.artifactory.client.ArtifactoryStreamingResponse;

import java.io.IOException;
import java.io.InputStream;

public class ArtifactoryStreamingResponseImpl extends AbstractArtifactoryResponseImpl implements ArtifactoryStreamingResponse {

public ArtifactoryStreamingResponseImpl(HttpResponse httpResponse) {
super(httpResponse);
}

@Override
public InputStream getInputStream() throws IOException {
InputStream is = null;
HttpEntity entity = getHttpResponse().getEntity();
if (entity != null) {
is = entity.getContent();
}
return is;
}

@Override
public boolean isSuccessResponse() {
int status = getStatusLine().getStatusCode();
return (status == HttpStatus.SC_OK ||
status == HttpStatus.SC_PARTIAL_CONTENT);
}

@Override
public void close() throws Exception {
IOUtils.close(getInputStream());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.jfrog.artifactory.client;

import org.apache.commons.io.IOUtils;
import org.jfrog.artifactory.client.impl.ArtifactoryRequestImpl;
import org.testng.annotations.Test;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import static org.testng.Assert.*;

public class StreamingRestCallTest extends ArtifactoryTestsBase {

@Test
public void testDownloadWithHeadersByStreamingRestCall() throws IOException {
InputStream inputStream = this.getClass().getResourceAsStream("/sample.txt");
assertNotNull(inputStream);
artifactory.repository(localRepository.getKey()).upload(PATH, inputStream).withProperty("color", "blue")
.withProperty("color", "red").doUpload();

Map<String, String> headers = new HashMap<>();
headers.put("Range", "bytes=0-10");
ArtifactoryRequest request = new ArtifactoryRequestImpl()
.apiUrl(localRepository.getKey() + "/" + PATH)
.method(ArtifactoryRequest.Method.GET)
.setHeaders(headers)
.requestType(ArtifactoryRequest.ContentType.JSON);

ArtifactoryStreamingResponse response = artifactory.streamingRestCall(request);
assertTrue(response.isSuccessResponse());

inputStream = response.getInputStream();
String actual = textFrom(inputStream);
assertEquals(actual, textFrom(this.getClass().getResourceAsStream("/sample.txt")).substring(0, 11));
}

@Test
public void testErrorStreamingRestCall() throws IOException {
ArtifactoryRequest request = new ArtifactoryRequestImpl()
.apiUrl(localRepository.getKey() + "/" + PATH + "shouldNotExist")
.method(ArtifactoryRequest.Method.GET)
.requestType(ArtifactoryRequest.ContentType.JSON);
ArtifactoryStreamingResponse response = artifactory.streamingRestCall(request);
assertFalse(response.isSuccessResponse());
assertEquals(response.getStatusLine().getStatusCode(), 404);
String raw = IOUtils.toString(response.getInputStream(), StandardCharsets.UTF_8);
assertEquals(raw, "{\n" +
" \"errors\" : [ {\n" +
" \"status\" : 404,\n" +
" \"message\" : \"File not found.\"\n" +
" } ]\n" +
"}");
}
}

0 comments on commit 102bee9

Please sign in to comment.