Skip to content

Commit

Permalink
Defers determining the current language.
Browse files Browse the repository at this point in the history
Some requests do not need the language at all.
Also, parsing the Accept-Language header or also
checking for a user in the session is quite "expensive".

Therefore we defer this call until really needed.
  • Loading branch information
andyHa committed Oct 4, 2018
1 parent 1fd1d4d commit fcabf17
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 51 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
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
38 changes: 34 additions & 4 deletions src/main/java/sirius/web/security/UserContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
* Used to access the current user and scope.
Expand Down Expand Up @@ -210,12 +211,39 @@ private void bindUserToRequest(WebContext ctx) {
UserManager manager = getUserManager();
UserInfo user = manager.bindToRequest(ctx);
setCurrentUser(user);
CallContext.getCurrent().setLang(user.getLang());
} else {
setCurrentUser(UserInfo.NOBODY);
}
}

/**
* Bind a user from the session if available.
* <p>
* If no user is available (currently logged in) nothing will happen. User {@link #getUser()}
* to fully bind a user and attempt a login.
*
* @param ctx the current web context to bind against
* @return the user which was found in the session or an empty optional if none is present
*/
public Optional<UserInfo> bindUserIfPresent(WebContext ctx) {
if (ctx == null || !ctx.isValid()) {
return Optional.empty();
}

if (currentUser != null) {
return Optional.of(currentUser);
}

UserManager manager = getUserManager();
UserInfo user = manager.findUserForRequest(ctx);
if (user.isLoggedIn()) {
setCurrentUser(user);
return Optional.of(user);
}

return Optional.empty();
}

/**
* Installs the given scope as current scope.
* <p>
Expand Down Expand Up @@ -243,8 +271,10 @@ public void setCurrentScope(ScopeInfo scope) {
*/
public void setCurrentUser(@Nullable UserInfo user) {
this.currentUser = user == null ? UserInfo.NOBODY : user;
CallContext.getCurrent().addToMDC(MDC_USER_ID, () -> currentUser.getUserId());
CallContext.getCurrent().addToMDC(MDC_USER_NAME, () -> currentUser.getUserName());
CallContext call = CallContext.getCurrent();
call.addToMDC(MDC_USER_ID, () -> currentUser.getUserId());
call.addToMDC(MDC_USER_NAME, () -> currentUser.getUserName());
call.setLang(user.getLang());
}

/**
Expand Down Expand Up @@ -411,7 +441,7 @@ public String getFieldErrorMessage(String field) {
/**
* Returns the current user.
* <p>
* If no user is present yet, it tries to parse the current {@link WebContext} and retireve the user from the
* If no user is present yet, it tries to parse the current {@link WebContext} and retrieve the user from the
* session.
*
* @return the currently active user
Expand Down
17 changes: 3 additions & 14 deletions src/main/java/sirius/web/services/ServiceDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.netty.handler.codec.http.HttpResponseStatus;
import sirius.kernel.async.CallContext;
import sirius.kernel.async.TaskContext;
import sirius.kernel.commons.CachingSupplier;
import sirius.kernel.commons.PriorityCollector;
import sirius.kernel.commons.Strings;
import sirius.kernel.commons.Tuple;
Expand Down Expand Up @@ -113,22 +114,10 @@ private void invokeService(WebContext ctx, ServiceCall call, StructuredService s
}
}

// Install language
CallContext.getCurrent().setLang(NLS.makeLang(ctx.getLang()));

// 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 service code...
UserInfo user = 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;
}

// ... and check permissions
CachingSupplier<UserInfo> userSupplier = new CachingSupplier<>(UserContext::getCurrentUser);
for (String p : Permissions.computePermissionsFromAnnotations(serv.getClass())) {
if (!user.hasPermission(p)) {
if (!userSupplier.get().hasPermission(p)) {
ctx.respondWith().error(HttpResponseStatus.UNAUTHORIZED, "Missing permission: " + p);
return;
}
Expand Down

0 comments on commit fcabf17

Please sign in to comment.