Skip to content

Commit

Permalink
Merge pull request #503 from scireum/aha/CookieCache
Browse files Browse the repository at this point in the history
Aha/cookie cache
  • Loading branch information
andyHa authored Oct 4, 2018
2 parents 1e5b162 + fcabf17 commit 5b51fcf
Show file tree
Hide file tree
Showing 16 changed files with 167 additions and 211 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<description>Provides a modern and scalable web server as SIRIUS module</description>

<properties>
<sirius.kernel>12.0-rc24</sirius.kernel>
<sirius.kernel>12.0-rc25</sirius.kernel>
</properties>

<dependencies>
Expand Down
33 changes: 6 additions & 27 deletions src/main/java/sirius/web/controller/ControllerDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import sirius.kernel.async.Promise;
import sirius.kernel.async.TaskContext;
import sirius.kernel.async.Tasks;
import sirius.kernel.commons.CachingSupplier;
import sirius.kernel.commons.Callback;
import sirius.kernel.commons.Explain;
import sirius.kernel.commons.PriorityCollector;
Expand All @@ -22,7 +23,6 @@
import sirius.kernel.di.std.Register;
import sirius.kernel.health.Exceptions;
import sirius.kernel.health.Log;
import sirius.kernel.nls.NLS;
import sirius.web.ErrorCodeException;
import sirius.web.http.Firewall;
import sirius.web.http.InputStreamHandler;
Expand All @@ -31,7 +31,6 @@
import sirius.web.http.WebContext;
import sirius.web.http.WebDispatcher;
import sirius.web.security.UserContext;
import sirius.web.security.UserInfo;
import sirius.web.services.JSONStructuredOutput;

