Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added handle of HTTP 429 error code #31

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<groupId>com.ciscospark</groupId>
<artifactId>ciscospark-client</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.0-elibra.0</version>

<build>
<plugins>
Expand Down
146 changes: 133 additions & 13 deletions src/main/java/com/ciscospark/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.net.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;

import javax.net.ssl.HttpsURLConnection;
Expand Down Expand Up @@ -85,6 +87,15 @@ <T> Iterator<T> list(Class<T> clazz, URL url) {
return new PagingIterator<T>(clazz, url);
}

<T> T head(Class<T> clazz, String path, List<String[]> params){
return readHeaders(clazz, requestHeaders("HEAD", path, params, null));
}

<T> T head(Class<T> clazz, URL url){
return readHeaders(clazz, request(url, "HEAD", null).connection.getHeaderFields());
}


void delete(String path) {
delete(getUrl(path, null));
}
Expand Down Expand Up @@ -135,6 +146,14 @@ <T> InputStream request(String method, String path, List<String[]> params, T bod
URL url = getUrl(path, params);
return request(url, method, body).inputStream;
}

<T> Map<String, List<String>> requestHeaders(String method, String path, List<String[]> params, T body) {
URL url = getUrl(path, params);
return request(url, method, body).connection.getHeaderFields();
}



