A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 

688 rader
20 KiB

  1. /*
  2. * Copyright © 2018-2019 A Bunch Tell LLC.
  3. *
  4. * This file is part of WriteFreely.
  5. *
  6. * WriteFreely is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License, included
  8. * in the LICENSE file in this source code package.
  9. */
  10. package writefreely
  11. import (
  12. "fmt"
  13. "html/template"
  14. "net/http"
  15. "net/url"
  16. "runtime/debug"
  17. "strconv"
  18. "strings"
  19. "time"
  20. "github.com/gorilla/sessions"
  21. "github.com/writeas/impart"
  22. "github.com/writeas/web-core/log"
  23. "github.com/writeas/writefreely/page"
  24. )
  25. type UserLevel int
  26. const (
  27. UserLevelNone UserLevel = iota // user or not -- ignored
  28. UserLevelOptional // user or not -- object fetched if user
  29. UserLevelNoneRequired // non-user (required)
  30. UserLevelUser // user (required)
  31. )
  32. type (
  33. handlerFunc func(app *App, w http.ResponseWriter, r *http.Request) error
  34. userHandlerFunc func(app *App, u *User, w http.ResponseWriter, r *http.Request) error
  35. userApperHandlerFunc func(apper Apper, u *User, w http.ResponseWriter, r *http.Request) error
  36. dataHandlerFunc func(app *App, w http.ResponseWriter, r *http.Request) ([]byte, string, error)
  37. authFunc func(app *App, r *http.Request) (*User, error)
  38. )
  39. type Handler struct {
  40. errors *ErrorPages
  41. sessionStore *sessions.CookieStore
  42. app Apper
  43. }
  44. // ErrorPages hold template HTML error pages for displaying errors to the user.
  45. // In each, there should be a defined template named "base".
  46. type ErrorPages struct {
  47. NotFound *template.Template
  48. Gone *template.Template
  49. InternalServerError *template.Template
  50. Blank *template.Template
  51. }
  52. // NewHandler returns a new Handler instance, using the given StaticPage data,
  53. // and saving alias to the application's CookieStore.
  54. func NewHandler(apper Apper) *Handler {
  55. h := &Handler{
  56. errors: &ErrorPages{
  57. NotFound: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>404</title></head><body><p>Not found.</p></body></html>{{end}}")),
  58. Gone: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>410</title></head><body><p>Gone.</p></body></html>{{end}}")),
  59. InternalServerError: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>500</title></head><body><p>Internal server error.</p></body></html>{{end}}")),
  60. Blank: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>{{.Title}}</title></head><body><p>{{.Content}}</p></body></html>{{end}}")),
  61. },
  62. sessionStore: apper.App().sessionStore,
  63. app: apper,
  64. }
  65. return h
  66. }
  67. // NewWFHandler returns a new Handler instance, using WriteFreely template files.
  68. // You MUST call writefreely.InitTemplates() before this.
  69. func NewWFHandler(apper Apper) *Handler {
  70. h := NewHandler(apper)
  71. h.SetErrorPages(&ErrorPages{
  72. NotFound: pages["404-general.tmpl"],
  73. Gone: pages["410.tmpl"],
  74. InternalServerError: pages["500.tmpl"],
  75. Blank: pages["blank.tmpl"],
  76. })
  77. return h
  78. }
  79. // SetErrorPages sets the given set of ErrorPages as templates for any errors
  80. // that come up.
  81. func (h *Handler) SetErrorPages(e *ErrorPages) {
  82. h.errors = e
  83. }
  84. // User handles requests made in the web application by the authenticated user.
  85. // This provides user-friendly HTML pages and actions that work in the browser.
  86. func (h *Handler) User(f userHandlerFunc) http.HandlerFunc {
  87. return func(w http.ResponseWriter, r *http.Request) {
  88. h.handleHTTPError(w, r, func() error {
  89. var status int
  90. start := time.Now()
  91. defer func() {
  92. if e := recover(); e != nil {
  93. log.Error("%s: %s", e, debug.Stack())
  94. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  95. status = http.StatusInternalServerError
  96. }
  97. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  98. }()
  99. u := getUserSession(h.app.App(), r)
  100. if u == nil {
  101. err := ErrNotLoggedIn
  102. status = err.Status
  103. return err
  104. }
  105. err := f(h.app.App(), u, w, r)
  106. if err == nil {
  107. status = http.StatusOK
  108. } else if err, ok := err.(impart.HTTPError); ok {
  109. status = err.Status
  110. } else {
  111. status = http.StatusInternalServerError
  112. }
  113. return err
  114. }())
  115. }
  116. }
  117. // Admin handles requests on /admin routes
  118. func (h *Handler) Admin(f userHandlerFunc) http.HandlerFunc {
  119. return func(w http.ResponseWriter, r *http.Request) {
  120. h.handleHTTPError(w, r, func() error {
  121. var status int
  122. start := time.Now()
  123. defer func() {
  124. if e := recover(); e != nil {
  125. log.Error("%s: %s", e, debug.Stack())
  126. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  127. status = http.StatusInternalServerError
  128. }
  129. log.Info(fmt.Sprintf("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()))
  130. }()
  131. u := getUserSession(h.app.App(), r)
  132. if u == nil || !u.IsAdmin() {
  133. err := impart.HTTPError{http.StatusNotFound, ""}
  134. status = err.Status
  135. return err
  136. }
  137. err := f(h.app.App(), u, w, r)
  138. if err == nil {
  139. status = http.StatusOK
  140. } else if err, ok := err.(impart.HTTPError); ok {
  141. status = err.Status
  142. } else {
  143. status = http.StatusInternalServerError
  144. }
  145. return err
  146. }())
  147. }
  148. }
  149. // AdminApper handles requests on /admin routes that require an Apper.
  150. func (h *Handler) AdminApper(f userApperHandlerFunc) http.HandlerFunc {
  151. return func(w http.ResponseWriter, r *http.Request) {
  152. h.handleHTTPError(w, r, func() error {
  153. var status int
  154. start := time.Now()
  155. defer func() {
  156. if e := recover(); e != nil {
  157. log.Error("%s: %s", e, debug.Stack())
  158. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  159. status = http.StatusInternalServerError
  160. }
  161. log.Info(fmt.Sprintf("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()))
  162. }()
  163. u := getUserSession(h.app.App(), r)
  164. if u == nil || !u.IsAdmin() {
  165. err := impart.HTTPError{http.StatusNotFound, ""}
  166. status = err.Status
  167. return err
  168. }
  169. err := f(h.app, u, w, r)
  170. if err == nil {
  171. status = http.StatusOK
  172. } else if err, ok := err.(impart.HTTPError); ok {
  173. status = err.Status
  174. } else {
  175. status = http.StatusInternalServerError
  176. }
  177. return err
  178. }())
  179. }
  180. }
  181. // UserAPI handles requests made in the API by the authenticated user.
  182. // This provides user-friendly HTML pages and actions that work in the browser.
  183. func (h *Handler) UserAPI(f userHandlerFunc) http.HandlerFunc {
  184. return h.UserAll(false, f, func(app *App, r *http.Request) (*User, error) {
  185. // Authorize user from Authorization header
  186. t := r.Header.Get("Authorization")
  187. if t == "" {
  188. return nil, ErrNoAccessToken
  189. }
  190. u := &User{ID: app.db.GetUserID(t)}
  191. if u.ID == -1 {
  192. return nil, ErrBadAccessToken
  193. }
  194. return u, nil
  195. })
  196. }
  197. func (h *Handler) UserAll(web bool, f userHandlerFunc, a authFunc) http.HandlerFunc {
  198. return func(w http.ResponseWriter, r *http.Request) {
  199. handleFunc := func() error {
  200. var status int
  201. start := time.Now()
  202. defer func() {
  203. if e := recover(); e != nil {
  204. log.Error("%s: %s", e, debug.Stack())
  205. impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "Something didn't work quite right."})
  206. status = 500
  207. }
  208. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  209. }()
  210. u, err := a(h.app.App(), r)
  211. if err != nil {
  212. if err, ok := err.(impart.HTTPError); ok {
  213. status = err.Status
  214. } else {
  215. status = 500
  216. }
  217. return err
  218. }
  219. err = f(h.app.App(), u, w, r)
  220. if err == nil {
  221. status = 200
  222. } else if err, ok := err.(impart.HTTPError); ok {
  223. status = err.Status
  224. } else {
  225. status = 500
  226. }
  227. return err
  228. }
  229. if web {
  230. h.handleHTTPError(w, r, handleFunc())
  231. } else {
  232. h.handleError(w, r, handleFunc())
  233. }
  234. }
  235. }
  236. func (h *Handler) RedirectOnErr(f handlerFunc, loc string) handlerFunc {
  237. return func(app *App, w http.ResponseWriter, r *http.Request) error {
  238. err := f(app, w, r)
  239. if err != nil {
  240. if ie, ok := err.(impart.HTTPError); ok {
  241. // Override default redirect with returned error's, if it's a
  242. // redirect error.
  243. if ie.Status == http.StatusFound {
  244. return ie
  245. }
  246. }
  247. return impart.HTTPError{http.StatusFound, loc}
  248. }
  249. return nil
  250. }
  251. }
  252. func (h *Handler) Page(n string) http.HandlerFunc {
  253. return h.Web(func(app *App, w http.ResponseWriter, r *http.Request) error {
  254. t, ok := pages[n]
  255. if !ok {
  256. return impart.HTTPError{http.StatusNotFound, "Page not found."}
  257. }
  258. sp := pageForReq(app, r)
  259. err := t.ExecuteTemplate(w, "base", sp)
  260. if err != nil {
  261. log.Error("Unable to render page: %v", err)
  262. }
  263. return err
  264. }, UserLevelOptional)
  265. }
  266. func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
  267. return func(w http.ResponseWriter, r *http.Request) {
  268. // TODO: factor out this logic shared with Web()
  269. h.handleHTTPError(w, r, func() error {
  270. var status int
  271. start := time.Now()
  272. defer func() {
  273. if e := recover(); e != nil {
  274. u := getUserSession(h.app.App(), r)
  275. username := "None"
  276. if u != nil {
  277. username = u.Username
  278. }
  279. log.Error("User: %s\n\n%s: %s", username, e, debug.Stack())
  280. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  281. status = 500
  282. }
  283. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  284. }()
  285. var session *sessions.Session
  286. var err error
  287. if ul != UserLevelNone {
  288. session, err = h.sessionStore.Get(r, cookieName)
  289. if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
  290. // Cookie is required, but we can ignore this error
  291. log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
  292. }
  293. _, gotUser := session.Values[cookieUserVal].(*User)
  294. if ul == UserLevelNoneRequired && gotUser {
  295. to := correctPageFromLoginAttempt(r)
  296. log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
  297. err := impart.HTTPError{http.StatusFound, to}
  298. status = err.Status
  299. return err
  300. } else if ul == UserLevelUser && !gotUser {
  301. log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
  302. err := ErrNotLoggedIn
  303. status = err.Status
  304. return err
  305. }
  306. }
  307. // TODO: pass User object to function
  308. err = f(h.app.App(), w, r)
  309. if err == nil {
  310. status = 200
  311. } else if httpErr, ok := err.(impart.HTTPError); ok {
  312. status = httpErr.Status
  313. if status < 300 || status > 399 {
  314. addSessionFlash(h.app.App(), w, r, httpErr.Message, session)
  315. return impart.HTTPError{http.StatusFound, r.Referer()}
  316. }
  317. } else {
  318. e := fmt.Sprintf("[Web handler] 500: %v", err)
  319. if !strings.HasSuffix(e, "write: broken pipe") {
  320. log.Error(e)
  321. } else {
  322. log.Error(e)
  323. }
  324. log.Info("Web handler internal error render")
  325. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  326. status = 500
  327. }
  328. return err
  329. }())
  330. }
  331. }
  332. // Web handles requests made in the web application. This provides user-
  333. // friendly HTML pages and actions that work in the browser.
  334. func (h *Handler) Web(f handlerFunc, ul UserLevel) http.HandlerFunc {
  335. return func(w http.ResponseWriter, r *http.Request) {
  336. h.handleHTTPError(w, r, func() error {
  337. var status int
  338. start := time.Now()
  339. defer func() {
  340. if e := recover(); e != nil {
  341. u := getUserSession(h.app.App(), r)
  342. username := "None"
  343. if u != nil {
  344. username = u.Username
  345. }
  346. log.Error("User: %s\n\n%s: %s", username, e, debug.Stack())
  347. log.Info("Web deferred internal error render")
  348. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  349. status = 500
  350. }
  351. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  352. }()
  353. if ul != UserLevelNone {
  354. session, err := h.sessionStore.Get(r, cookieName)
  355. if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
  356. // Cookie is required, but we can ignore this error
  357. log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
  358. }
  359. _, gotUser := session.Values[cookieUserVal].(*User)
  360. if ul == UserLevelNoneRequired && gotUser {
  361. to := correctPageFromLoginAttempt(r)
  362. log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
  363. err := impart.HTTPError{http.StatusFound, to}
  364. status = err.Status
  365. return err
  366. } else if ul == UserLevelUser && !gotUser {
  367. log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
  368. err := ErrNotLoggedIn
  369. status = err.Status
  370. return err
  371. }
  372. }
  373. // TODO: pass User object to function
  374. err := f(h.app.App(), w, r)
  375. if err == nil {
  376. status = 200
  377. } else if httpErr, ok := err.(impart.HTTPError); ok {
  378. status = httpErr.Status
  379. } else {
  380. e := fmt.Sprintf("[Web handler] 500: %v", err)
  381. log.Error(e)
  382. log.Info("Web internal error render")
  383. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  384. status = 500
  385. }
  386. return err
  387. }())
  388. }
  389. }
  390. func (h *Handler) All(f handlerFunc) http.HandlerFunc {
  391. return func(w http.ResponseWriter, r *http.Request) {
  392. h.handleError(w, r, func() error {
  393. // TODO: return correct "success" status
  394. status := 200
  395. start := time.Now()
  396. defer func() {
  397. if e := recover(); e != nil {
  398. log.Error("%s:\n%s", e, debug.Stack())
  399. impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "Something didn't work quite right."})
  400. status = 500
  401. }
  402. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  403. }()
  404. // TODO: do any needed authentication
  405. err := f(h.app.App(), w, r)
  406. if err != nil {
  407. if err, ok := err.(impart.HTTPError); ok {
  408. status = err.Status
  409. } else {
  410. status = 500
  411. }
  412. }
  413. return err
  414. }())
  415. }
  416. }
  417. func (h *Handler) Download(f dataHandlerFunc, ul UserLevel) http.HandlerFunc {
  418. return func(w http.ResponseWriter, r *http.Request) {
  419. h.handleHTTPError(w, r, func() error {
  420. var status int
  421. start := time.Now()
  422. defer func() {
  423. if e := recover(); e != nil {
  424. log.Error("%s: %s", e, debug.Stack())
  425. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  426. status = 500
  427. }
  428. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  429. }()
  430. data, filename, err := f(h.app.App(), w, r)
  431. if err != nil {
  432. if err, ok := err.(impart.HTTPError); ok {
  433. status = err.Status
  434. } else {
  435. status = 500
  436. }
  437. return err
  438. }
  439. ext := ".json"
  440. ct := "application/json"
  441. if strings.HasSuffix(r.URL.Path, ".csv") {
  442. ext = ".csv"
  443. ct = "text/csv"
  444. } else if strings.HasSuffix(r.URL.Path, ".zip") {
  445. ext = ".zip"
  446. ct = "application/zip"
  447. }
  448. w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s%s", filename, ext))
  449. w.Header().Set("Content-Type", ct)
  450. w.Header().Set("Content-Length", strconv.Itoa(len(data)))
  451. fmt.Fprint(w, string(data))
  452. status = 200
  453. return nil
  454. }())
  455. }
  456. }
  457. func (h *Handler) Redirect(url string, ul UserLevel) http.HandlerFunc {
  458. return func(w http.ResponseWriter, r *http.Request) {
  459. h.handleHTTPError(w, r, func() error {
  460. start := time.Now()
  461. var status int
  462. if ul != UserLevelNone {
  463. session, err := h.sessionStore.Get(r, cookieName)
  464. if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
  465. // Cookie is required, but we can ignore this error
  466. log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
  467. }
  468. _, gotUser := session.Values[cookieUserVal].(*User)
  469. if ul == UserLevelNoneRequired && gotUser {
  470. to := correctPageFromLoginAttempt(r)
  471. log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
  472. err := impart.HTTPError{http.StatusFound, to}
  473. status = err.Status
  474. return err
  475. } else if ul == UserLevelUser && !gotUser {
  476. log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
  477. err := ErrNotLoggedIn
  478. status = err.Status
  479. return err
  480. }
  481. }
  482. status = sendRedirect(w, http.StatusFound, url)
  483. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  484. return nil
  485. }())
  486. }
  487. }
  488. func (h *Handler) handleHTTPError(w http.ResponseWriter, r *http.Request, err error) {
  489. if err == nil {
  490. return
  491. }
  492. if err, ok := err.(impart.HTTPError); ok {
  493. if err.Status >= 300 && err.Status < 400 {
  494. sendRedirect(w, err.Status, err.Message)
  495. return
  496. } else if err.Status == http.StatusUnauthorized {
  497. q := ""
  498. if r.URL.RawQuery != "" {
  499. q = url.QueryEscape("?" + r.URL.RawQuery)
  500. }
  501. sendRedirect(w, http.StatusFound, "/login?to="+r.URL.Path+q)
  502. return
  503. } else if err.Status == http.StatusGone {
  504. w.WriteHeader(err.Status)
  505. p := &struct {
  506. page.StaticPage
  507. Content *template.HTML
  508. }{
  509. StaticPage: pageForReq(h.app.App(), r),
  510. }
  511. if err.Message != "" {
  512. co := template.HTML(err.Message)
  513. p.Content = &co
  514. }
  515. h.errors.Gone.ExecuteTemplate(w, "base", p)
  516. return
  517. } else if err.Status == http.StatusNotFound {
  518. w.WriteHeader(err.Status)
  519. h.errors.NotFound.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  520. return
  521. } else if err.Status == http.StatusInternalServerError {
  522. w.WriteHeader(err.Status)
  523. log.Info("handleHTTPErorr internal error render")
  524. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  525. return
  526. } else if err.Status == http.StatusAccepted {
  527. impart.WriteSuccess(w, "", err.Status)
  528. return
  529. } else {
  530. p := &struct {
  531. page.StaticPage
  532. Title string
  533. Content template.HTML
  534. }{
  535. pageForReq(h.app.App(), r),
  536. fmt.Sprintf("Uh oh (%d)", err.Status),
  537. template.HTML(fmt.Sprintf("<p style=\"text-align: center\" class=\"introduction\">%s</p>", err.Message)),
  538. }
  539. h.errors.Blank.ExecuteTemplate(w, "base", p)
  540. return
  541. }
  542. impart.WriteError(w, err)
  543. return
  544. }
  545. impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "This is an unhelpful error message for a miscellaneous internal error."})
  546. }
  547. func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) {
  548. if err == nil {
  549. return
  550. }
  551. if err, ok := err.(impart.HTTPError); ok {
  552. if err.Status >= 300 && err.Status < 400 {
  553. sendRedirect(w, err.Status, err.Message)
  554. return
  555. }
  556. // if strings.Contains(r.Header.Get("Accept"), "text/html") {
  557. impart.WriteError(w, err)
  558. // }
  559. return
  560. }
  561. if IsJSON(r.Header.Get("Content-Type")) {
  562. impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "This is an unhelpful error message for a miscellaneous internal error."})
  563. return
  564. }
  565. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  566. }
  567. func correctPageFromLoginAttempt(r *http.Request) string {
  568. to := r.FormValue("to")
  569. if to == "" {
  570. to = "/"
  571. } else if !strings.HasPrefix(to, "/") {
  572. to = "/" + to
  573. }
  574. return to
  575. }
  576. func (h *Handler) LogHandlerFunc(f http.HandlerFunc) http.HandlerFunc {
  577. return func(w http.ResponseWriter, r *http.Request) {
  578. h.handleHTTPError(w, r, func() error {
  579. status := 200
  580. start := time.Now()
  581. defer func() {
  582. if e := recover(); e != nil {
  583. log.Error("Handler.LogHandlerFunc\n\n%s: %s", e, debug.Stack())
  584. h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
  585. status = 500
  586. }
  587. // TODO: log actual status code returned
  588. log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())
  589. }()
  590. f(w, r)
  591. return nil
  592. }())
  593. }
  594. }
  595. func sendRedirect(w http.ResponseWriter, code int, location string) int {
  596. w.Header().Set("Location", location)
  597. w.WriteHeader(code)
  598. return code
  599. }