import java.lang.reflect.InvocationTargetException;
Expand Down Expand Up @@ -163,7 +162,10 @@ public boolean dispatch(WebContext ctx) throws Exception {

private void performRoute(WebContext ctx, Route route, List<Object> params) {
try {
setupContext(ctx, route);
TaskContext.get()
.setSystem(SYSTEM_MVC)
.setSubSystem(route.getController().getClass().getSimpleName())
.setJob(ctx.getRequestedURI());

// Intercept call...
for (Interceptor interceptor : interceptors) {
Expand All @@ -172,17 +174,8 @@ private void performRoute(WebContext ctx, Route route, List<Object> params) {
}
}

// Install user. This is forcefully called here to ensure that the ScopeDetetor
// and the user manager are guaranteed to be invoked one we enter the controller code...
UserInfo user = UserContext.getCurrentUser();
String missingPermission = route.checkAuth(new CachingSupplier<>(UserContext::getCurrentUser));

// If the underlying ScopeDetector made a redirect (for whatever reasons)
// the response will be committed and we can (must) safely return...
if (ctx.isResponseCommitted()) {
return;
}

String missingPermission = route.checkAuth(user);
if (missingPermission != null) {
handlePermissionError(ctx, route, missingPermission);
} else {
Expand All @@ -201,12 +194,6 @@ private void performRoute(WebContext ctx, Route route, List<Object> params) {
}

private void executeRoute(WebContext ctx, Route route, List<Object> params) throws Exception {
// If a user authenticated during this call...bind to session!
UserContext userCtx = UserContext.get();
if (userCtx.getUser().isLoggedIn()) {
userCtx.attachUserToSession();
}

if (route.isJSONCall()) {
executeJSONCall(ctx, route, params);
} else {
Expand Down Expand Up @@ -251,14 +238,6 @@ private void handlePermissionError(WebContext ctx, Route route, String missingPe
ctx.respondWith().error(HttpResponseStatus.UNAUTHORIZED);
}

private void setupContext(WebContext ctx, Route route) {
CallContext.getCurrent().setLang(NLS.makeLang(ctx.getLang()));
TaskContext.get()
.setSystem(SYSTEM_MVC)
.setSubSystem(route.getController().getClass().getSimpleName())
.setJob(ctx.getRequestedURI());
}

private void handleFailure(WebContext ctx, Route route, Throwable ex) {
try {
CallContext.getCurrent()
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/sirius/web/controller/Route.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -251,12 +252,12 @@ private void setAtPosition(List<Object> list, int position, Object value) {
* @return <tt>null</tt> if the user is authorized or otherwise the name of the permission which the user is
* missing.
*/
protected String checkAuth(UserInfo user) {
protected String checkAuth(Supplier<UserInfo> user) {
if (permissions == null) {
return null;
}
for (String p : permissions) {
if (!user.hasPermission(p)) {
if (!user.get().hasPermission(p)) {
return p;
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/sirius/web/http/Response.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import io.netty.handler.stream.ChunkedWriteHandler;
import sirius.kernel.Sirius;
import sirius.kernel.async.CallContext;
import sirius.kernel.async.ExecutionPoint;
import sirius.kernel.commons.MultiMap;
import sirius.kernel.commons.Strings;
import sirius.kernel.di.std.Part;
Expand Down Expand Up @@ -293,6 +294,25 @@ private void setupHeaders(DefaultHttpResponse response) {
response.headers()
.set("Strict-Transport-Security", "max-age=" + WebContext.hstsMaxAge + "; includeSubDomains");
}

// NEVER allow a Set-Cookie header within a cached request...
if (response.headers().contains(HttpHeaderNames.SET_COOKIE)) {
if (response.headers().contains(HttpHeaderNames.EXPIRES)) {
WebServer.LOG.WARN("A response with 'set-cookie' and 'expires' was created for URI: %s%n%s%n%s",
wc.getRequestedURI(),
wc,
ExecutionPoint.snapshot());
response.headers().remove(HttpHeaderNames.EXPIRES);
}
String cacheControl = response.headers().get(HttpHeaderNames.CACHE_CONTROL);
if (cacheControl != null && !cacheControl.startsWith(HttpHeaderValues.NO_CACHE.toString())) {
WebServer.LOG.WARN("A response with 'set-cookie' and 'cache-control' was created for URI: %s%n%s%n%s",
wc.getRequestedURI(),
wc,
ExecutionPoint.snapshot());
response.headers().set(HttpHeaderNames.CACHE_CONTROL, HttpHeaderValues.NO_CACHE + ", max-age=0");
}
}
}

private void updateStatistics(HttpResponseStatus status) {
Expand Down
52 changes: 17 additions & 35 deletions src/main/java/sirius/web/http/WebContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Provides access to a request received by the WebServer.
Expand All @@ -92,6 +94,9 @@ public class WebContext implements SubContext {
private static final String PROTOCOL_HTTPS = "https";
private static final String PROTOCOL_HTTP = "http";

private static final Pattern ACCEPT_LANGUAGE_PATTERN =
Pattern.compile(" *([a-z]{2})(-[a-z]{2})? *(;q=([0-9.]+) *)?");

/*
* Underlying channel to send and receive data
*/
Expand Down Expand Up @@ -193,11 +198,6 @@ public class WebContext implements SubContext {
*/
private volatile boolean sessionModified;

/*
* Contains the decoded language as two-letter code
*/
private String lang;

/*
* Specifies the microtiming key used for this request. If null, no microtiming will be recorded.
*/
Expand Down Expand Up @@ -1171,46 +1171,28 @@ private long determineSessionCookieTTL() {
/**
* Returns the accepted language of the client as two-letter language code.
*
* @return the two-letter code of the accepted language of the user agent. Returns the current language, if no
* supported language was submitted.
* @return the two-letter code of the accepted language of the user agent or <tt>null</tt> if no valid accept
* language was found
*/
@Nullable
public String getLang() {
if (lang == null) {
lang = parseAcceptLanguage();
}
return lang;
}

/*
* Parses the accept language header
*/
private String parseAcceptLanguage() {
double bestQ = 0;
String currentLang = CallContext.getCurrent().getLang();
String currentLang = null;
String header = getHeader(HttpHeaderNames.ACCEPT_LANGUAGE);
if (Strings.isEmpty(header)) {
return currentLang;
}
header = header.toLowerCase();
for (String str : header.split(",")) {
String[] arr = str.trim().replace("-", "_").split(";");

//Parse the q-value
double q = 1.0D;
for (String s : arr) {
s = s.trim();
if (s.startsWith("q=")) {
q = Double.parseDouble(s.substring(2).trim());
break;
for (String languageBlock : header.split(",")) {
Matcher m = ACCEPT_LANGUAGE_PATTERN.matcher(languageBlock);
if (m.matches()) {
double q = Value.of(m.group(4)).asDouble(1.0d);
String language = m.group(1);
if (q > bestQ && NLS.isSupportedLanguage(language)) {
bestQ = q;
currentLang = language;
}
}

//Parse the locale
String[] l = arr[0].split("_");
if (l.length > 0 && q > bestQ && NLS.isSupportedLanguage(l[0])) {
currentLang = l[0];
bestQ = q;
}
}

return currentLang;
Expand Down
16 changes: 13 additions & 3 deletions src/main/java/sirius/web/http/WebServerHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import sirius.kernel.health.Average;
import sirius.kernel.health.Exceptions;
import sirius.kernel.nls.NLS;
import sirius.web.security.UserContext;

import javax.net.ssl.SSLHandshakeException;
import java.io.File;
Expand Down Expand Up @@ -158,12 +159,21 @@ private WebContext setupContext(ChannelHandlerContext ctx, HttpRequest req) {
currentCall = CallContext.initialize();
currentCall.addToMDC("uri", req.uri());
WebContext wc = currentCall.get(WebContext.class);
if (ssl) {
wc.ssl = true;
}
wc.ssl = this.ssl;
wc.setCtx(ctx);
wc.setRequest(req);
currentCall.get(TaskContext.class).setSystem("HTTP").setJob(wc.getRequestedURI());

// Adds a deferred handler to determine the language to i18n stuff.
// If a user is present, the system will sooner or later detect it and set the appropriate
// language. If not, this handler will be evaluated, check for a user in the session or
// if everything else fails, parse the lang header.
currentCall.deferredSetLang(callContext -> {
if (!callContext.get(UserContext.class).bindUserIfPresent(wc).isPresent()) {
callContext.setLang(NLS.makeLang(wc.getLang()));
}
});

return wc;
}

Expand Down
Loading

0 comments on commit 5b51fcf

Please sign in to comment.