event-publisher-library/src/main/java/org/gcube/event/publisher/HTTPWithOIDCAuthEventSender...

289 lines
12 KiB
Java

package org.gcube.event.publisher;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import org.gcube.oidc.rest.JWTToken;
import org.gcube.oidc.rest.OpenIdConnectRESTHelper;
import org.gcube.oidc.rest.OpenIdConnectRESTHelperException;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HTTPWithOIDCAuthEventSender implements EventSender {
protected static final Logger log = LoggerFactory.getLogger(HTTPWithOIDCAuthEventSender.class);
private URL baseEndpointURL;
private String clientId;
private String clientSecret;
private URL tokenURL;
public HTTPWithOIDCAuthEventSender(URL baseEndpointURL, String clientId, String clientSecret, URL tokenURL) {
this.baseEndpointURL = baseEndpointURL;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.tokenURL = tokenURL;
}
@Override
public void send(Event event) {
log.debug("Starting HTTP POST thread to: {}", baseEndpointURL);
new Thread(new HTTPPost(baseEndpointURL, event)).start();
}
@Override
public String sendAndGetResult(Event event) {
log.debug("Starting HTTP POST thread to: {}", baseEndpointURL);
HTTPPost post = new HTTPPost(baseEndpointURL, event);
Thread postThread = new Thread(post);
postThread.start();
try {
postThread.join();
return post.getResult();
} catch (InterruptedException e) {
log.error("While waiting for HTTP Post thread termination", e);
return null;
}
}
@Override
public JSONObject retrive(String id) {
return new HTTPGet(baseEndpointURL, id).readJSON();
}
protected URL getTokenURL() {
return tokenURL;
}
protected JWTToken getAuthorizationToken() throws OpenIdConnectRESTHelperException {
if (clientId != null && clientSecret != null && tokenURL != null) {
log.debug("Getting OIDC token for clientId '{}' from: {}", clientId, tokenURL);
return OpenIdConnectRESTHelper.queryClientToken(clientId, clientSecret, tokenURL);
} else {
log.debug("Can't get OIDC token since not all the required params were provied");
return null;
}
}
public abstract class HTTPVerb {
protected static final int CONNECTION_TIMEOUT = 10000;
protected static final int READ_TIMEOUT = 5000;
protected URL baseEndpoint;
public HTTPVerb(URL baseEndpoint) {
this.baseEndpoint = baseEndpoint;
}
}
public class HTTPPost extends HTTPVerb implements Runnable {
private static final int PAUSE_INCREMENT_FACTOR = 2;
private static final long MAX_RETRYINGS = 2;
private final int[] RETRY_CODES = { HttpURLConnection.HTTP_BAD_GATEWAY,
HttpURLConnection.HTTP_CLIENT_TIMEOUT, HttpURLConnection.HTTP_GATEWAY_TIMEOUT,
HttpURLConnection.HTTP_INTERNAL_ERROR };
private Event event;
private String result;
private long actualPause = 1;
private long retryings = 0;
public HTTPPost(URL baseEndpoint, Event event) {
super(baseEndpoint);
this.event = event;
}
@Override
public void run() {
try {
URL eventEndpoint = null;
try {
eventEndpoint = new URL(baseEndpointURL, event.getName());
} catch (MalformedURLException e) {
log.error("Cannot compute event endpoint URL. Event name: " + event.getName() + ", base endpoint: "
+ baseEndpointURL, e);
return;
}
boolean OK = false;
do {
try {
log.debug("Getting auth token for client '{}' if needed", clientId);
JWTToken token = getAuthorizationToken();
log.debug("Performing HTTP POST to: {}", baseEndpoint);
HttpURLConnection connection = (HttpURLConnection) eventEndpoint.openConnection();
connection.setRequestMethod("POST");
connection.setConnectTimeout(CONNECTION_TIMEOUT);
log.trace("HTTP connection timeout set to: {}", connection.getConnectTimeout());
connection.setReadTimeout(READ_TIMEOUT);
log.trace("HTTP connection Read timeout set to: {}", connection.getReadTimeout());
connection.setRequestProperty("Content-Type", "application/json");
// Commented out as per the Conductor issue: https://github.com/Netflix/conductor/issues/376
// connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
if (token != null) {
log.debug("Setting authorization header as: {}", token.getAccessTokenAsBearer());
connection.setRequestProperty("Authorization", token.getAccessTokenAsBearer());
} else {
log.debug("Sending request without authorization header");
}
OutputStream os = connection.getOutputStream();
String jsonString = event.toJSONString();
log.trace("Sending event JSON: {}", jsonString);
os.write(jsonString.getBytes("UTF-8"));
os.flush();
os.close();
StringBuilder sb = new StringBuilder();
int httpResultCode = connection.getResponseCode();
log.trace("HTTP Response code: {}", httpResultCode);
log.trace("Reading response");
InputStreamReader isr = null;
if (httpResultCode == HttpURLConnection.HTTP_OK) {
OK = true;
// From this point ahead every exception should not set OK to false
// since the server acknowledged with 200
isr = new InputStreamReader(connection.getInputStream(), "UTF-8");
} else {
isr = new InputStreamReader(connection.getErrorStream(), "UTF-8");
}
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
sb.append("\n" + line);
}
br.close();
isr.close();
sb.deleteCharAt(0);
result = sb.toString();
if (OK) {
log.debug("[{}] Event publish for {} is OK", httpResultCode, event.getName());
} else {
log.trace("Response message from server:\n{}", result);
if (shouldRetryWithCode(httpResultCode)) {
if (retryings <= MAX_RETRYINGS) {
log.warn("[{}] Event publish ERROR, retrying in {} seconds", httpResultCode,
actualPause);
Thread.sleep(actualPause * 1000);
log.debug("Start retrying event publish: {}", event.getName());
actualPause *= PAUSE_INCREMENT_FACTOR;
retryings += 1;
} else {
log.error("[{}] Event publish ERROR, exhausted tries after {} retryings",
httpResultCode,
retryings);
break;
}
} else {
log.info("[{}] Event publish ERROR but should not retry with this HTTP code",
httpResultCode);
break;
}
}
} catch (IOException | OpenIdConnectRESTHelperException e) {
log.error("POSTing JSON to: " + eventEndpoint, e);
}
} while (!OK);
} catch (InterruptedException e) {
log.error("Sleeping before retry event send", e);
}
}
private boolean shouldRetryWithCode(int httpResultCode) {
return Arrays.binarySearch(RETRY_CODES, httpResultCode) > 0;
}
public String getResult() {
return result;
}
}
public class HTTPGet extends HTTPVerb {
private String id;
public HTTPGet(URL baseEndpoint, String id) {
super(baseEndpoint);
this.id = id;
}
public JSONObject readJSON() {
URL endpoint = null;
JSONObject results = null;
try {
endpoint = new URL(baseEndpoint, id);
} catch (MalformedURLException e) {
log.error("Cannot compute retrieve endpoint URL. ID: " + id + ", base endpoint: " + baseEndpointURL, e);
return null;
}
try {
log.debug("Getting auth token for client '{}' if needed", clientId);
JWTToken token = getAuthorizationToken();
log.debug("Performing HTTP GET to: {}", endpoint);
HttpURLConnection connection = (HttpURLConnection) endpoint.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(CONNECTION_TIMEOUT);
log.trace("HTTP connection timeout set to: {}", connection.getConnectTimeout());
connection.setReadTimeout(READ_TIMEOUT);
log.trace("HTTP connection Read timeout set to: {}", connection.getReadTimeout());
if (token != null) {
log.debug("Setting authorization header as: {}", token.getAccessTokenAsBearer());
connection.setRequestProperty("Authorization", token.getAccessTokenAsBearer());
} else {
log.debug("Sending request without authorization header");
}
StringBuilder sb = new StringBuilder();
int httpResultCode = connection.getResponseCode();
log.trace("HTTP Response code: {}", httpResultCode);
log.trace("Reading response");
InputStreamReader isr = null;
if (httpResultCode == HttpURLConnection.HTTP_OK) {
isr = new InputStreamReader(connection.getInputStream(), "UTF-8");
} else {
isr = new InputStreamReader(connection.getErrorStream(), "UTF-8");
}
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
isr.close();
if (httpResultCode == HttpURLConnection.HTTP_OK) {
log.debug("[{}] Got results for {}", httpResultCode, id);
try {
results = (JSONObject) new JSONParser().parse(sb.toString());
} catch (ParseException e) {
log.warn("Error parsing results string as JSON: {}", sb.toString());
}
} else {
log.warn("[{}] Error getting results for ID {}", httpResultCode, id);
}
} catch (IOException | OpenIdConnectRESTHelperException e) {
log.error("Getting results from: " + endpoint, e);
}
return results;
}
}
}