package spimedb.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.resource.FileResourceManager;
import io.undertow.util.Headers;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spimedb.SpimeDB;
import java.io.*;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Deque;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import static io.undertow.Handlers.resource;
/**
* dead simple HTTP response cache
*/
public class HTTP {
static final String defaultClientPath = "./src/main/resources/public";
private static final Logger logger = LoggerFactory.getLogger(HTTP.class);
private final Path cachePath;
public HTTP() {
this(SpimeDB.TMP_SPIMEDB_CACHE_PATH);
}
public HTTP(String cachePath) {
this.cachePath = FileUtils.pathOrCreate(cachePath);
}
@Nullable public static Path tmpCacheDir() {
return FileUtils.pathOrCreate(SpimeDB.TMP_SPIMEDB_CACHE_PATH);
}
@Nullable public static File tmpCacheFile(String path) {
File f = tmpCacheDir().resolve(path).toFile();
if (!f.exists()) {
try {
f.createNewFile();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
return f;
}
public static void send(String s, HttpServerExchange ex) {
ex.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
ex.getResponseSender().send(s);
ex.endExchange();
}
public static void stream(HttpServerExchange ex, Consumer<OutputStream> s) {
stream(ex, s, "text/plain");
}
public static void stream(HttpServerExchange ex, Consumer<OutputStream> s, String contentType) {
ex.getResponseHeaders().put(Headers.CONTENT_TYPE, contentType);
ex.dispatch(() -> {
ex.startBlocking();
OutputStream os = ex.getOutputStream();
s.accept(os);
//ex.getResponseSender().close();
ex.endExchange();
});
}
static void send(byte[] s, HttpServerExchange ex, String type) {
ex.getResponseHeaders().put(Headers.CONTENT_TYPE, type);
ex.getResponseSender().send(ByteBuffer.wrap(s));
//ex.getResponseSender().close();
ex.endExchange();
}
static void send(JsonNode d, HttpServerExchange ex) {
ex.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
ex.startBlocking();
try {
JSON.json.writeValue(ex.getOutputStream(), d);
} catch (IOException ex1) {
logger.warn("send: {}", ex1);
}
ex.getResponseSender().close();
}
public static String[] getStringArrayParameter(HttpServerExchange ex, String param) throws IOException {
String s = getStringParameter(ex, param);
ArrayNode a = JSON.json.readValue(s, ArrayNode.class);
String[] ids = JSON.toStrings(a);
return ids;
}
@Nullable public static String getStringParameter(HttpServerExchange ex, String param) {
Map<String, Deque<String>> reqParams = ex.getQueryParameters();
Deque<String> idArray = reqParams.get(param);
if (idArray==null)
return null;
return idArray.getFirst();
}
public static HttpHandler handleClientResources() {
return handleClientResources(defaultClientPath);
}
public static HttpHandler handleClientResources(String clientPath) {
File base = new File(clientPath);
return resource(
new FileResourceManager(base, 0))
// new CachingResourceManager(
// 16384,
// 16*1024*1024,
// new DirectBufferCache(100, 10, 1000),
// new PathResourceManager(getResourcePath(), 0, true, true),
// 0 //7 * 24 * 60 * 60 * 1000
// ))
.setCachable((x) -> true)
//.setDirectoryListingEnabled(true)
.addWelcomeFiles("index.html")
;
// return header(resource( new FileResourceManager(base, 100, true, "/") )
// .setWelcomeFiles("index.html")
// .setDirectoryListingEnabled(true), "Access-Control-Allow-Origin", "*");
}
public void asStream(String url, /* long maxAge */ Consumer<InputStream> result) throws IOException {
//TODO use Tee pipe to read and write the cache miss simultaneously and still provide streaming result to the callee
//and just provide a FileInputStream to a cache hit
IOException e = asFile(url, file -> {
try {
result.accept(new FileInputStream(file));
return null; //OK
} catch (FileNotFoundException ee) {
//e.printStackTrace();
return ee;
}
});
if (e!=null)
throw e;
}
public String asString(String url) throws IOException {
return asFile(url, f->{
try {
return IOUtils.toString(new FileInputStream(f));
} catch (IOException e) {
//e.printStackTrace();
return e.toString();
}
});
}
public void asFile(String url, Consumer<File> result) throws IOException {
asFile(url, f -> { result.accept(f); return null; } );
}
public static String filenameable(String inputName) {
return inputName.replaceAll("[^\\/a-zA-Z0-9-_\\.]", "_");
}
public <X> X asFile(String url, /* long maxAge, etc.. */ Function<File,X> result) throws IOException {
URL u = new URL(url);
//u.openConnection().... <- properly check cache conditions via the response headers or something
Path targetPath = cachePath.resolve(filenameable(u.toString()));
File target = targetPath.toFile();
if (!target.exists()) {
logger.info("cache miss: {} @ {}", url, target);
Files.createFile(targetPath);
//TODO file locking
FileOutputStream fos = new FileOutputStream(target);
IOUtils.copy(u.openStream(), fos);
fos.close();
} else {
logger.info("cache hit: {} @ {}", url, target);
}
return result.apply(target);
}
public static void main(String[] args) throws IOException {
HTTP http = new HTTP();
http.asFile("http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/significant_day.geojson", (Consumer<File>) System.out::println);
}
}