Once a handler has finished its work, send the result through its supplied
http.ResponseWriter. The response helpers set the format and status and
return any error, so the helper can be the handler’s final call. Finish fallible
application work first: a returned error cannot replace a response already started.
Import net/http and arkoren.dev/core/http/response. These fragments assume an
Arkoren handler with writer w and incoming request r. For data delivered over
time rather than one complete result, use Streaming Responses.
Return JSON
Give clients a deliberate response shape rather than serializing an internal model with private fields. For an existing invoice ID:
type InvoiceCreated struct {
ID string `json:"id"`
Status string `json:"status"`
}
return response.JSON(w, http.StatusCreated, InvoiceCreated{
ID: invoiceID,
Status: "draft",
})
The helper encodes using JSON-v2, sets Content-Type: application/json, and
writes the document followed by a newline. Encoding finishes before the final
status is written. An encoding failure therefore leaves the router free to
render an error response; a later network-write failure does not.
Native collection and pagination values use the same helper. Lazy collections
are materialized for encoding: they must be finite and fit in memory. A source
error leaves the response uncommitted, but work already performed by that source
is not rolled back. response.XML likewise buffers its encoded document before
writing and uses application/xml.
Send Text
For a plain acknowledgment, use an explicit text response:
return response.String(w, http.StatusOK, "Invoice accepted")
This sets text/plain; charset=utf-8, replacing an existing media type. It does
not validate or transform the supplied text. If no body is needed, write only
the final status:
return response.Status(w, http.StatusNoContent)
The 204 is still a committed response. An informational status such as 103
is not final; send a final response afterward. An Arkoren handler returning
nil without a final response defaults to 204, unlike an adapted native
handler’s 200 default.
Render HTML
Use html/template when a page contains user-provided values. With an existing
parsed *html/template.Template named invoicePage, pass values as data, not
as template source:
type InvoicePage struct {
Reference string
}
return response.HTMLTemplate(w, http.StatusOK, invoicePage,
InvoicePage{Reference: reference},
)
Contextual HTML escaping protects the reference when the template uses ordinary
strings. Do not mark untrusted values as trusted template HTML. Rendering
finishes in a buffer before commitment; execution failures return without
starting the response. Success sets text/html; charset=utf-8.
response.HTML writes its input unchanged: reserve it for trusted HTML you
produced yourself, never concatenated user input. StringTemplate uses
text/template without HTML escaping and sets text/plain; charset=utf-8.
Both template helpers replace a pre-existing media type. For another format,
render into your own buffer and send the bytes with Raw.
Send Bytes
For already encoded content, set a known media type and use Raw. With an
existing PNG byte slice preview:
w.Header().Set("Content-Type", "image/png")
return response.Raw(w, http.StatusOK, preview)
Raw keeps an explicit Content-Type; otherwise it uses
application/octet-stream rather than sniffing bytes. Bytes always sets
application/octet-stream, replacing an earlier media type. Do not let an
untrusted upload choose an active response type such as HTML. See
file downloads for storage transfer
and download handling rather than loading large files into a response buffer.
Redirect Safely
For a fixed application-owned path after a successful POST:
return response.Redirect(w, http.StatusSeeOther, "/invoices")
Redirect sets Location and the supplied status without a response body. It
does not validate the target. A user-controlled return path instead needs
SafeRedirect; import arkoren.dev/core/http/request to read it:
next := request.QueryOr(r, "next", "/invoices")
return response.SafeRedirect(w, http.StatusSeeOther, next)
Only a relative path starting with exactly one / is accepted. Absolute URLs,
network-path targets such as //other.example, backslashes, and URL parse
failures return response.ErrUnsafeRedirect (400) before commitment. The
router renders that error; the helper does not write the rejection itself.
ValidateRedirectTarget provides the same check when validation must happen
before another operation.
Response Commitment
A response is committed when a final status is written, explicitly or
implicitly through a body write or flush. Informational 1xx statuses other
than 101 Switching Protocols leave the final response pending. 101 is final
for commitment tracking.
Buffered encoding and template preparation can fail while the response is still pending. Once a final status is written, returned errors still reach outer middleware and after-response hooks, but the router neither replaces the response nor appends a problem body. Accepted bytes cannot be undone, even if a later write fails. Error Handling explains panic recovery at this boundary; Streaming Responses explains live-output failures.
Always use the supplied writer for normal HTTP output. Writing through an
unwrapped native writer bypasses hooks and commitment tracking. A successful
connection hijack suppresses further error rendering and the default 204;
a failed hijack does not itself commit. You own a hijacked connection, including
closing it and arranging shutdown. Router recovery does not take it back.
For all output helpers, see the
response API reference.