-
-
Notifications
You must be signed in to change notification settings - Fork 132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Add support for flash messages. #62
Conversation
Can flash messages be stored inside fieldls of sessions? See |
I don't see why flash messages need all this machinery. It seems they should be either session fields, or their own simple cookies. Do we need to ever realistically store flash messages on the server, given how ephemeral they are? If not, they can just be custom cookies. Is there a need for a database back end at all for flash messages? |
Thanks for your feedback. I've made some revisions to store messages in sessions instead. |
Thanks, it looks much neater now, too! I still want to ask about sessions vs. bare cookies. With sessions, flash messages will sometimes be stored in database (depending on the session back end). Do flash messages really need that? This has potential performance implications from committing the flash message data to storage. If we directly store flash messages in a new kind of cookie ( |
Also, cc @tmattio. |
Thanks for starting this @jsthomas! 🙂 Here are my two cents after reading the PR:
I'm also of the opinion that flash messages should be dead simple. They don't need to be signed or persisted, so they can be decoupled from sessions to keep the implementation simple. In fact, Phoenix (sorry if I'm always putting the same framework on the table, it's the one I'm most familiar with) just provides a few functions to work with Flash: put_flash(conn, key, message)
get_flash(conn)
clear_flash(conn) It's simiar to the API provided in this PR, so this is a good signal 👍 I personally also like APIs that offer helpers for the common levels. E.g: val get_flash_info : request -> string option promise |
If we do this, I suggest factoring out flash messages to a separate library. |
I spent some time researching how other frameworks store flash messages in an effort to better understand the sessions versus bare cookies issue. Rails stores flash messages as part of the session, which means a cookie session by default (more here and here) but can also be a relational database. In Django, flash messages can be stored either in cookies or in the session, and the session backend can be a relational database (more here and here). Django actually goes a bit further, and has a "fallback" storage option where flash messages will be stored in a cookie if they fit and the (DB or memory) session otherwise. Flask appears to be similar (see here and here). The Flask docs have an explicit warning that flash messages that are too big may fail silently. So there is a precedent for using session storage and/or a database to store this data, with cookie-based storage a consistent default. The downside to a cookie-only approach is that it limits flash messages to 4096 bytes. I struggled to think of a realistic use case where a flash message (or set of messages) needs to be that big. Maybe putting a stack trace or other instrumentation into a I haven't thoroughly researched legal or security reasons that one might prefer non-cookie based flash message storage. The wikipedia article on secure cookies asserts, "even with Overall I lean toward the bare-cookie approach because it's simple, covers most use cases, and separates concerns (no overlap with sessions). |
I agree with this. Depending on the API, it might be easy, upon future request, to offer session storage as an option. However, let's implement in cookies only to get started. |
src/dream.mli
Outdated
|
||
val clear_flash : level -> request -> unit | ||
val put_flash : level -> string -> request -> unit | ||
val get_flash : level -> request -> string option |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this return a string list
? Should it be possible to store multiple messages of the same level? Should get_flash
return all messages of the same level or higher?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this return a string list? Should it be possible to store multiple messages of the same level?
Django allows you to store multiple messages of the same level; Phoenix appears to just let you store one (support for multiple messages was added then removed). Looking in the Phoenix issue linked earlier, one of the maintainers wrote:
Going through my Ruby web apps, I almost always have a single flash message per key. The caveat of multiple is it makes the single case more verbose.
This, and the fact that multi-message support was added and then later removed makes me think the single message approach is better. But I'm happy to revise if you'd prefer the other approach.
Should get_flash return all messages of the same level or higher?
I'm speculating, but I don't think most web developers treat flash messages' levels as totally ordered; they're more like labels for ensuring a particular message will be shown with relevant styling (green for info, red for error, etc.). It might have been better to choose some other word than "level", like "key" or "label".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This, and the fact that multi-message support was added and then later removed makes me think the single message approach is better. But I'm happy to revise if you'd prefer the other approach.
The comments since phoenixframework/phoenix#304 (comment) are about adding multiple message support, and listing workarounds.
It seems worrying that flash messages can be lost — at least for me personally, it would give me anxiety. In more "technical" terms, it makes usage of flash message non-composable, since you cannot predict, purely locally, if a message you are adding will actually be displayed, despite there not being a clear_flash
.
Out of the frameworks I checked/confirmed so far, Phoenix and Laravel don't support multiple messages. Django, Flask, Express, and Rails do, and expect you to iterate over the messages. It seems that the most used frameworks (Rails, Express, Django) use the multiple-messages style.
I personally don't see how code can be "correct" and not iterate over messages, given that there are already, at the very least, multiple levels, even if with only one message each. Wouldn't you just define a sub-template for doing this iteration, and base styles on each message's "level," which you get as an "output" of get_flash
rather than pass as an "input?"
Is there a reason to ever hold on to some flash messages for display in response to a later request? Do you want to e.g. choose only the info messages, and not display errors?
I think the base flash functionality of Dream should be "correct" and worry-free, and we can build a single-message getter on top of it later, if it is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also want to note that it is pretty easy for users to List.filter
flash messages, or take the head, and it is easy to define the right helper based on a base functionality that doesn't ever lose messages. The converse is not true — it's not (easily) possible to define a multi-message behavior on top of a mutable, overwriting, single-message behavior, unless you wrap everything (not just the getter locally) so as to flatten lists into some kind of format inside strings, and parse them when retrieving.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have experience with the Phoenix style of flash messages, could you explain the advantage of
val get_flash : category -> request -> message (option or list)
over
val get_flash : request -> (category * message) list
What benefit does Phoenix/its users gain from choosing a single category, and passing it as argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading this over in the last few days, I now think there are more advantages to the multi-message approach. I thought this point against the single-message approach was especially persuasive:
it makes usage of flash message non-composable, since you cannot predict, purely locally, if a message you are adding will actually be displayed
I've revised to allow for multiple messages of the same category, and done away with fixed message categories.
</body> | ||
</html> | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is mixing Tyxml (in results_page
) and templates (in input_form
) like this idiomatic? I couldn't figure out how to implement results_page
using templates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll edit this example to not use TyXML after the merge, and ping.
Lwt.return( | ||
let entries = List.rev !outbox in | ||
let content = List.fold_right (fun (x,y) a -> `String x :: `String y :: a) entries [] in | ||
let value = `List content |> Yojson.Basic.to_string in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if using Yojson to serialize is the right approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a right approach.
Thanks for the PR and research! Also thanks to @tmattio for the review. |
Thanks for reviewing, I learned a lot! |
This work-in-progress PR adds support for flash messages to the session middleware to address issue #43. It adds these new items:
The function
put_flash
takes a category and message as arguments; each category/message pair is stored in a list inside a cookie. This means flash messages are limited to 4096 bytes. The stored pairs can be retrieved on the next request (and only the next request) usingget_flash
. Message categories are arbitrary strings, though one could add helper functions for common categories likeinfo
,error
, etc.A new example,
b-flash-message
illustrates how to use the two functions.Not yet addressed: