When a request for a path calls an action, the action has access to the request object through a request
method.
The
request
object is an instance of HTTP::Request.
Lucky also provides access to some helpful methods to determine the requests desired response format.
json?
- true if the client accepts “application/json”ajax?
- true if the X-Requested-With header is “XMLHttpRequest”html?
- true if the client accepts HTMLxml?
- true if the client accepts is “application/xml” or “application/xhtml+xml”plain?
- true if the client accepts is “text/plain”multipart?
- true if the client accepts starts with “multipart/form-data”You can use these methods to direct the request or return different responses.
class Users::Show < BrowserAction
get "/me" do
if json?
# The client wants json, so let's return some json
json(UserSerializer.new(current_user))
else
# Just render the page like normal
html Users::ShowPage, user: current_user
end
end
end
By default, generated Lucky apps set some helpful boundaries around what formats each action in your application can support. These can be overriden in specific actions with the accepted_formats
method.
For example, API actions only support JSON requests. Because only one format is specified as accepted, it is automatically set as the default format:
abstract class ApiAction < Lucky::Action
accepted_formats [:json]
end
In an API action, requests would be handled like this:
URL | Accept Header | Server Response |
---|---|---|
https://myapp.com/api/users | No specific request format | JSON (the default format) |
https://myapp.com/api/users | Accept: application/json |
JSON (the requested, accepted format) |
https://myapp.com/api/users | Accept: application/csv |
Response status 406 (not acceptable) |
Browser actions, on the other hand, can support either JSON or HTML, and treat non-specified formats as HTML by default:
abstract class BrowserAction < Lucky::Action
accepted_formats [:html, :json], default: :html
end
In a browser action, requests would be handled like this:
https://myapp.com/users
with no specific request format => Server responds with HTML (the default format)https://myapp.com/users
with Accept: application/json
=> Server responds with JSON (the requested, accepted format)https://myapp.com/users
with Accept: application/csv
=> Server responds with 406 (not acceptable)It’s important to note that this only controls which requests are accepted for processing, and does not automatically create or handle those responses appropriately. For example, requesting https://myapp.com/users
with an Accept: application/xml
header does not mean that valid XML content will be returned automatically, only that Lucky will allow your action to process the request.
If you need to access or set the headers, you can use request.headers
or response.headers
.
class Dashboard::Index < BrowserAction
get "/dashboard" do
remote_ip = request.headers["X-Forwarded-For"]?
if remote_ip
plain_text "The remote IP is #{remote_ip}"
else
plain_text "No remote IP found"
end
end
end
For things like handling CORS, and many other operations like caching,
it may be necessary to set response headers. Set these values
through the response.headers
object.
class Admin::Reports::Show < BrowserAction
get "/admin/reports/:report_id" do
response.headers["Cache-Control"] = "max-age=150"
html ShowPage
end
end
You can read more about working with headers in the Crystal docs on HTTP::Headers.
Every action is required to return a response
These are the built-in Lucky response methods:
html
- render a Lucky::HTMLPageredirect
- redirect the request to another locationplain_text
- respond with plain text with text/plain
Content-Type.json
- return a JSON response with application/json
Content-Type.raw_json
- similar to json
. See Rendering JSON for comparisons.xml
- return an XML response with text/xml
Content-Type.head
- return HTTP HEAD responsefile
- return a file for downloaddata
- return a String of datacomponent
- render a Component.class Jobs::Reports::Create < ApiAction
post "/jobs/reports/" do
# Run some fancy background job
if plain?
# plain text request, return some plain text
plain_text "Job sent for processing"
else
# Respond with HEAD 201
head 201
end
end
end
The
response
object is an instance of HTTP::Server::Response.
The html()
macro will render your Page object with a 200 status.
Error status codes like 404, 422, and 500, are handled separately
by your Errors::Show
action. Read the Error Handling
guide for more info.
In cases where you want to render an HTML page, but with a custom
status, Lucky provides a separate html_with_status
macro.
get "/teaparty" do
html_with_status IndexPage, 418
end
The file
method can be used to return a file and it’s contents to the browser, or render the contents of the file
inline to a web browser.
class Reports::Show < BrowserAction
get "/reports/sales" do
file "/path/to/reports.pdf",
disposition: "attachment",
filename: "report-#{Time.utc.month}.pdf",
content_type: "application/pdf"
end
end
If you need to return a file that already exists, you can use file
, but in the case that you only have the
String data, you can use this data
method.
class Reports::Show < BrowserAction
get "/reports/sales" do
report_data = "Street,City,State
123 street, Luckyville, CR
"
data report_data,
disposition: "attachment",
filename: "report-#{Time.utc.month}.pdf",
content_type: "application/csv"
end
end
The default
Content-Type
fordata
is"application/octet-stream"
The component
method allows you to return the HTML generated from a specific component. This can be used in place
of rendering an entire HTML page which can be useful for loading dynamic HTML on your front-end.
# src/components/comment_component.cr
class CommentComponent < BaseComponent
needs comment : Comment
def render
para(comment.text)
end
end
# src/actions/api/comments/show.cr
class Api::Comments::Show < ApiAction
get "/comments/:id" do
comment = CommentQuery.new.find(id)
component CommentComponent, comment: comment
end
end
In this example, you could make a javascript call to this /comments/123
endpoint which would return the HTML just for that comment.
This allows you to build out the HTML using Lucky’s type-safe builder, but also injecting dynamic HTML in to your page.
Lucky provides many different built in responses for automatically setting the appropriate Content-Type header for you.
When you need to respond with a non-standard Content-Type, you can use the send_text_response
method directly.
class Playlists::Index < BrowserAction
get "/playlist.m3u8" do
playlist = "..."
send_text_response(playlist, "application/x-mpegURL", status: 200)
end
end
Text responses are compressed automatically based on two Lucky::Server.settings
entries. If alternate behavior is desired, these settings can be adjusted in your Lucky app in config/server.cr
Lucky::Server.settings.gzip_enabled
(enabled in production by default, disabled in development and test. Can be changed in config/server.cr
)Lucky::Server.settings.gzip_content_types
You can redirect using the redirect
method:
Note that for most methods that link you elsewhere (like
redirect
, or thelink
helper in HTML pages), you can pass the action directly if it does not need any params. You can see this in the firstredirect
example below.
class Users::Create < BrowserAction
post "/users" do
redirect to: Users::Index # Default status is 302
redirect to: Users::Show.with(user_id: "user_id") # If the action needs params
redirect to: "/somewhere_else" # Redirect using a string path
redirect to: Users::Index, status: 301 # Override status
end
end
The default status for a redirect is HTTP::Status::FOUND
(302), but if you need a different status code, you can pass any HTTP Status Enum.
Alternatively, the default status value can also be configured globally:
# config/server.cr
Lucky::Redirectable.configure do |settings|
settings.redirect_status = HTTP::Status::SEE_OTHER.value
end
redirect_back
allows an action to send the user back to where they made the request from. This is really useful in situations like submitting
a lead form where the app might have it in several locations but the app doesn’t need to take them anywhere in particular after they submit it.
Rather than sending the user to a specific place after submitting the form, we can now send them back to where they originally submitted it.
class NewsletterSignupsCreate < BrowserAction
post "/newsletter/signup" do
# do the signup code
redirect_back fallback: Home::Index
end
end
The
fallback
argument is required and is used if the HTTP Referer is empty.
For security, Lucky prevents the redirect_back
from sending the user back to an external host. If you want to allow this, you’ll need
to set the allow_external: true
option.
post "/newsletter/signup" do
# Referer set to https://external.site/
redirect_back fallback: Home::Index, allow_external: true
end
Each of the response methods will have a built-in content type for that specific
method. If you’re using html
, it’ll return text/html
. For JSON, it’ll return application/json
.
When you need to change these values, there’s a method for the specific type you can
override.
For html, you’ll use the html_content_type
method. For JSON, it’s the json_content_type
.
There’s also XML, and plain text available in xml_content_type
and plain_content_type
respectively.
class LogEntries::Index < BrowserAction
get "/log_entries" do
log_entries = LogEntryQuery.new
html IndexPage, log_entries: log_entries
end
def html_content_type
"text/html; charset=ISO-8859-1"
end
end