Skip to content

Commit

Permalink
Implement ServiceDownException for case GitHub's API is down (#1813)
Browse files Browse the repository at this point in the history
* Implement ServiceDownException for case GitHub's API is down

* Update src/main/java/org/kohsuke/github/GitHubConnectorResponseErrorHandler.java

* Update src/main/java/org/kohsuke/github/GitHubConnectorResponseErrorHandler.java

* Apply suggestions from code review

* Update src/main/java/org/kohsuke/github/ServiceDownException.java

---------

Co-authored-by: Liam Newman <[email protected]>
  • Loading branch information
The-Huginn and bitwiseman authored Mar 15, 2024
1 parent eb269bd commit af19581
Show file tree
Hide file tree
Showing 8 changed files with 620 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
import org.jetbrains.annotations.NotNull;
import org.kohsuke.github.connector.GitHubConnectorResponse;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

import javax.annotation.Nonnull;

import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;

// TODO: Auto-generated Javadoc
Expand Down Expand Up @@ -49,6 +53,10 @@ abstract class GitHubConnectorResponseErrorHandler {

/** The status http bad request or greater. */
static GitHubConnectorResponseErrorHandler STATUS_HTTP_BAD_REQUEST_OR_GREATER = new GitHubConnectorResponseErrorHandler() {
private static final String CONTENT_TYPE = "Content-type";
private static final String TEXT_HTML = "text/html";
private static final String UNICORN_TITLE = "<title>Unicorn!";

@Override
public boolean isError(@NotNull GitHubConnectorResponse connectorResponse) throws IOException {
return connectorResponse.statusCode() >= HTTP_BAD_REQUEST;
Expand All @@ -58,9 +66,38 @@ public boolean isError(@NotNull GitHubConnectorResponse connectorResponse) throw
public void onError(@NotNull GitHubConnectorResponse connectorResponse) throws IOException {
if (connectorResponse.statusCode() == HTTP_NOT_FOUND) {
throw new FileNotFoundException(connectorResponse.request().url().toString());
} else if (isServiceDown(connectorResponse)) {
throw new ServiceDownException(connectorResponse);
} else {
throw new HttpException(connectorResponse);
}
}

private boolean isServiceDown(GitHubConnectorResponse connectorResponse) throws IOException {
if (connectorResponse.statusCode() < HTTP_INTERNAL_ERROR) {
return false;
}

String contentTypeHeader = connectorResponse.header(CONTENT_TYPE);
if (contentTypeHeader != null && contentTypeHeader.contains(TEXT_HTML)) {
try (BufferedReader bufReader = new BufferedReader(
new InputStreamReader(connectorResponse.bodyStream(), StandardCharsets.UTF_8))) {
String line;
int hardLineCap = 25;
// <title> node is expected in the beginning anyway.
// This way we do not load the raw long images' Strings, which are later in the HTML code
// Regex or .contains would result in iterating the whole HTML document, if it didn't match
// UNICORN_TITLE
while (hardLineCap > 0 && (line = bufReader.readLine()) != null) {
if (line.trim().startsWith(UNICORN_TITLE)) {
return true;
}
hardLineCap--;
}
}
}

return false;
}
};
}
26 changes: 26 additions & 0 deletions src/main/java/org/kohsuke/github/ServiceDownException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.kohsuke.github;

import org.kohsuke.github.connector.GitHubConnectorResponse;

import java.io.IOException;

/**
* Special {@link IOException} case for http exceptions, when {@link HttpException} is thrown due to GitHub service
* being down.
*
* Inherits from {@link HttpException} to maintain compatibility with existing clients.
*
* @author <a href="mailto:[email protected]">Rastislav Budinsky</a>
*/
public class ServiceDownException extends HttpException {

/**
* Instantiates a new service down exception.
*
* @param connectorResponse
* the connector response to base this on
*/
public ServiceDownException(GitHubConnectorResponse connectorResponse) {
super(connectorResponse);
}
}
16 changes: 16 additions & 0 deletions src/test/java/org/kohsuke/github/GitHubTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -397,4 +397,20 @@ public void testHeaderFieldName() throws Exception {
org.getResponseHeaderFields().keySet().contains("CacHe-ControL"));
assertThat(org.getResponseHeaderFields().get("cachE-cOntrol").get(0), is("private, max-age=60, s-maxage=60"));
}

/**
* Test expect GitHub {@link ServiceDownException}
*
*/
@Test
public void testCatchServiceDownException() {
snapshotNotAllowed();
try {
GHRepository repo = gitHub.getRepository("hub4j-test-org/github-api");
repo.getFileContent("ghcontent-ro/service-down");
fail("Exception was expected");
} catch (IOException e) {
assertThat(e.getClass().getName(), equalToIgnoringCase(ServiceDownException.class.getName()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"login": "The-Huginn",
"id": 78657734,
"node_id": "MDQ6VXNlcjc4NjU3NzM0",
"avatar_url": "https://avatars.githubusercontent.com/u/78657734?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/The-Huginn",
"html_url": "https://github.com/The-Huginn",
"followers_url": "https://api.github.com/users/The-Huginn/followers",
"following_url": "https://api.github.com/users/The-Huginn/following{/other_user}",
"gists_url": "https://api.github.com/users/The-Huginn/gists{/gist_id}",
"starred_url": "https://api.github.com/users/The-Huginn/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/The-Huginn/subscriptions",
"organizations_url": "https://api.github.com/users/The-Huginn/orgs",
"repos_url": "https://api.github.com/users/The-Huginn/repos",
"events_url": "https://api.github.com/users/The-Huginn/events{/privacy}",
"received_events_url": "https://api.github.com/users/The-Huginn/received_events",
"type": "User",
"site_admin": false,
"name": "Rastislav Budinsky",
"company": "Red Hat",
"blog": "thehuginn.com",
"location": null,
"email": null,
"hireable": null,
"bio": null,
"twitter_username": "The_Hug1nn",
"public_repos": 47,
"public_gists": 0,
"followers": 3,
"following": 2,
"created_at": "2021-02-06T17:33:36Z",
"updated_at": "2024-03-11T08:56:45Z"
}
Loading

0 comments on commit af19581

Please sign in to comment.