Last active
December 21, 2015 15:19
-
-
Save mufumbo/6325670 to your computer and use it in GitHub Desktop.
proof of concept for simulating the appengine dispatch.xml while in development mode
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.yumyumlabs.web.filter; | |
import com.google.appengine.api.labs.modules.ModulesService; | |
import com.google.appengine.api.labs.modules.ModulesServiceFactory; | |
import com.google.appengine.api.utils.SystemProperty; | |
import javax.servlet.*; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.http.HttpServletResponse; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStreamWriter; | |
import java.net.HttpURLConnection; | |
import java.net.URL; | |
import java.net.URLEncoder; | |
import java.util.Enumeration; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.logging.Logger; | |
public class AppengineDispatchFilter implements Filter { | |
final static Logger logger = Logger.getLogger(AppengineDispatchFilter.class.getName()); | |
private final static boolean IS_PROD = SystemProperty.environment.value() == SystemProperty.Environment.Value.Production; | |
final static Map<String, String> urlModuleMap = new HashMap<>(); | |
@Override | |
public void init(FilterConfig filterConfig) throws ServletException { | |
if (!IS_PROD) { | |
logger.info("fake dispatcher initialized with dispatch.xml config:"); | |
mapConfigurationDispatch(); | |
} | |
} | |
// TODO: instead of changing this by hand, read from dispatch.xml and figure it out | |
// TODO: https://code.google.com/p/googleappengine/source/browse/trunk/java/src/main/com/google/apphosting/utils/config/DispatchXmlReader.java | |
private void mapConfigurationDispatch() { | |
urlModuleMap.put("*/admin*", "webadmin"); | |
urlModuleMap.put("*/api/recipe/list-by-path*", "searcher"); | |
urlModuleMap.put("*/api/recipe/search*", "searcher"); | |
urlModuleMap.put("*/c/i/*", "content"); | |
// TODO: this shouldn't be here if TaskOptions header host was working. | |
urlModuleMap.put("*/tasks*", "tasks"); | |
} | |
@Override | |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | |
if (IS_PROD) { | |
chain.doFilter(request, response); | |
} | |
else { | |
HttpServletResponse resp = (HttpServletResponse) response; | |
HttpServletRequest req = ((HttpServletRequest) request); | |
String uri = req.getRequestURI().toString(); | |
String queryString = req.getQueryString(); | |
String method = req.getMethod(); | |
String module = null; | |
for (String originalRule : urlModuleMap.keySet()) { | |
String rule = originalRule; | |
// we're not supporting this type of rule | |
if (rule.startsWith("*")) { | |
rule = rule.substring(1, rule.length()); | |
} | |
if (rule.endsWith("*")) { | |
rule = rule.substring(0, rule.length() - 1); | |
if (uri.startsWith(rule)) { | |
module = urlModuleMap.get(originalRule); | |
logger.info("Found rule[" + originalRule + "] module[" + module + "] on uri[" + uri + "]"); | |
break; | |
} | |
} | |
if (rule.equals(uri)) { | |
module = urlModuleMap.get(originalRule); | |
logger.info("Found exact match[" + originalRule + "] module[" + module + "]"); | |
break; | |
} | |
} | |
if (module != null) { | |
ModulesService modulesApi = ModulesServiceFactory.getModulesService(); | |
String moduleHost = modulesApi.getModuleHostname(module, modulesApi.getDefaultVersion(module)); | |
String url = "http://" + moduleHost + uri; | |
if (queryString != null) { | |
url += "?" + queryString; | |
} | |
URL u = new URL(url); | |
HttpURLConnection connection = (HttpURLConnection) u.openConnection(); | |
connection.setRequestMethod(method); | |
connection.setConnectTimeout(30000); | |
connection.setReadTimeout(30000); | |
connection.setDoOutput(true); | |
// Simply passing headers through. TODO: improve this for multi value | |
StringBuilder headerDbg = new StringBuilder(); | |
Enumeration headers = req.getHeaderNames(); | |
while (headers != null && headers.hasMoreElements()) { | |
Object name = headers.nextElement(); | |
Enumeration values = req.getHeaders(name.toString()); | |
while (values != null && values.hasMoreElements()) { | |
Object value = values.nextElement(); | |
if (value != null) { | |
String key = name.toString(); | |
if ("host".equals(key.toLowerCase())) { | |
logger.info("dispatch OldHost[" + value.toString() + "] NewHost[" + moduleHost + "]"); | |
} | |
else { | |
if (headerDbg.length() > 0) | |
headerDbg.append("&"); | |
headerDbg.append(key).append("={").append(value.toString()).append("}"); | |
} | |
connection.setRequestProperty(key, value.toString()); | |
} | |
} | |
} | |
logger.info("dispatching[" + module + "] " + method + " to " + url + " with headrs:[" + headerDbg.toString() + "]"); | |
if ("GET".equals(method)) { | |
connection.setInstanceFollowRedirects(true); // TODO: handle redirects passing through the redirect header to this request. | |
} | |
else if ("POST".equals(method)) { | |
connection.setDoInput(true); | |
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); | |
Enumeration<String> params = req.getParameterNames(); | |
int i = 0; | |
while (params != null && params.hasMoreElements()) { | |
String param = params.nextElement(); | |
String[] values = req.getParameterValues(param); | |
if (values != null) { | |
for (String value : values) { | |
if (i++ > 0) { | |
writer.write("&"); | |
} | |
param = URLEncoder.encode(param, "utf-8"); | |
value = URLEncoder.encode(value, "utf-8"); | |
writer.write(param); | |
writer.write("="); | |
writer.write(value); | |
} | |
} | |
} | |
writer.flush(); | |
writer.close(); | |
} | |
connection.connect(); | |
int status = connection.getResponseCode(); | |
resp.setStatus(status); | |
if (status != HttpURLConnection.HTTP_OK) { | |
logger.info("dispatch of " + url + " with status " + status); | |
} | |
ByteArrayOutputStream out = new ByteArrayOutputStream(); | |
byte[] tmp = new byte[4096]; | |
InputStream in = connection.getInputStream(); | |
int len = 0; | |
while ((len = in.read(tmp)) > 0) { | |
out.write(tmp, 0, len); | |
} | |
in.close(); | |
out.close(); | |
byte[] content = out.toByteArray(); | |
response.getOutputStream().write(content); | |
} | |
else { | |
chain.doFilter(request, response); | |
} | |
} | |
} | |
@Override | |
public void destroy() { | |
if (!IS_PROD) | |
logger.info("destroy"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is an updated version that supports parsing the dispatch.xml file. It also should work with POST methods with non-form style payloads (e.g. a JSON payload) :
https://gist.github.com/codedance/9340965