-
Notifications
You must be signed in to change notification settings - Fork 4
Fix coco 4672 template processor race condition #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop-b2school
Are you sure you want to change the base?
Changes from all commits
10ba9a4
952124b
773b437
f5da945
203bc65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| package fr.wseduc.webutils.http; | ||
|
|
||
| import com.samskivert.mustache.Mustache; | ||
| import io.vertx.core.http.HttpServerRequest; | ||
| import io.vertx.core.json.JsonObject; | ||
|
|
||
| import java.io.Reader; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Container for all parameters needed for the template processing | ||
| */ | ||
| public final class ProcessTemplateContext { | ||
|
|
||
| private final HttpServerRequest request; | ||
| private final JsonObject params; | ||
| private final String templateString; | ||
| private final Reader reader; | ||
| private final boolean escapeHtml; | ||
| private final String defaultValue; | ||
| private final Map<String, Mustache.Lambda> lambdas; | ||
|
|
||
| private ProcessTemplateContext(HttpServerRequest request, JsonObject params, String templateString, Reader reader, | ||
| boolean escapeHtml, String defaultValue, Map<String, Mustache.Lambda> lambdas) { | ||
| this.request = request; | ||
| this.params = params; | ||
| this.templateString = templateString; | ||
| this.reader = reader; | ||
| this.escapeHtml = escapeHtml; | ||
| this.defaultValue = defaultValue; | ||
| this.lambdas = new HashMap<>(lambdas); | ||
| } | ||
|
|
||
| public static class Builder { | ||
|
|
||
| private HttpServerRequest request; | ||
| private JsonObject params; | ||
| private String templateString; | ||
| private Reader reader; | ||
| private boolean escapeHtml; | ||
| private String defaultValue; | ||
| private final Map<String, Mustache.Lambda> lambdas = new HashMap<>(); | ||
|
|
||
| public Builder request(HttpServerRequest request) { | ||
| this.request = request; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder escapeHtml(boolean escapeHtml) { | ||
| this.escapeHtml = escapeHtml; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setDefaultValue(String defaultValue) { | ||
| this.defaultValue = defaultValue; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder params(JsonObject params) { | ||
| this.params = params; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder templateString(String templateString) { | ||
| this.templateString = templateString; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder reader(Reader reader) { | ||
| this.reader = reader; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder lambdas(Map<String, Mustache.Lambda> lambdas) { | ||
| this.lambdas.putAll(lambdas); | ||
| return this; | ||
| } | ||
|
|
||
| public HttpServerRequest request() { | ||
| return request; | ||
| } | ||
|
|
||
| public ProcessTemplateContext build() { | ||
| return new ProcessTemplateContext(request, params, templateString, reader, escapeHtml, defaultValue, lambdas); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| public HttpServerRequest request() { | ||
| return request; | ||
| } | ||
|
|
||
| public JsonObject params() { | ||
| return params; | ||
| } | ||
|
|
||
| public String templateString() { | ||
| return templateString; | ||
| } | ||
|
|
||
| public Reader reader() { | ||
| return reader; | ||
| } | ||
|
|
||
| public Map<String, Mustache.Lambda> lambdas() { | ||
| return lambdas; | ||
| } | ||
|
|
||
| public boolean escapeHtml() { | ||
| return escapeHtml; | ||
| } | ||
|
|
||
| public String defaultValue() { | ||
| return defaultValue; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,8 +20,11 @@ | |
| import java.net.URI; | ||
| import java.net.URISyntaxException; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| import com.samskivert.mustache.Mustache; | ||
| import fr.wseduc.webutils.template.TemplateProcessor; | ||
| import fr.wseduc.webutils.template.FileTemplateProcessor; | ||
| import fr.wseduc.webutils.template.lambdas.FormatBirthDateLambda; | ||
|
|
@@ -30,6 +33,7 @@ | |
| import fr.wseduc.webutils.template.lambdas.LocaleDateLambda; | ||
| import fr.wseduc.webutils.template.lambdas.ModsLambda; | ||
| import fr.wseduc.webutils.template.lambdas.StaticLambda; | ||
| import io.micrometer.common.util.StringUtils; | ||
| import io.netty.handler.codec.http.HttpResponseStatus; | ||
| import io.vertx.core.Handler; | ||
| import io.vertx.core.Vertx; | ||
|
|
@@ -47,6 +51,12 @@ | |
|
|
||
| import static fr.wseduc.webutils.Utils.isNotEmpty; | ||
|
|
||
| /** | ||
| * Method with @deprecated, relative to template processing are NOT thread safe and should not be used. Alternative method not deprecated was developed. | ||
| * Lambda for template processing dependent of the user context (request, host, etc..) must not be shared and are local variable. | ||
| * Static lambda that doesn't change with the context can be used | ||
| * Parameter for the template are either NOT thread safe. EscapeHTML, defaultValue, should be passed as local variable | ||
| */ | ||
| public class Renders { | ||
|
|
||
| protected static final Logger log = LoggerFactory.getLogger(Renders.class); | ||
|
|
@@ -57,6 +67,7 @@ public class Renders { | |
| protected String staticHost; | ||
| protected FileTemplateProcessor templateProcessor; | ||
| protected static final List<String> allowedHosts = new ArrayList<>(); | ||
| private final Map<String, Mustache.Lambda> staticLambdas = new HashMap<>(); | ||
|
|
||
| public Renders(Vertx vertx, JsonObject config) { | ||
| this.config = config; | ||
|
|
@@ -66,8 +77,10 @@ public Renders(Vertx vertx, JsonObject config) { | |
| this.vertx = vertx; | ||
| if (vertx != null) { | ||
| this.templateProcessor = new FileTemplateProcessor(vertx, "view/", false); | ||
| this.templateProcessor.setLambda("formatBirthDate", new FormatBirthDateLambda()); | ||
| this.templateProcessor.setLambda("modVersion", new ModsLambda(vertx)); | ||
| staticLambdas.put("formatBirthDate", new FormatBirthDateLambda()); | ||
| staticLambdas.put("modVersion", new ModsLambda(vertx)); | ||
|
|
||
| staticLambdas.forEach(templateProcessor::setLambda); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -84,13 +97,15 @@ protected void init(Vertx vertx, JsonObject config) | |
|
|
||
| if (templateProcessor == null && vertx != null) { | ||
| this.templateProcessor = new FileTemplateProcessor(vertx, "view/", false); | ||
| this.templateProcessor.setLambda("formatBirthDate", new FormatBirthDateLambda()); | ||
| this.templateProcessor.setLambda("modVersion", new ModsLambda(vertx)); | ||
| staticLambdas.put("formatBirthDate", new FormatBirthDateLambda()); | ||
| staticLambdas.put("modVersion", new ModsLambda(vertx)); | ||
|
|
||
| staticLambdas.forEach(templateProcessor::setLambda); | ||
| } | ||
| } | ||
|
|
||
| protected void setLambdaTemplateRequest(final HttpServerRequest request) | ||
| { | ||
| @Deprecated | ||
| protected void setLambdaTemplateRequest(final HttpServerRequest request) { | ||
| String host = Renders.getHost(request); | ||
| if(host == null) // This can happen for forged requests | ||
| host = ""; | ||
|
|
@@ -104,6 +119,88 @@ protected void setLambdaTemplateRequest(final HttpServerRequest request) | |
| this.templateProcessor.setLambda("datetime", new LocaleDateLambda(I18n.acceptLanguage(request))); | ||
| } | ||
|
|
||
| /** | ||
| * Create lambda from request for mustache. These lambdas should be used with TemplateProcessorContext and method | ||
| * that use it in order to avoid race condition on template processing | ||
| * @param request HttpRequest of the user to set various dependent request lambda (host, domain, language, etc..) | ||
| * @return Map with request dependent lambda | ||
| */ | ||
| protected Map<String, Mustache.Lambda> getLambdasFromRequest(final HttpServerRequest request) { | ||
| Map<String, Mustache.Lambda> lambdas = new HashMap<>(); | ||
|
|
||
| String host = Renders.getHost(request); | ||
| if(host == null) // This can happen for forged requests | ||
| host = ""; | ||
| String sttcHost = this.staticHost != null ? this.staticHost : host; | ||
|
|
||
|
|
||
| lambdas.put("i18n", | ||
| new I18nLambda(I18n.acceptLanguage(request), host, I18n.getTheme(request))); | ||
| lambdas.put("static", | ||
| new StaticLambda(config.getBoolean("ssl", sttcHost.startsWith("https")), sttcHost, this.pathPrefix + "/public")); | ||
| lambdas.put("infra", | ||
| new InfraLambda(config.getBoolean("ssl", sttcHost.startsWith("https")), sttcHost, "/infra/public", request.headers().get("X-Forwarded-For") == null)); | ||
| lambdas.put("datetime", new LocaleDateLambda(I18n.acceptLanguage(request))); | ||
| return lambdas; | ||
| } | ||
|
|
||
| public void renderTemplateView(HttpServerRequest request) { | ||
| renderTemplateView(request, new JsonObject(), new HashMap<>()); | ||
| } | ||
|
|
||
| public void renderTemplateView(HttpServerRequest request, Map<String, Mustache.Lambda> lambdas) { | ||
| renderTemplateView(request, new JsonObject(), lambdas); | ||
| } | ||
|
|
||
| public void renderTemplateView(HttpServerRequest request, JsonObject params) { | ||
| renderTemplateView(request, params, new HashMap<>()); | ||
| } | ||
|
|
||
| public void renderTemplateView(HttpServerRequest request, JsonObject params, Map<String, Mustache.Lambda> lambdas) { | ||
| renderTemplateView(request, params, null, null, 200, lambdas); | ||
| } | ||
|
|
||
| public void renderTemplateView(HttpServerRequest request, JsonObject params, String resourceName, | ||
| Reader r, Map<String, Mustache.Lambda> lambdas) { | ||
| renderTemplateView(request, params, resourceName, r, 200, lambdas); | ||
| } | ||
|
|
||
| public void renderTemplateView(final HttpServerRequest request, JsonObject params, | ||
| String resourceName, Reader r, final int status, Map<String, Mustache.Lambda> lambdas) { | ||
|
|
||
| Map<String, Mustache.Lambda> mergeLambdas = getLambdasFromRequest(request); | ||
| mergeLambdas.putAll(lambdas); | ||
| mergeLambdas.putAll(staticLambdas); | ||
|
|
||
| ProcessTemplateContext.Builder context = new ProcessTemplateContext.Builder() | ||
| .escapeHtml(true) | ||
| .reader(r) | ||
| .lambdas( mergeLambdas) | ||
| .request(request); | ||
|
|
||
| context.templateString(genTemplateName(resourceName, request)); | ||
|
|
||
| templateProcessor.processTemplate(context.build(), writer -> { | ||
| if (writer != null) { | ||
| request.response().putHeader("content-type", "text/html; charset=utf-8"); | ||
| request.response().setStatusCode(status); | ||
| if (hookRenderProcess != null) { | ||
| executeHandlersHookRender(request, new Handler<Void>() { | ||
| @Override | ||
| public void handle(Void v) { | ||
| request.response().end(writer.toString()); | ||
| } | ||
| }); | ||
| } else { | ||
| request.response().end(writer.toString()); | ||
| } | ||
| } else { | ||
| renderError(request); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| @Deprecated | ||
| public void renderView(HttpServerRequest request) { | ||
| renderView(request, new JsonObject()); | ||
| } | ||
|
|
@@ -112,14 +209,17 @@ public void renderView(HttpServerRequest request) { | |
| * Render a Mustache template : see http://mustache.github.com/mustache.5.html | ||
| * TODO : isolate scope management | ||
| */ | ||
| @Deprecated | ||
| public void renderView(HttpServerRequest request, JsonObject params) { | ||
| renderView(request, params, null, null, 200); | ||
| } | ||
|
|
||
| @Deprecated | ||
| public void renderView(HttpServerRequest request, JsonObject params, String resourceName, Reader r) { | ||
| renderView(request, params, resourceName, r, 200); | ||
| } | ||
|
|
||
| @Deprecated | ||
| public void renderView(final HttpServerRequest request, JsonObject params, | ||
| String resourceName, Reader r, final int status) { | ||
| processTemplate(request, params, resourceName, r, new Handler<Writer>() { | ||
|
|
@@ -161,37 +261,48 @@ public void handle(Void v) { | |
| handlers[0].handle(null); | ||
| } | ||
|
|
||
| @Deprecated | ||
| public void processTemplate(HttpServerRequest request, String template, JsonObject params, final Handler<String> handler) | ||
| { | ||
| this.setLambdaTemplateRequest(request); | ||
| this.templateProcessor.escapeHTML(true).processTemplate(this.genTemplateName(template, request), params, handler); | ||
| } | ||
|
|
||
| @Deprecated | ||
| public void processTemplate(final HttpServerRequest request, JsonObject p, String resourceName, Reader r, final Handler<Writer> handler) | ||
| { | ||
| this.setLambdaTemplateRequest(request); | ||
| this.templateProcessor.escapeHTML(true); | ||
| this.templateProcessor.processTemplate(this.genTemplateName(resourceName, request), p, r, handler); | ||
| } | ||
|
|
||
| @Deprecated | ||
| public void processTemplate(final HttpServerRequest request, JsonObject p, String resourceName, boolean escapeHTML, final Handler<String> handler) | ||
| { | ||
| this.setLambdaTemplateRequest(request); | ||
| this.templateProcessor.escapeHTML(escapeHTML).processTemplate(this.genTemplateName(resourceName, request), p, handler); | ||
| } | ||
|
|
||
| private String genTemplateName(final String resourceName, final HttpServerRequest request) | ||
| { | ||
| if (resourceName != null && !resourceName.trim().isEmpty()) | ||
| public void processTemplateWithLambdas(ProcessTemplateContext.Builder context, Handler<Writer> handler) { | ||
| context.lambdas(getLambdasFromRequest(context.request())); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. En passant par
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oui c'et vrai |
||
| this.templateProcessor.processTemplate(context.build(), handler); | ||
| } | ||
|
|
||
| public void processTemplateWithLambdas(ProcessTemplateContext.Builder context, String resourceName, Handler<Writer> handler) { | ||
| context.lambdas(getLambdasFromRequest(context.request())); | ||
| context.templateString(genTemplateName(resourceName, context.request())); | ||
| this.templateProcessor.processTemplate(context.build(), handler); | ||
| } | ||
|
|
||
| private String genTemplateName(final String resourceName, final HttpServerRequest request) { | ||
| if (!StringUtils.isEmpty(resourceName)) { | ||
| return resourceName; | ||
| else | ||
| { | ||
| String template = request.path().substring(pathPrefix.length()); | ||
| if (template.trim().isEmpty()) { | ||
| template = pathPrefix.substring(1); | ||
| } | ||
| return template + ".html"; | ||
| } | ||
| String template = request.path().substring(pathPrefix.length()); | ||
| if (template.trim().isEmpty()) { | ||
| template = pathPrefix.substring(1); | ||
| } | ||
| return template + ".html"; | ||
| } | ||
|
|
||
| public static void ok(HttpServerRequest request) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.