Support reverse proxy

This commit is contained in:
yushijinhun 2018-12-31 00:52:48 +08:00
parent bdd5f4bfcb
commit 9cfb6325a3
No known key found for this signature in database
GPG Key ID: 5BC167F73EA558E4
3 changed files with 114 additions and 12 deletions

View File

@ -1,13 +1,26 @@
package moe.yushi.authlibinjector.httpd;
import static moe.yushi.authlibinjector.util.IOUtils.transfer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IHTTPSession;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IStatus;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.NanoHTTPD;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Response;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Status;
@ -15,8 +28,8 @@ import moe.yushi.authlibinjector.util.Logging;
public class URLProcessor {
private static final Pattern URL_REGEX = Pattern.compile("^https?:\\/\\/(?<domain>[^\\/]+)(?<path>\\/.*)$");
private static final Pattern LOCAL_URL_REGEX = Pattern.compile("^/(?<domain>[^\\/]+)(?<path>\\/.*)$");
private static final Pattern URL_REGEX = Pattern.compile("^(?<protocol>https?):\\/\\/(?<domain>[^\\/]+)(?<path>\\/.*)$");
private static final Pattern LOCAL_URL_REGEX = Pattern.compile("^/(?<protocol>https?)/(?<domain>[^\\/]+)(?<path>\\/.*)$");
private List<URLFilter> filters;
private URLRedirector redirector;
@ -31,17 +44,18 @@ public class URLProcessor {
if (!matcher.find()) {
return Optional.empty();
}
String protocol = matcher.group("protocol");
String domain = matcher.group("domain");
String path = matcher.group("path");
Optional<String> result = transform(domain, path);
Optional<String> result = transform(protocol, domain, path);
if (result.isPresent()) {
Logging.TRANSFORM.fine("Transformed url [" + inputUrl + "] to [" + result.get() + "]");
}
return result;
}
private Optional<String> transform(String domain, String path) {
private Optional<String> transform(String protocol, String domain, String path) {
boolean handleLocally = false;
for (URLFilter filter : filters) {
if (filter.canHandle(domain, path)) {
@ -51,7 +65,7 @@ public class URLProcessor {
}
if (handleLocally) {
return Optional.of("http://127.0.0.1:" + getLocalApiPort() + "/" + domain + path);
return Optional.of("http://127.0.0.1:" + getLocalApiPort() + "/" + protocol + "/" + domain + path);
}
return redirector.redirect(domain, path);
@ -81,6 +95,7 @@ public class URLProcessor {
public Response serve(IHTTPSession session) {
Matcher matcher = LOCAL_URL_REGEX.matcher(session.getUri());
if (matcher.find()) {
String protocol = matcher.group("protocol");
String domain = matcher.group("domain");
String path = matcher.group("path");
for (URLFilter filter : filters) {
@ -90,7 +105,7 @@ public class URLProcessor {
result = filter.handle(domain, path, session);
} catch (Throwable e) {
Logging.HTTPD.log(Level.WARNING, "An error occurred while processing request [" + session.getUri() + "]", e);
return Response.newFixedLength(Status.INTERNAL_ERROR, null, null);
return Response.newFixedLength(Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Internal Server Error");
}
if (result.isPresent()) {
@ -99,11 +114,93 @@ public class URLProcessor {
}
}
}
}
Logging.HTTPD.fine("No handler is found for [" + session.getUri() + "]");
return Response.newFixedLength(Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found");
String target = redirector.redirect(domain, path)
.orElseGet(() -> protocol + "://" + domain + path);
try {
return reverseProxy(session, target);
} catch (IOException e) {
Logging.HTTPD.log(Level.WARNING, "Reserve proxy error", e);
return Response.newFixedLength(Status.BAD_GATEWAY, MIME_PLAINTEXT, "Bad Gateway");
}
} else {
Logging.HTTPD.fine("No handler is found for [" + session.getUri() + "]");
return Response.newFixedLength(Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found");
}
}
};
}
private static final Set<String> ignoredHeaders = new HashSet<>(Arrays.asList("host", "expect", "connection", "keep-alive", "transfer-encoding"));
@SuppressWarnings("resource")
private Response reverseProxy(IHTTPSession session, String upstream) throws IOException {
String method = session.getMethod();
String url = session.getQueryParameterString() == null ? upstream : upstream + "?" + session.getQueryParameterString();
Map<String, String> requestHeaders = session.getHeaders();
ignoredHeaders.forEach(requestHeaders::remove);
InputStream clientIn = session.getInputStream();
Logging.HTTPD.fine(() -> "Reserve proxy: > " + method + " " + url + ", headers: " + requestHeaders);
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod(method);
conn.setDoOutput(clientIn != null);
requestHeaders.forEach(conn::setRequestProperty);
conn.connect();
if (clientIn != null) {
try (OutputStream upstreamOut = conn.getOutputStream()) {
transfer(clientIn, upstreamOut);
}
}
int responseCode = conn.getResponseCode();
String reponseMessage = conn.getResponseMessage();
Map<String, List<String>> responseHeaders = new LinkedHashMap<>();
conn.getHeaderFields().forEach((name, values) -> {
if (name != null && !ignoredHeaders.contains(name.toLowerCase())) {
responseHeaders.put(name, values);
}
});
InputStream upstreamIn;
try {
upstreamIn = conn.getInputStream();
} catch (IOException e) {
upstreamIn = conn.getErrorStream();
}
Logging.HTTPD.fine(() -> "Reserve proxy: < " + responseCode + " " + reponseMessage + " , headers: " + responseHeaders);
IStatus status = new IStatus() {
@Override
public int getRequestStatus() {
return responseCode;
}
@Override
public String getDescription() {
return responseCode + " " + reponseMessage;
}
};
long contentLength = -1;
for (Entry<String, List<String>> header : responseHeaders.entrySet()) {
if ("content-length".equalsIgnoreCase(header.getKey())) {
contentLength = Long.parseLong(header.getValue().get(0));
break;
}
}
Response response;
if (contentLength != -1) {
response = Response.newFixedLength(status, null, upstreamIn, contentLength);
} else {
response = Response.newChunked(status, null, upstreamIn);
}
responseHeaders.forEach((name, values) -> values.forEach(value -> response.addHeader(name, value)));
return response;
}
}

View File

@ -45,6 +45,7 @@ public enum Status implements IStatus {
INTERNAL_ERROR(500, "Internal Server Error"),
NOT_IMPLEMENTED(501, "Not Implemented"),
BAD_GATEWAY(502, "Bad Gateway"),
SERVICE_UNAVAILABLE(503, "Service Unavailable"),
UNSUPPORTED_HTTP_VERSION(505, "HTTP Version Not Supported");

View File

@ -40,12 +40,16 @@ public final class IOUtils {
public static byte[] asBytes(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
transfer(in, out);
return out.toByteArray();
}
public static void transfer(InputStream from, OutputStream to) throws IOException {
byte[] buf = new byte[8192];
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
while ((read = from.read(buf)) != -1) {
to.write(buf, 0, read);
}
return out.toByteArray();
}
public static String asString(byte[] bytes) {