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 toXembly() throws IOException { - final Iterator headers = - new RqHeaders(this.req).header(this.header).iterator(); + final Iterator cookies = + new RqCookies(this.req).cookie(this.cookie).iterator(); final Directives dirs = new Directives(); - if (headers.hasNext()) { - final String value = headers.next(); + if (cookies.hasNext()) { + final String value = cookies.next(); dirs.add("flash") .add("message").set(value).up() .add("level").set(Level.INFO.toString()); diff --git a/src/main/java/org/takes/facets/flash/package-info.java b/src/main/java/org/takes/facets/flash/package-info.java index 41bcf6006..7f5145b99 100644 --- a/src/main/java/org/takes/facets/flash/package-info.java +++ b/src/main/java/org/takes/facets/flash/package-info.java @@ -23,7 +23,128 @@ */ /** - * Flash. + * Flash messages. + * + *

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}: + * + *

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 {@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: + * + *

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 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: + * + *

 GET / HTTP/1.1
+ * Host: www.example.com
+ * Cookie: RsFlash=can%27t%20save%20your%20post%2C%20sorry/SEVERE
+ * + *

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: + * + *

 new FtBasic(
+ *   new TsFlash(TsFork(new FkRegex("/", "hello, world!"))), 8080
+ *  ).start(Exit.NEVER);
+ * }
+ * + *

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!): + * + *

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);
+ *     }
+ *   }
+ * }
+ * + *

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: + * + *

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
+ *       )
+ *     );
+ *   }
+ * }
+ * + *

Don't forget that the cookie you receive is not just a flash message, + * but also other parameters, URL-encoded and separated by "slash".

* * @author Yegor Bugayenko (yegor@teamed.io) * @version $Id$ diff --git a/src/test/java/org/takes/facets/flash/XeFlashTest.java b/src/test/java/org/takes/facets/flash/XeFlashTest.java index e3f4ca083..5ac8ff566 100644 --- a/src/test/java/org/takes/facets/flash/XeFlashTest.java +++ b/src/test/java/org/takes/facets/flash/XeFlashTest.java @@ -47,7 +47,6 @@ public final class XeFlashTest { */ @Test public void generatesFlashData() throws IOException { - final String header = "x"; MatcherAssert.assertThat( IOUtils.toString( new RsXembly( @@ -55,9 +54,8 @@ public void generatesFlashData() throws IOException { "root", new XeFlash( new RqWithHeader( - new RqFake(), header, "how are you" - ), - header + new RqFake(), "Cookie", "RsFlash=how are you" + ) ) ) ).body()