static class Response {
HttpsURLConnection connection;
InputStream inputStream;
Expand Down Expand Up @@ -167,7 +186,7 @@ private boolean authenticate() {
if (clientId != null && clientSecret != null) {
if (authCode != null && redirectUri != null) {
log(Level.FINE, "Requesting access token");
URL url = getUrl("/access_token",null);
URL url = getUrl("/access_token", null);
AccessTokenRequest body = new AccessTokenRequest();
body.setGrant_type("authorization_code");
body.setClient_id(clientId);
Expand All @@ -182,7 +201,7 @@ private boolean authenticate() {
return true;
} else if (refreshToken != null) {
log(Level.FINE, "Refreshing access token");
URL url = getUrl("/access_token",null);
URL url = getUrl("/access_token", null);
AccessTokenRequest body = new AccessTokenRequest();
body.setClient_id(clientId);
body.setClient_secret(clientSecret);
Expand All @@ -202,23 +221,26 @@ private void log(Level level, String msg, Object... args) {
logger.log(level, msg, args);
}
}

private <T> Response doRequest(URL url, String method, T body) {
return doRequest(url, method, body, 0);
}

private <T> Response doRequest(URL url, String method, T body, int retryNumber) {
try {
HttpsURLConnection connection = getConnection(url);
String trackingId = connection.getRequestProperty(TRACKING_ID);
connection.setRequestMethod(method);
if (logger != null && logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Request {0}: {1} {2}",
new Object[] { trackingId, method, connection.getURL().toString() });
new Object[]{trackingId, method, connection.getURL().toString()});
}
if (body != null) {
connection.setDoOutput(true);
if (logger != null && logger.isLoggable(Level.FINEST)) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
writeJson(body, byteArrayOutputStream);
logger.log(Level.FINEST, "Request Body {0}: {1}",
new Object[] { trackingId, byteArrayOutputStream.toString() });
new Object[]{trackingId, byteArrayOutputStream.toString()});
byteArrayOutputStream.writeTo(connection.getOutputStream());
} else {
writeJson(body, connection.getOutputStream());
Expand All @@ -228,9 +250,23 @@ private <T> Response doRequest(URL url, String method, T body) {
int responseCode = connection.getResponseCode();
if (logger != null && logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Response {0}: {1} {2}",
new Object[] { trackingId, responseCode, connection.getResponseMessage() });
new Object[]{trackingId, responseCode, connection.getResponseMessage()});
}
if (responseCode == 429) {
if(retryNumber>5)
throw new RuntimeException("Too many retry after HTTP response 429");

// retry based of Retry-After response header
final String retryAfter = connection.getHeaderField("Retry-After");
try {
Thread.sleep(Long.parseLong(retryAfter));
return doRequest(url, method, body, retryNumber+1);
} catch (InterruptedException e) {
log(Level.SEVERE, e.getMessage());
}
} else {
checkForErrorResponse(connection, responseCode);
}
checkForErrorResponse(connection, responseCode);

if (logger != null && logger.isLoggable(Level.FINEST)) {
InputStream inputStream = logResponse(trackingId, connection.getInputStream());
Expand Down Expand Up @@ -337,6 +373,25 @@ private HttpsURLConnection getConnection(URL url) throws IOException {
}


private static <T> T readHeaders(Class<T> clazz, Map<String, List<String>> headerFields){
try{
T result = clazz.newInstance();
for (Field field : clazz.getDeclaredFields()){
field.setAccessible(true);
Object fieldObject = field.get(result);
if (fieldObject instanceof HeaderField &&
headerFields.containsKey(((HeaderField) fieldObject).getHeaderName())){
((HeaderField) fieldObject).setHeaderValue(headerFields.get(((HeaderField) fieldObject).getHeaderName()));
}
}
return result;
} catch (Exception ex) {
throw new SparkException(ex);
}
}



private static <T> T readJson(Class<T> clazz, InputStream inputStream) {
JsonParser parser = Json.createParser(inputStream);
parser.next();
Expand All @@ -349,7 +404,8 @@ private static <T> T readObject(Class<T> clazz, JsonParser parser) {
List<Object> list = null;
Field field = null;
String key = "";
PARSER_LOOP: while (parser.hasNext()) {
PARSER_LOOP:
while (parser.hasNext()) {
JsonParser.Event event = parser.next();
switch (event) {
case KEY_NAME:
Expand Down Expand Up @@ -413,7 +469,7 @@ private static <T> T readObject(Class<T> clazz, JsonParser parser) {
Object next = iterator.next();
iterator.set(URI.create(next.toString()));
}
} else if (field.getType().getComponentType() != null /* this is an array class */ ) {
} else if (field.getType().getComponentType() != null /* this is an array class */) {
itemClazz = field.getType().getComponentType(); // this would also cover the String array we had previously
} else {
throw new SparkException("bad field class: " + field.getType());
Expand All @@ -429,7 +485,7 @@ private static <T> T readObject(Class<T> clazz, JsonParser parser) {

// the field type points us in the direction of the class to instantiate


if (null != field) {
if (null != list) {
// we are in a list - we likely have a s at the end, which we should drop
Expand Down Expand Up @@ -600,12 +656,11 @@ public void remove() {
}



private void scrollToItemsArray(JsonParser parser) {
JsonParser.Event event;
while (parser.hasNext()) {
event = parser.next();
if (event == JsonParser.Event.KEY_NAME && parser.getString().equals("items")) {
if (event == JsonParser.Event.KEY_NAME && parser.getString().equals("items")) {
break;
}
}
Expand All @@ -617,7 +672,6 @@ private void scrollToItemsArray(JsonParser parser) {
}



private static final Pattern linkPattern = Pattern.compile("\\s*<(\\S+)>\\s*;\\s*rel=\"(\\S+)\",?");

private HttpsURLConnection getLink(HttpsURLConnection connection, String rel) throws IOException {
Expand All @@ -640,4 +694,70 @@ private HttpsURLConnection parseLinkHeader(String link, String desiredRel) throw
}
return result;
}

public File getFile(URL url) {
File file = this.doRequest4File(url);
return file;
}

private File doRequest4File(URL url) {
try {
HttpURLConnection connection = this.getConnection(url);
String trackingId = connection.getRequestProperty("TrackingID");
connection.setRequestMethod("GET");
if (this.logger != null && this.logger.isLoggable(Level.FINE)) {
this.logger.log(Level.FINE, "Request {0}: {1} {2}", new Object[]{trackingId, "GET", connection.getURL().toString()});
}

int responseCode = connection.getResponseCode();
if (this.logger != null && this.logger.isLoggable(Level.FINE)) {
this.logger.log(Level.FINE, "Response {0}: {1} {2}", new Object[]{trackingId, responseCode, connection.getResponseMessage()});
}

if (responseCode != 200) {
this.logger.info("No file to download. Server replied HTTP code: " + responseCode);
connection.disconnect();
return null;
} else {
String fileName = "";
String disposition = connection.getHeaderField("Content-Disposition");
String contentType = connection.getContentType();
int contentLength = connection.getContentLength();
if (disposition != null) {
int index = disposition.indexOf("filename=");
if (index > 0) {
fileName = disposition.substring(index + 10, disposition.length() - 1);
}
} else {
String fileURL = url.toString();
fileName = fileURL.substring(fileURL.lastIndexOf("/") + 1);
}

if (this.logger != null && this.logger.isLoggable(Level.FINE)) {
this.logger.info("Content-Type = " + contentType);
this.logger.info("Content-Disposition = " + disposition);
this.logger.info("Content-Length = " + contentLength);
this.logger.info("fileName = " + fileName);
}

InputStream inputStream = connection.getInputStream();
String tempDirectory = System.getProperty("java.io.tmpdir");
Path saveFilePath = Paths.get(tempDirectory, fileName);
File file = saveFilePath.toFile();
FileOutputStream outputStream = new FileOutputStream(file);
byte[] buffer = new byte[512];

int bytesRead;
while((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}

outputStream.close();
inputStream.close();
return file;
}
} catch (IOException var16) {
throw new SparkException("io error", var16);
}
}
}
29 changes: 29 additions & 0 deletions src/main/java/com/ciscospark/Content.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.ciscospark;


import java.util.List;

public class Content {

private HeaderField cacheControl = new HeaderField("Cache-Control");
private HeaderField contentDisposition = new HeaderField("Content-Disposition");
private HeaderField contentLength = new HeaderField("Content-Length");
private HeaderField contentType = new HeaderField("Content-Type");

public List<String> getCacheControl() {
return cacheControl.getHeaderValue();
}

public List<String> getContentDisposition() {
return contentDisposition.getHeaderValue();
}

public List<String> getContentLength() {
return contentLength.getHeaderValue();
}

public List<String> getContentType() {
return contentType.getHeaderValue();
}

}
25 changes: 25 additions & 0 deletions src/main/java/com/ciscospark/HeaderField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.ciscospark;

import java.util.List;

public class HeaderField {

private String headerName;
private List<String> headerValue;

public HeaderField(String headerName) {
this.headerName = headerName;
}

public String getHeaderName() {
return headerName;
}

public List<String> getHeaderValue() {
return headerValue;
}

public void setHeaderValue(List<String> headerValue) {
this.headerValue = headerValue;
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/ciscospark/RequestBuilder.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ciscospark;

import java.io.File;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
Expand All @@ -15,7 +16,12 @@ public interface RequestBuilder<T> {
T post(T body);
T put(T body);
T get();

T head();

Iterator<T> iterate();
LinkedResponse<List<T>> paginate();
void delete();

File getFile();
}