handler.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. package templ
  2. import "net/http"
  3. // ComponentHandler is a http.Handler that renders components.
  4. type ComponentHandler struct {
  5. Component Component
  6. Status int
  7. ContentType string
  8. ErrorHandler func(r *http.Request, err error) http.Handler
  9. StreamResponse bool
  10. }
  11. const componentHandlerErrorMessage = "templ: failed to render template"
  12. func (ch *ComponentHandler) ServeHTTPBuffered(w http.ResponseWriter, r *http.Request) {
  13. // Since the component may error, write to a buffer first.
  14. // This prevents partial responses from being written to the client.
  15. buf := GetBuffer()
  16. defer ReleaseBuffer(buf)
  17. err := ch.Component.Render(r.Context(), buf)
  18. if err != nil {
  19. if ch.ErrorHandler != nil {
  20. w.Header().Set("Content-Type", ch.ContentType)
  21. ch.ErrorHandler(r, err).ServeHTTP(w, r)
  22. return
  23. }
  24. http.Error(w, componentHandlerErrorMessage, http.StatusInternalServerError)
  25. return
  26. }
  27. w.Header().Set("Content-Type", ch.ContentType)
  28. if ch.Status != 0 {
  29. w.WriteHeader(ch.Status)
  30. }
  31. // Ignore write error like http.Error() does, because there is
  32. // no way to recover at this point.
  33. _, _ = w.Write(buf.Bytes())
  34. }
  35. func (ch *ComponentHandler) ServeHTTPStreamed(w http.ResponseWriter, r *http.Request) {
  36. w.Header().Set("Content-Type", ch.ContentType)
  37. if ch.Status != 0 {
  38. w.WriteHeader(ch.Status)
  39. }
  40. if err := ch.Component.Render(r.Context(), w); err != nil {
  41. if ch.ErrorHandler != nil {
  42. w.Header().Set("Content-Type", ch.ContentType)
  43. ch.ErrorHandler(r, err).ServeHTTP(w, r)
  44. return
  45. }
  46. http.Error(w, componentHandlerErrorMessage, http.StatusInternalServerError)
  47. }
  48. }
  49. // ServeHTTP implements the http.Handler interface.
  50. func (ch ComponentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  51. if ch.StreamResponse {
  52. ch.ServeHTTPStreamed(w, r)
  53. return
  54. }
  55. ch.ServeHTTPBuffered(w, r)
  56. }
  57. // Handler creates a http.Handler that renders the template.
  58. func Handler(c Component, options ...func(*ComponentHandler)) *ComponentHandler {
  59. ch := &ComponentHandler{
  60. Component: c,
  61. ContentType: "text/html; charset=utf-8",
  62. }
  63. for _, o := range options {
  64. o(ch)
  65. }
  66. return ch
  67. }
  68. // WithStatus sets the HTTP status code returned by the ComponentHandler.
  69. func WithStatus(status int) func(*ComponentHandler) {
  70. return func(ch *ComponentHandler) {
  71. ch.Status = status
  72. }
  73. }
  74. // WithContentType sets the Content-Type header returned by the ComponentHandler.
  75. func WithContentType(contentType string) func(*ComponentHandler) {
  76. return func(ch *ComponentHandler) {
  77. ch.ContentType = contentType
  78. }
  79. }
  80. // WithErrorHandler sets the error handler used if rendering fails.
  81. func WithErrorHandler(eh func(r *http.Request, err error) http.Handler) func(*ComponentHandler) {
  82. return func(ch *ComponentHandler) {
  83. ch.ErrorHandler = eh
  84. }
  85. }
  86. // WithStreaming sets the ComponentHandler to stream the response instead of buffering it.
  87. func WithStreaming() func(*ComponentHandler) {
  88. return func(ch *ComponentHandler) {
  89. ch.StreamResponse = true
  90. }
  91. }