package nodebox.function;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.jayway.jsonpath.JsonPath;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
public class NetworkFunctions {
public static final Map<Integer, Response> responseCache = new HashMap<Integer, Response>();
public static final FunctionLibrary LIBRARY;
static {
LIBRARY = JavaLibrary.ofClass("network", NetworkFunctions.class,
"httpGet", "queryJSON", "encodeURL");
}
public static synchronized Map<String, Object> httpGet(final String url, final String username, final String password, final long refreshTimeSeconds) {
Integer cacheKey = Objects.hashCode(url, username, password);
if (responseCache.containsKey(cacheKey)) {
Response r = responseCache.get(cacheKey);
long timeNow = nowSeconds();
long timeFetched = r.timeFetched;
if ((timeNow - timeFetched) <= refreshTimeSeconds) {
return r.response;
}
}
Map<String, Object> r = _httpGet(url, username, password);
Response res = new Response(nowSeconds(), r);
responseCache.put(cacheKey, res);
return r;
}
public static Iterable<?> queryJSON(final Object json, final String query) {
if (json instanceof Map) {
Map<?, ?> requestMap = (Map<?, ?>) json;
if (requestMap.containsKey("body")) {
return queryJSON(requestMap.get("body"), query);
} else {
throw new IllegalArgumentException("Cannot parse JSON input.");
}
} else if (json instanceof String) {
Object results = JsonPath.read((String) json, query);
if (!(results instanceof Iterable)) {
return ImmutableList.of(results);
} else {
return (Iterable<?>) results;
}
} else {
throw new IllegalArgumentException("Cannot parse JSON input.");
}
}
public static String encodeURL(final String s) {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
return s;
}
}
private static Map<String, Object> _httpGet(final String url, final String username, final String password) {
HttpGet request = new HttpGet(url);
if (username != null && !username.trim().isEmpty()) {
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password);
BasicScheme scheme = new BasicScheme();
Header authorizationHeader;
try {
authorizationHeader = scheme.authenticate(credentials, request, new BasicHttpContext());
} catch (AuthenticationException e) {
throw new RuntimeException(e);
}
request.addHeader(authorizationHeader);
}
try {
DefaultHttpClient client = new DefaultHttpClient();
HttpResponse response = client.execute(request);
HttpEntity entity = response.getEntity();
if (entity != null) {
String body = EntityUtils.toString(entity);
HashMap<String, String> m = new HashMap<String, String>();
for (Header h : response.getAllHeaders()) {
m.put(h.getName(), h.getValue());
}
Map<String, String> headers = ImmutableMap.copyOf(m);
return ImmutableMap.of(
"body", body,
"statusCode", response.getStatusLine().getStatusCode(),
"headers", headers);
} else {
// 204 No Content
return emptyResponse(204);
}
} catch (IllegalStateException e) {
if (e.getMessage().startsWith("Target host must not be null")) {
throw new RuntimeException("URL should start with \"http://\" or \"https://\".");
} else {
throw e;
}
} catch (IOException e) {
// We return status code 408 (Request Timeout) here since we always want to return a valid response.
// However, the exception signifies an IO error, so maybe the network connection is down.
// This has no valid HTTP response (since there is NO response).
return emptyResponse(408);
}
}
private static Map<String, Object> emptyResponse(int statusCode) {
return ImmutableMap.<String, Object>of(
"body", "",
"statusCode", statusCode
);
}
private static long nowSeconds() {
return System.currentTimeMillis() / 1000;
}
private static class Response {
private final long timeFetched;
private final Map<String, Object> response;
private Response(long timeFetched, Map<String, Object> response) {
this.timeFetched = timeFetched;
this.response = response;
}
}
}