diff --git a/src/main/java/org/takes/facets/flash/RsFlash.java b/src/main/java/org/takes/facets/flash/RsFlash.java index 1b819b2f4..e438bd3bf 100644 --- a/src/main/java/org/takes/facets/flash/RsFlash.java +++ b/src/main/java/org/takes/facets/flash/RsFlash.java @@ -33,6 +33,48 @@ /** * Forwarding response. * + *
This class helps you to automate flash message mechanism, by + * adding flash messages to your responses, for example: + * + *
public final class TkDiscussion implements Take { + * @Override + * public Response act() { + * // save the post to the database + * return new RsFlash( + * new RsForward(), + * "thanks for the post" + * ); + * } + * }+ * + *
This decorator will add the + * required "Set-Cookie" header to the response. This is all it is doing. + * The response is added to the cookie in URL-encoded format, together + * with the logging level. Flash messages could be of different severity, + * we're using Java logging levels for that, for example: + * + *
public final class TkDiscussion implements Take { + * @Override + * public Response act() { + * if (failed) { + * throw new RsFlash( + * new RsForward(), + * "can't save your post, sorry", + * java.util.logging.Level.SEVERE + * ); + * } + * } + * }+ * + *
This is how the HTTP response will look like (simplified): + * + *
HTTP/1.1 303 See Other + * Set-Cookie: RsFlash=can%27t%20save%20your%20post%2C%20sorry/SEVERE+ * + *
Here, the name of the cookie is {@code RsFlash}. You can change this + * default name using a constructor of {@link org.takes.facets.flash.RsFlash}, + * but it's not recommended. It's better to use the default name. + * *
The class is immutable and thread-safe. * * @author Yegor Bugayenko (yegor@teamed.io) diff --git a/src/main/java/org/takes/facets/flash/TsFlash.java b/src/main/java/org/takes/facets/flash/TsFlash.java index 4beb14c89..183f13bb7 100644 --- a/src/main/java/org/takes/facets/flash/TsFlash.java +++ b/src/main/java/org/takes/facets/flash/TsFlash.java @@ -31,19 +31,35 @@ import org.takes.Take; import org.takes.Takes; import org.takes.rq.RqCookies; -import org.takes.rq.RqWithHeader; import org.takes.rs.RsWithCookie; /** * Takes that understands Flash cookie and converts it into a HTTP header. * + *
This decorator helps your "takes" to automate flash messages and + * destroy cookies on their way back, + * from the browser to the server. This is what a browser will send back: + * + *
GET / HTTP/1.1 + * Host: www.example.com + * Cookie: RsFlash=can%27t%20save%20your%20post%2C%20sorry/SEVERE+ * + *
This decorator adds "Set-Cookie" with an empty + * value to the response. That's all it's doing. All you need to do + * is to decorate your existing "takes", for example: + * + *
new FtBasic( + * new TsFlash(TsFork(new FkRegex("/", "hello, world!"))), 8080 + * ).start(Exit.NEVER); + * }+ * *
The class is immutable and thread-safe.
*
* @author Yegor Bugayenko (yegor@teamed.io)
* @version $Id$
* @since 0.1
*/
-@EqualsAndHashCode(of = { "origin", "cookie", "header" })
+@EqualsAndHashCode(of = { "origin", "cookie" })
public final class TsFlash implements Takes {
/**
@@ -56,29 +72,22 @@ public final class TsFlash implements Takes {
*/
private final transient String cookie;
- /**
- * Header name.
- */
- private final transient String header;
-
/**
* Ctor.
* @param takes Original takes
*/
public TsFlash(final Takes takes) {
- this(takes, RsFlash.class.getSimpleName(), "X-Takes-Flash");
+ this(takes, RsFlash.class.getSimpleName());
}
/**
* Ctor.
* @param takes Original takes
* @param name Cookie name
- * @param hdr Header name
*/
- public TsFlash(final Takes takes, final String name, final String hdr) {
+ public TsFlash(final Takes takes, final String name) {
this.origin = takes;
this.cookie = name;
- this.header = hdr;
}
@Override
@@ -91,11 +100,7 @@ public Take route(final Request request) throws IOException {
@Override
public Response act() throws IOException {
return new RsWithCookie(
- TsFlash.this.origin.route(
- new RqWithHeader(
- request, TsFlash.this.header, values.next()
- )
- ).act(),
+ TsFlash.this.origin.route(request).act(),
TsFlash.this.cookie,
""
);
diff --git a/src/main/java/org/takes/facets/flash/XeFlash.java b/src/main/java/org/takes/facets/flash/XeFlash.java
index cc9861887..2b835816f 100644
--- a/src/main/java/org/takes/facets/flash/XeFlash.java
+++ b/src/main/java/org/takes/facets/flash/XeFlash.java
@@ -28,7 +28,7 @@
import java.util.logging.Level;
import lombok.EqualsAndHashCode;
import org.takes.Request;
-import org.takes.rq.RqHeaders;
+import org.takes.rq.RqCookies;
import org.takes.rs.xe.XeSource;
import org.xembly.Directive;
import org.xembly.Directives;
@@ -42,7 +42,7 @@
* @version $Id$
* @since 0.1
*/
-@EqualsAndHashCode(of = { "req", "header" })
+@EqualsAndHashCode(of = { "req", "cookie" })
public final class XeFlash implements XeSource {
/**
@@ -51,35 +51,35 @@ public final class XeFlash implements XeSource {
private final transient Request req;
/**
- * Header name.
+ * Cookie name.
*/
- private final transient String header;
+ private final transient String cookie;
/**
* Ctor.
* @param request Request
*/
public XeFlash(final Request request) {
- this(request, "X-Takes-Flash");
+ this(request, RsFlash.class.getSimpleName());
}
/**
* Ctor.
* @param request Request
- * @param hdr Header name
+ * @param name Cookie name
*/
- public XeFlash(final Request request, final String hdr) {
+ public XeFlash(final Request request, final String name) {
this.req = request;
- this.header = hdr;
+ this.cookie = name;
}
@Override
public Iterable Flash messages is a useful technique that allows us to send short
+ * texts from one web page to another without any persistence on the server.
+ * Here is how it works. A web page performs some operation, for example
+ * making a post to a discussion thread. Then, the page returns a HTTP response
+ * with a redirection status 303. The response contains a "Set-Cookie" HTTP
+ * header with a flash message "thanks for the post".
+ *
+ * The browser preserves the cookie
+ * and redirects to the page with the discussion thread. The browser makes
+ * a new HTTP request, to render the content of the discussion thread. This
+ * HTTP GET request contains a "Cookie" HTTP header with that message
+ * "thanks for the post". The server adds this message to the HTML page,
+ * informing the user about the action just completed, and returns a HTTP
+ * response. This response contains a "Set-Cookie" HTTP header with an empty
+ * value, which is a signal for the browser to remove the cookie.
+ *
+ * The browser won't send the flash message twice, because it is deleted
+ * after the first time it was rendered in HTML by the server. The deletion
+ * is controlled by the server, when it returns an HTTP response with
+ * "Set-Cookie" header with an empty value.
+ *
+ * Classes in this package helps to automate this mechanism. First,
+ * you add flash messages to your responses using
+ * {@link org.takes.facets.flash.RsFlash}:
+ *
+ * This {@link org.takes.facets.flash.RsFlash} decorator will add the
+ * required "Set-Cookie" header to the response. This is all it is doing.
+ * The response is added to the cookie in URL-encoded format, together
+ * with the logging level. Flash messages could be of different severity,
+ * we're using Java logging levels for that, for example:
+ *
+ * This is how the HTTP response will look like (simplified):
+ *
+ * Here, the name of the cookie is {@code RsFlash}. You can change this
+ * default name using a constructor of {@link org.takes.facets.flash.RsFlash},
+ * but it's not recommended. It's better to use the default name.
+ *
+ * The next step is to understand that cookie on its way back,
+ * from the browser to the server. This is what a browser will send back:
+ *
+ * There is a class {@link org.takes.facets.flash.TsFlash}, that
+ * decorates your existing "takes" and adds "Set-Cookie" with an empty
+ * value to the response. That's all it's doing. All you need to do
+ * is to decorate your existing "takes", for example:
+ *
+ * The last step is to fetch that cookie from the request and add
+ * to the HTML page. You can use {@link org.takes.rq.RqCookies} for that
+ * (it's a pseudo-code, don't build HTML like this!):
+ *
+ * If you're using Xembly to build XML output, you can use
+ * {@link org.takes.facets.flash.XeFlash} for fetching flash messages
+ * from cookies and adding them to XML:
+ *
+ * Don't forget that the cookie you receive is not just a flash message,
+ * but also other parameters, URL-encoded and separated by "slash".public final class TkDiscussion implements Take {
+ * @Override
+ * public Response act() {
+ * // save the post to the database
+ * return new RsFlash(
+ * new RsForward(),
+ * "thanks for the post"
+ * );
+ * }
+ * }
+ *
+ * public final class TkDiscussion implements Take {
+ * @Override
+ * public Response act() {
+ * if (failed) {
+ * throw new RsFlash(
+ * new RsForward(),
+ * "can't save your post, sorry",
+ * java.util.logging.Level.SEVERE
+ * );
+ * }
+ * }
+ * }
+ *
+ * HTTP/1.1 303 See Other
+ * Set-Cookie: RsFlash=can%27t%20save%20your%20post%2C%20sorry/SEVERE
+ *
+ * GET / HTTP/1.1
+ * Host: www.example.com
+ * Cookie: RsFlash=can%27t%20save%20your%20post%2C%20sorry/SEVERE
+ *
+ * new FtBasic(
+ * new TsFlash(TsFork(new FkRegex("/", "hello, world!"))), 8080
+ * ).start(Exit.NEVER);
+ * }
+ *
+ * public final class TkDiscussion implements Take {
+ * private final Request req;
+ * @Override
+ * public Response act() {
+ * String html = "this is our discussion thread...";
+ * final Iterator<String> cookies =
+ * new RqCookies(this.req).cookie("RsFlash").iterator();
+ * if (cookies.hasNext()) {
+ * html = cookies.next() + html;
+ * }
+ * return new RsHTML(html);
+ * }
+ * }
+ * }
+ *
+ * public final class TkDiscussion implements Take {
+ * private final Request req;
+ * @Override
+ * public Response act() {
+ * return new RsXembly(
+ * new XeAppend(
+ * "page",
+ * new XeFlash(this.req),
+ * // your other Xembly sources
+ * )
+ * );
+ * }
+ * }
+ *
+ *