§Java Http.Context
changes
play.mvc.Http.Context
is a crucial part of Java HTTP & MVC APIs, but it is not a good abstraction of how these APIs should work. It either has some concepts that could be better modeled or implementation details that are complex to test and to reason about in a multi-threading framework like Play. For example, Http.Context
uses a thread local to capture and access the current request, but it gives the impression that the current request can be accessed from any place, which is not currently true if you are using Actors or a custom thread pool.
Regarding the API modeling, there are some duplicated concepts (like play.mvc.Result
vs play.mvc.Http.Response
) and some methods look out of the place (for example play.mvc.Http.Context.id
instead of using play.mvc.Http.RequestHeader.id
). Given that, multiple changes were made to Http.Context
and the idea is to move away from it. We are then providing new APIs that are simpler to test, to reason about and to maintain in the future.
Since play.mvc.Http.Context
is a central part of the existing APIs, deprecating it had an impact on multiple places that were depending on it, take for example play.mvc.Controller
. This page documents these changes and how to migrate, but you can see the deprecated Javadocs for each methods too.
§Http.Context.current()
and Http.Context.request()
deprecated
That means other methods that depend directly on these two were also deprecated:
play.mvc.Controller.ctx()
play.mvc.Controller.request()
Before Play 2.7, when using Play with Java, the only way to access the Http.Request
was Http.Context.current()
which was used internally by Controller.request()
method. The problem with Http.Context.current()
is that it is implemented using a thread local, which is harder to test, to keep in sync with changes made at other places and makes it harder to access the request in other threads.
With Play 2.7 you can now access the current request by just adding it as a param to your routes and actions.
For example, the routes files contain:
GET / controllers.HomeController.index(request: Request)
And the corresponding action method:
import play.mvc.*;
public class HomeController extends Controller {
public Result index(Http.Request request) {
return ok("Hello, your request path " + request.path());
}
}
Play will automatically detect a route param of type Request
(which is an import for play.mvc.Http.Request
) and will pass the actual request into the corresponding action method’s param.
Note: It is unlikely but possible that you have a custom
QueryStringBindable
orPathBindable
with the nameRequest
. If so, that one would now collide with Play detection of request params.
Therefore you should use the fully qualified name of yourRequest
type, for example.GET / controllers.HomeController.index(myRequest: com.mycompany.Request)
If you use Http.Context.current()
in other places besides controllers you have to pass the desired data via method parameters to these places now. Look at this example which checks if the current request’s remote address is on a blacklist:
§Before
import play.mvc.Http;
public class SecurityHelper {
public static boolean isBlacklisted() {
String remoteAddress = Http.Context.current().request().remoteAddress();
return blacklist.contains(remoteAddress);
}
}
Corresponding controller:
import play.mvc.*;
public class HomeController extends Controller {
public Result index() {
if (SecurityHelper.isBlacklisted()) {
return badRequest();
}
return ok("Hello, your request path " + request().path());
}
}
§After
public class SecurityHelper {
public static boolean isBlacklisted(String remoteAddress) {
return blacklist.contains(remoteAddress);
}
}
Corresponding controller:
import play.mvc.*;
public class HomeController extends Controller {
public Result index(Http.Request request) {
if (SecurityHelper.isBlacklisted(request.remoteAddress())) {
return badRequest();
}
return ok("Hello, your request path " + request.path());
}
}
§Security.Authenticated
changes
To secure action to prevent access without authentication you can use @Security.Authenticated
.
§Before
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Security;
public class Secured extends Security.Authenticator {
@Override
public String getUsername(Http.Context ctx) {
return ctx.session().get("id");
}
@Override
public Result onUnauthorized(Http.Context ctx) {
ctx.flash().put("danger", "You need to login before access the application.");
return redirect(controllers.routes.HomeController.login());
}
}
§After
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Security;
import java.util.Optional;
public class Secured extends Security.Authenticator {
@Override
public Optional getUsername(Http.Request req) {
return req.session().getOptional("id");
}
@Override
public Result onUnauthorized(Http.Request req) {
return redirect(controllers.routes.HomeController.login()).
flashing("danger", "You need to login before access the application.");
}
}
And the corresponding action method:
@Security.Authenticated(Secured.class)
public Result index(Http.Request request) {
return ok(views.html.index.render(request));
}
§Action.call(Context)
deprecated
If you are using action composition you have to update your actions to avoid Http.Context
.
§Before
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import java.util.concurrent.CompletionStage;
public class MyAction extends Action.Simple {
public CompletionStage<Result> call(Http.Context ctx) {
return delegate.call(ctx);
}
}
§After
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import java.util.concurrent.CompletionStage;
public class MyAction extends Action.Simple {
public CompletionStage<Result> call(Http.Request req) {
return delegate.call(req);
}
}
§Http.Context.response()
and Http.Response
class deprecated
That means other methods that depend directly on these were also deprecated:
play.mvc.Controller.response()
Http.Response
was deprecated with other accesses methods to it. It was mainly used to add headers and cookies, but these are already available in play.mvc.Result
and then the API got a little confused. For Play 2.7, you should migrate code like:
§Before
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Controller;
public class FooController extends Controller {
// This uses the deprecated response() APIs
public Result index() {
response().setHeader("Header", "Value");
response().setCookie(Http.Cookie.builder("Cookie", "cookie value").build());
response().discardCookie("CookieName");
return ok("Hello World");
}
}
§After
The code above should be written as:
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Controller;
public class FooController extends Controller {
// This uses the deprecated response() APIs
public Result index() {
return ok("Hello World")
.withHeader("Header", "value")
.withCookies(Http.Cookie.builder("Cookie", "cookie value").build())
.discardCookie("CookieName");
}
}
If you have action composition that depends on Http.Context.response
, you can also rewrite it like the code below:
§Before
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import java.util.concurrent.CompletionStage;
public class MyAction extends Action.Simple {
@Override
public CompletionStage<Result> call(Http.Context ctx) {
ctx.response().setHeader("Name", "Value");
return delegate.call(ctx);
}
}
§After
The code above should be written as:
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
import java.util.concurrent.CompletionStage;
public class MyAction extends Action.Simple {
@Override
public CompletionStage<Result> call(Http.Request req) {
return delegate.call(req)
.thenApply(result -> result.withHeader("Name", "Value"));
}
}
§Lang and Messages methods in Http.Context
deprecated
The following methods have been deprecated:
Http.Context.lang()
Http.Context.changeLang(Lang lang)
Http.Context.changeLang(String code)
Http.Context.clearLang()
Http.Context.setTransientLang(Lang lang)
Http.Context.setTransientLang(String code)
Http.Context.clearTransientLang()
Http.Context.messages()
That means other methods that depend directly on these were also deprecated:
play.mvc.Controller.lang()
play.mvc.Controller.changeLang(Lang lang)
play.mvc.Controller.changeLang(String code)
play.mvc.Controller.clearLang()
The new way of changing lang now is to have an instance of play.i18n.MessagesApi
injected and call corresponding play.mvc.Result
methods. For example:
§Before
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
import play.i18n.MessagesApi;
public class FooController extends Controller {
public Result action() {
changeLang(Lang.forCode("es"));
return Results.ok("Hello");
}
}
§After
import javax.inject.Inject;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
import play.i18n.MessagesApi;
public class FooController extends Controller {
private final MessagesApi messagesApi;
@Inject
public FooController(MessagesApi messagesApi) {
this.messagesApi = messagesApi;
}
public Result action() {
return Results.ok("Hello").withLang(Lang.forCode("es"), messagesApi);
}
}
If you are using changeLang
to change the Lang
used to render a template, you should now pass the Messages
itself as a parameter. This will make the template clearer and easier to read. For example, in an action method, you have to create a Messages
instance like:
§Before
import play.mvc.Result;
import play.mvc.Controller;
public class MyController extends Controller {
public Result action() {
changeLang(Lang.forCode("es"));
return ok(myview.render(messages));
}
}
§After
import javax.inject.Inject;
import play.i18n.Messages;
import play.i18n.MessagesApi;
import play.mvc.Result;
import play.mvc.Controller;
public class MyController extends Controller {
private final MessagesApi messagesApi;
@Inject
public MyController(MessagesApi messagesApi) {
this.messagesApi = messagesApi;
}
public Result action() {
Messages messages = this.messagesApi.preferred(Lang.forCode("es"));
return ok(myview.render(messages));
}
}
Or if you want to have a fallback to the languages of the request you can do that as well:
import javax.inject.Inject;
import play.i18n.Messages;
import play.i18n.MessagesApi;
import play.mvc.Result;
import play.mvc.Controller;
public class MyController extends Controller {
private final MessagesApi messagesApi;
@Inject
public MyController(MessagesApi messagesApi) {
this.messagesApi = messagesApi;
}
public Result action() {
Lang lang = Lang.forCode("es");
// Get a Message instance based on the spanish locale, however if that isn't available
// try to choose the best fitting language based on the current request
Messages messages = this.messagesApi.preferred(request.withTransientLang(lang));
return ok(myview.render(messages));
}
}
Note: To not repeat that code again and again inside each action method you could e.g. create the
Messages
instance in an action of the action composition chain and save that instance in a request Attribute so you can access it later.
Now the template:
@()(implicit messages: play.i18n.Messages)
@{messages.at("hello")}
Note: Declaring
messages
asimplicit
will make it available to sub-views that implicitly require aMessagesProvider
without the need to pass them one.
And the same applies to clearLang
:
§Before
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
import play.i18n.MessagesApi;
public class FooController extends Controller {
public Result action() {
clearLang();
return Results.ok("Hello");
}
}
§After
import javax.inject.Inject;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
import play.i18n.MessagesApi;
public class FooController extends Controller {
private final MessagesApi messagesApi;
@Inject
public FooController(MessagesApi messagesApi) {
this.messagesApi = messagesApi;
}
public Result action() {
return Results.ok("Hello").withoutLang(messagesApi);
}
}
§Http.Context.session()
deprecated
That means other methods that depend directly on it were also deprecated:
play.mvc.Controller.session()
play.mvc.Controller.session(String key, String value)
play.mvc.Controller.session(String key)
The new way to retrieve the session of a request is to call the session()
method of an Http.Request
instance. The new way to manipulate the session is to call corresponding play.mvc.Result
methods. For example:
§Before
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
public class FooController extends Controller {
public Result info() {
String user = session("current_user");
return Results.ok("Hello " + user);
}
public Result login() {
session("current_user", "[email protected]");
return Results.ok("Hello");
}
public Result logout() {
session().remove("current_user");
return Results.ok("Hello");
}
public Result clear() {
session().clear();
return Results.ok("Hello");
}
}
§After
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
import java.util.Optional;
public class FooController extends Controller {
public Result info(Http.Request request) {
// Get the current user or then fallback to guest
String user = request.session().getOptional("current_user").orElse("guest");
return Results.ok("Hello " + user);
}
public Result login(Http.Request request) {
return Results.ok("Hello")
.addingToSession(request, "current_user", "[email protected]");
}
public Result logout(Http.Request request) {
return Results.ok("Hello")
.removingFromSession(request, "current_user");
}
public Result clear() {
return Results.ok("Hello")
.withNewSession();
}
}
§Http.Context.flash()
deprecated
That means other methods that depend directly on it were also deprecated:
play.mvc.Controller.flash()
play.mvc.Controller.flash(String key, String value)
play.mvc.Controller.flash(String key)
The new way to retrieve the flash of a request is to call the flash()
method of a Http.Request
instance. The new way to manipulate the flash is to call corresponding play.mvc.Result
methods. For example:
§Before
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
public class FooController extends Controller {
public Result info() {
String message = flash("message");
return Results.ok("Message: " + message);
}
public Result login() {
flash("message", "Login successful");
return Results.redirect("/dashboard");
}
public Result logout() {
flash().remove("message");
return Results.redirect("/");
}
public Result clear() {
flash().clear();
return Results.redirect("/");
}
}
§After
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Controller;
public class FooController extends Controller {
public Result info(Http.Request request) {
String message = request.flash().getOptional("message").orElse("The default message");
return Results.ok("Message: " + message);
}
public Result login() {
return Results.redirect("/dashboard")
.flashing("message", "Login successful");
}
public Result logout() {
return Results.redirect("/")
.removingFromFlash("message");
}
public Result clear() {
return Results.redirect("/")
.withNewFlash();
}
}
§Template helper methods deprecated
Inside templates, Play offered you various helper methods which rely on Http.Context
internally. These methods are deprecated starting with Play 2.7. Instead, you have to explicitly pass the desired object to your templates now.
§Before
@()
@ctx()
@request()
@response()
@flash()
@session()
@lang()
@messages()
@Messages("some_msg_key")
§After
@(Http.Request request, Lang lang, Messages messages)
@request
@request.flash()
@request.session()
@lang
@messages
@messages("some_msg_key")
There is no direct replacement for ctx()
and response()
.
§Some template tags need an implicit Request
, Messages
or Lang
instance
Some template tags need to access a Http.Request
, Messages
or Lang
instance in order to work correctly. Until now these tags just made use of Http.Context.current()
to retrieve such instances.
Because Http.Context
is deprecated however, such instances should now be passed as implicit
parameters to templates which make use of such tags. By marking the parameter as implicit
you don’t always have to pass it on to the tag which actually needs it, but the tag can retrieve it from the implicit scope automatically.
Note: To better understand how implicit parameters works, see implicit parameter and where does Scala look for implicits sections of Scala FAQ.
Following tags need an implicit Request
instance to be present:
@(arg1, arg2,...)(implicit request: Http.Request)
@*These tags will automatically use the implicit request passed to this template:*@
@helper.jsloader
@helper.script
@helper.style
@helper.javascriptRouter
@CSRF
@CSRF.formField
@CSRF.getToken
@defaultpages.devError
@defaultpages.devNotFound
@defaultpages.error
@defaultpages.badRequest
@defaultpages.notFound
@defaultpages.todo
@defaultpages.unauthorized
So, if you have a view that use some of the tags above, for example if you have a file app/views/names.scala.html
like below:
@(names: List[String])(implicit request: Http.Request)
<html>
<head>
<!-- `scripts` tags requires a request to generate a CSP nonce -->
@script(args = 'type -> "text/javascript") {
alert("Just a single inline script");
}
</head>
<body>
...
</body>
</html>
Your controller will need to pass the request as a parameter to the render
method:
import java.util.List;
import java.util.ArrayList;
import javax.inject.Inject;
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Controller;
public class SomeController extends Controller {
public Result action(Http.Request request) {
List<String> names = new ArrayList<>("Jane", "James", "Rich");
return ok(views.html.names.render(names, request));
}
}
There are also the helper tags that need an implicit Messages
instance to be present:
@(arg1, arg2,...)(implicit messages: play.i18n.Messages)
These tags will automatically use the implicit messages passed to this template:
@helper.inputText
@helper.inputDate
@helper.inputCheckboxGroup
@helper.inputFile
@helper.inputRadioGroup
@helper.inputPassword
@helper.textarea
@helper.input
@helper.select
@helper.checkbox
So, if you have a view that use some of the tags above, for example if you have a file app/views/userForm.scala.html
like below:
@(userForm: Form[User])(implicit messages: play.i18n.Messages)
<html>
<head>
<title>User form page</title>
</head>
<body>
@helper.form(action = routes.UsersController.save) {
@helper.inputText(userForm("name"))
@helper.inputText(userForm("email"))
...
})
</body>
</html>
Your controller will then be like:
import javax.inject.Inject;
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Controller;
import play.i18n.Messages;
import play.i18n.MessagesApi;
import play.data.FormFactory;
public class SomeController extends Controller {
private final FormFactory formFactory;
private final MessagesApi messagesApi;
@Inject
public SomeController(FormFactory formFactory, MessagesApi messagesApi) {
this.formFactory = formFactory;
this.messagesApi = messagesApi;
}
public Result action(Http.Request request) {
Form<User> userForm = formFactory.form(User.class);
// Messages instance that will be passed to render the view and
// inside the view will be passed implicitly to helper tags.
Messages messages = messagesApi.preferred(request);
return ok(views.html.userForm.render(userForm, messages));
}
}
Note: some of these features were previously provided by
PlayMagicForJava
and were heavily depending onHttp.Context.current()
. That is why you will see warnings like:method implicitXXX in object PlayMagicForJava is deprecated (since 2.7.0): See https://www.playframework.com/documentation/latest/JavaHttpContextMigration27
Passing the parameters to your view will make it more clear what is happening and where your view is depending on other data.
Play itself does not provide tags that need a Lang
instance to be present, third-party modules however may do:
@(arg1, arg2,...)(implicit lang: play.i18n.Lang)
Third-party tags will automatically use the implicit messages passed to this template. You can pass an implicit instance of Lang
to your view just like the examples for Http.Request
and Messages
above.
§Changes in Java Forms related to Http.Context
When retrieving the Field
of a Form
(e.g. via myform.field("username")
or just myform("username")
inside templates) the language of the current Http.Context
was used to format the value of the field. Starting with Play 2.7 however this isn’t the case anymore. Instead you can now explicitly set the language the form should use when retrieving a field. To make things simple and to not force you to set the language for every form explicitly, Play sets it during binding already:
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.Controller;
import play.data.Form;
import play.data.FormFactory;
public class MyController extends Controller {
private final FormFactory formFactory;
@Inject
public MyController(FormFactory formFactory) {
this.formFactory = formFactory;
}
public Result action(Http.Request request) {
// In this example, the language of the form will be set
// to the preferred language of the request.
Form<User> form = formFactory.form(User.class).bindFromRequest(request);
return ok(views.form.render(form));
}
}
You can also change the language of an existing form if you need:
import play.mvc.Result;
import play.mvc.Controller;
import play.i18n.Lang;
import play.data.Form;
import play.data.FormFactory;
public class MyController extends Controller {
private final FormFactory formFactory;
@Inject
public MyController(FormFactory formFactory) {
this.formFactory = formFactory;
}
public Result action(Http.Request request) {
// There is first the lang from request
Form<User> form = formFactory.form(User.class).bindFromRequest(request);
// Let's change the language to `es`.
Lang lang = Lang.forCode("es");
Form<User> formWithNewLang = form.withLang(lang);
return ok(views.form.render(formWithNewLang));
}
}
Note: changing the language of the the current
Http.Context
(e.g. viaHttp.Context.current().changeLang(...)
orHttp.Context.current().setTransientLang(...)
) does not have an effect on the language used to retrieve the field value of a form anymore - as explained, useform.withLang(...)
instead.
§Http.Context
Request tags removed from args
Request tags, which have been deprecated in Play 2.6, have finally been removed in Play 2.7. Therefore the args
map of an Http.Context
instance no longer contains these removed request tags as well. Instead you can use the request.attrs()
method now, which provides you the same request attributes.
§CSRF tokens removed from args
The @AddCSRFToken
action annotation added two entries named CSRF_TOKEN
and CSRF_TOKEN_NAME
to the args
map of an Http.Context
instance. These entries have been removed. Use the new correct way to get the token.
§RoutingDSL changes
Until Play 2.6, when using Java routing DSL, there is no other way to access the current request
besides Http.Context.current()
. Now the DSL has new methods where a request will be passed to the block.
§Before
import play.mvc.Http;
import play.routing.Router;
import play.routing.RoutingDsl;
import javax.inject.Inject;
import static play.mvc.Results.ok;
public class MyRouter {
private final RoutingDsl routingDsl;
@Inject
public MyRouter(RoutingDsl routingDsl) {
this.routingDsl = routingDsl;
}
public Router router() {
return routingDsl
.GET("/hello/:to")
.routeTo(to -> {
Http.Request request = Http.Context.current().request();
return ok("Hello " + to + ". Here is the request path: " + request.path());
})
.build();
}
}
In the example above, we need to use Http.Context.current()
to access the request. From now on, you can instead write the code like below:
§After
import play.routing.Router;
import play.routing.RoutingDsl;
import javax.inject.Inject;
import static play.mvc.Results.ok;
public class MyRouter {
private final RoutingDsl routingDsl;
@Inject
public MyRouter(RoutingDsl routingDsl) {
this.routingDsl = routingDsl;
}
public Router router() {
return routingDsl
.GET("/hello/:to")
.routingTo((request, to) ->
ok("Hello " + to + ". Here is the request path: " + request.path())
)
.build();
}
}
An important aspect to note is that, in the new API, Http.Request
will always be the first parameter for the function blocks.
§Disabling the Http.Context
and JPA thread local
If you followed the above migration notes and changed all your code so it doesn’t make use of API’s that rely on Http.Context
(meaning you don’t get compiler warnings anymore) you can disable the Http.Context
thread local.
Just add the following line to your application.conf
file:
play.allowHttpContext = false
To also disable the play.db.jpa.JPAEntityManagerContext
thread local add:
play.jpa.allowJPAEntityManagerContext = false
Next: Play 2.6