LocalHTTPServer.java (97 lines of code) (raw):
//#!java --source 11
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class LocalHTTPServer {
private static final List<String> STATIC_CONTENT = List.of(".html", ".htm", ".css", ".js", ".wasm");
private static final List<String> RESTRICTED_HEADERS = List.of("connection", "host", "content-length", "origin", "user-agent", "authorization");
private static final Map<String, List<String>> CORS_HEADERS = Map.of(
"Access-Control-Allow-Origin", List.of("*"),
"Access-Control-Allow-Headers", List.of("*"));
private static final ConcurrentMap<String, String> CONTENT_TYPES_CACHE = new ConcurrentHashMap<>();
public static void main(String[] args) throws IOException, URISyntaxException {
HttpServer server = HttpServer.create(new InetSocketAddress(80), 0);
server.setExecutor(Executors.newCachedThreadPool());
Executor httpClientExecutor = Executors.newWorkStealingPool();
var context = server.createContext("/WAGL/", (HttpExchange exchange) -> {
var contentURI = exchange.getRequestURI().getPath().substring(1);
var extension = contentURI.contains(".") ? contentURI.substring(contentURI.lastIndexOf(".")) : "";
var path = (isStaticContent(extension) ? "http://localhost:3000/" : "https://dev.azure.com/") + contentURI;
var requestMethod = exchange.getRequestMethod();
System.out.println("path " + path + " method " + exchange.getRequestMethod());
if (!".ico".equals(extension)) {
try (var outputStream = exchange.getResponseBody()) {
var responseHeaders = exchange.getResponseHeaders();
if (requestMethod.equalsIgnoreCase("options")) {
CORS_HEADERS.forEach(responseHeaders::put);
exchange.sendResponseHeaders(200, -1);
outputStream.flush();
} else {
var response = executeRequest(path, httpClientExecutor, exchange);
var body = response.body();
var headers = response.headers().map();
headers.forEach(responseHeaders::put);
CORS_HEADERS.forEach(responseHeaders::put);
//responseHeaders.put("Connection", List.of("close"));
exchange.sendResponseHeaders(200, 0);
outputStream.write(body);
outputStream.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
exchange.sendResponseHeaders(404, 0);
exchange.getResponseBody().flush();
}
});
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
server.removeContext(context);
server.stop(0);
}));
server.start();
}
private static boolean isStaticContent(String extension) {
String canonicalExtension = Objects.toString(extension, "");
return STATIC_CONTENT.contains(canonicalExtension);
}
private static HttpResponse<byte[]> executeRequest(
String uri,
Executor httpClientExecutor,
HttpExchange exchange
) throws IOException, InterruptedException {
var cookie = System.getenv("X_COOKIE");
var method = exchange.getRequestMethod();
HttpRequest.Builder reqBuilder = HttpRequest.newBuilder(URI.create(uri))
.timeout(Duration.ofMillis(30000))
.method(method.toUpperCase(), !method.equalsIgnoreCase("GET") ? HttpRequest.BodyPublishers.ofInputStream(exchange::getRequestBody): HttpRequest.BodyPublishers.noBody())
.version(HttpClient.Version.HTTP_1_1);
exchange.getRequestHeaders().forEach((name, value) -> {
if (RESTRICTED_HEADERS.stream().noneMatch(name::equalsIgnoreCase)) {
reqBuilder.header(name, String.join("", value));
System.out.printf("header %s value %s%n", name, value);
}
});
HttpRequest httpRequest = reqBuilder
.header("Cookie", cookie)
.build();
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(15000))
.executor(httpClientExecutor)
.build();
return httpClient
.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray());
}
}