Skip to content

Commit

Permalink
Added three attempts to load the RSS feed in case the connection time…
Browse files Browse the repository at this point in the history
…s out or fails for any other reason.
  • Loading branch information
dobicinaitis committed Sep 4, 2023
1 parent b85c094 commit 3533e3b
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,31 @@
import com.apptasticsoftware.rssreader.RssReader;
import com.apptasticsoftware.rssreader.util.ItemComparator;
import dev.dobicinaitis.feedreader.exceptions.FeedReaderRuntimeException;
import lombok.RequiredArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.http.HttpClient;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.List;

@Slf4j
@RequiredArgsConstructor
@AllArgsConstructor
public class FeedReaderService {

private static final int CONNECTION_TIMEOUT_IN_SECONDS = 10;
private static final int FEED_READ_RETRY_COUNT = 3;

private final String url;
private final HttpClient httpClient;

public FeedReaderService(String url) {
this.url = url;
this.httpClient = createHttpClient(CONNECTION_TIMEOUT_IN_SECONDS);
}


/**
Expand All @@ -23,14 +37,55 @@ public class FeedReaderService {
* @return list of RSS feed items
*/
public List<Item> getItems() {
final RssReader rssReader = new RssReader();
return loadItems(FEED_READ_RETRY_COUNT).stream()
.sorted(ItemComparator.oldestItemFirst())
.toList();
}

/**
* Loads items from the RSS feed URL.
*
* @param retriesLeft number of attempts to load the RSS feed
* @return list of RSS feed items
*/
protected List<Item> loadItems(int retriesLeft) {
final RssReader rssReader = new RssReader(httpClient);
log.debug("Loading items from the RSS feed.");
while (true) {
try {
return rssReader.read(url).toList();
} catch (IOException e) {
log.error("Could not load the RSS feed, reason {}", e.getMessage());
retriesLeft--;
if (retriesLeft == 0) {
throw new FeedReaderRuntimeException(e);
}
}
log.info("Retrying to load the RSS feed, retries left: {}", retriesLeft);
}
}

/**
* Creates a new HTTP client with a custom connection timeout.
*
* @return HTTP client
*/
protected static HttpClient createHttpClient(int timeoutInSeconds) {
HttpClient client;
try {
return rssReader.read(url)
.sorted(ItemComparator.oldestItemFirst())
.toList();
} catch (IOException e) {
log.error("Could not load items from the RSS feed.");
throw new FeedReaderRuntimeException(e);
var sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, null, null);
client = HttpClient.newBuilder()
.sslContext(sslContext)
.connectTimeout(Duration.ofSeconds(timeoutInSeconds))
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(timeoutInSeconds))
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();
}
return client;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
import dev.dobicinaitis.feedreader.helpers.TestFeedServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

import java.net.http.HttpClient;

import static org.junit.jupiter.api.Assertions.*;

class FeedReaderServiceTest {

private static final String NON_ROUTABLE_IP = "10.255.255.1";
private static final TestFeedServer feedServer = new TestFeedServer();

@AfterAll
Expand Down Expand Up @@ -36,5 +40,33 @@ void shouldThrowFeedReaderRuntimeExceptionWhenFeedIsUnavailable() {
final FeedReaderService feedReader = new FeedReaderService(feedUrl);
assertThrows(FeedReaderRuntimeException.class, feedReader::getItems, "Should throw FeedReaderRuntimeException");
}

@Test
@Timeout(3)
void shouldRespectHttpRequestTimeoutSettings() {
// given
final int timeoutInSeconds = 1;
final int retryCount = 1;
final String feedUrl = "https://" + NON_ROUTABLE_IP;
final HttpClient httpClient = FeedReaderService.createHttpClient(timeoutInSeconds);
final FeedReaderService feedReader = new FeedReaderService(feedUrl, httpClient);
// when, then
assertThrows(FeedReaderRuntimeException.class, () -> feedReader.loadItems(retryCount), "Should throw FeedReaderRuntimeException");
// the timeout should occur after 1 second, so anything under 3 seconds is fine
}

@Test
@Timeout(5)
void shouldAttemptToLoadFeedMultipleTimes(){
// given
final int timeoutPerRequestInSeconds = 1;
final int retryCount = 3;
final String feedUrl = "https://" + NON_ROUTABLE_IP;
final HttpClient httpClient = FeedReaderService.createHttpClient(timeoutPerRequestInSeconds);
final FeedReaderService feedReader = new FeedReaderService(feedUrl, httpClient);
// when, then
assertThrows(FeedReaderRuntimeException.class, () -> feedReader.loadItems(retryCount), "Should throw FeedReaderRuntimeException");
// the timeout should occur after 3 seconds, so anything under 5 seconds is fine
}
}

0 comments on commit 3533e3b

Please sign in to comment.