Capstan is a Golang web framework that shares some similarities with others in its segment.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

608 lines
17KB

  1. package capstan
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "net/url"
  8. "strconv"
  9. "strings"
  10. . "git.destrealm.org/go/capstan/errors"
  11. "git.destrealm.org/go/capstan/render"
  12. "git.destrealm.org/go/capstan/session"
  13. "git.destrealm.org/go/capstan/utils"
  14. "github.com/go-chi/chi"
  15. "github.com/gorilla/websocket"
  16. )
  17. type ContextMaker func(http.ResponseWriter, *http.Request, *Route) Context
  18. type capstanContext struct {
  19. // Request associated with this context.
  20. request *http.Request
  21. // Response associated with this context.
  22. response http.ResponseWriter
  23. // Route metadata associated with this context.
  24. route *Route
  25. // Template variables specific to this request. This value may not work with
  26. // all template backends.
  27. requestTplVars render.MapValues
  28. // ParamTypes contains a map of parameters to their user-configured types.
  29. paramTypes map[string]string
  30. // Type of the response, typically "json" or "html". This will dictate the
  31. // content type returned by the server. If this value is empty, either the
  32. // default value configured by Go will be used or whatever the user sets via
  33. // the appropriate response header.
  34. typ string
  35. // Template renderer. Used with Context.Render().
  36. renderer render.Renderer
  37. // Context renderer. Used for additional context. If nil, `renderer` is
  38. // used instead.
  39. contextRenderer render.ContextRenderer
  40. // Session available for this context. This is created when
  41. // Context.Session() is called and then consulted for a nil status.
  42. sess session.Session
  43. // WebSocket connection, if established.
  44. wsConn *websocket.Conn
  45. // Content type for the response.
  46. contentType ContentType
  47. // Last error saved. This is typically generated by the various Write
  48. // methods on failure but may also be set by the caller.
  49. //
  50. // In the event of a panic captured by middleware, this may contain a stack
  51. // trace if debugging is enabled.
  52. lastError error
  53. // Internal error code state. Maps to HTTP errors. This can be set
  54. // internally by the context's read and write methods or externally by the
  55. // caller.
  56. //
  57. // Generally speaking, this selects only one of a handful of errors when set
  58. // automatically that are chosen to best represent the error condition. For
  59. // example, when reading malformed JSON, this will be set to 400 (Bad
  60. // Request). If a response fails its Write() call, this will be set to 500
  61. // (Internal Server Error).
  62. code int
  63. }
  64. // MakeMiddlewareContext returns a Context instance that's suitable for use
  65. // within middleware. In this case, Type is the empty string, and ParamTypes is
  66. // an empty map.
  67. func MakeMiddlewareContext(w http.ResponseWriter, r *http.Request) Context {
  68. return &capstanContext{request: r, response: w, paramTypes: make(map[string]string)}
  69. }
  70. func MakeRouteContext(w http.ResponseWriter, r *http.Request, rt *Route) Context {
  71. if rt != nil {
  72. return &capstanContext{
  73. request: r,
  74. response: w,
  75. requestTplVars: render.MapValues{},
  76. paramTypes: rt.ParamTypes,
  77. route: rt,
  78. renderer: rt.Renderer,
  79. }
  80. }
  81. return &capstanContext{
  82. request: r,
  83. response: w,
  84. }
  85. }
  86. // Float attempts to cast the named URI parameter to a float64 or returns an
  87. // error if the cast fails.
  88. //
  89. // This does not currently read query string parameters, unlike Param().
  90. func (c *capstanContext) Float(name string) (float64, error) {
  91. tp, ok := c.paramTypes[name]
  92. if !ok {
  93. return 0.0, ErrInvalidParam
  94. }
  95. switch tp {
  96. case "float":
  97. fallthrough
  98. case "float32":
  99. fallthrough
  100. case "float64":
  101. v, err := strconv.ParseFloat(chi.URLParam(c.request, name), 64)
  102. if err == nil {
  103. return v, nil
  104. }
  105. }
  106. return 0.0, ErrInvalidParam
  107. }
  108. // FloatDefault attempts to cast the named URI parameter to a float64 or returns
  109. // the deffault value specified by `def`.
  110. //
  111. // This does not currently read query string parameters, unlike Param().
  112. func (c *capstanContext) FloatDefault(name string, def float64) float64 {
  113. tp, ok := c.paramTypes[name]
  114. if !ok {
  115. return def
  116. }
  117. switch tp {
  118. case "float":
  119. fallthrough
  120. case "float32":
  121. fallthrough
  122. case "float64":
  123. v, err := strconv.ParseFloat(chi.URLParam(c.request, name), 64)
  124. if err == nil {
  125. return v
  126. }
  127. }
  128. return def
  129. }
  130. // Int attempts to cast the named URI parameter to a int64 or returns an error
  131. // if the cast fails.
  132. //
  133. // This does not currently read query string parameters, unlike Param().
  134. func (c *capstanContext) Int(name string) (int64, error) {
  135. tp, ok := c.paramTypes[name]
  136. if !ok {
  137. return 0, ErrInvalidParam
  138. }
  139. switch tp {
  140. case "int":
  141. fallthrough
  142. case "int32":
  143. fallthrough
  144. case "int64":
  145. v, err := strconv.ParseInt(chi.URLParam(c.request, name), 10, 64)
  146. if err == nil {
  147. return v, nil
  148. }
  149. }
  150. return 0, ErrInvalidParam
  151. }
  152. // IntDefault attempts to cast the named URI parameter to a int64 or returns the
  153. // deffault value specified by `def`.
  154. //
  155. // This does not currently read query string parameters, unlike Param().
  156. func (c *capstanContext) IntDefault(name string, def int64) int64 {
  157. tp, ok := c.paramTypes[name]
  158. if !ok {
  159. return def
  160. }
  161. switch tp {
  162. case "int":
  163. fallthrough
  164. case "int32":
  165. fallthrough
  166. case "int64":
  167. v, err := strconv.ParseInt(chi.URLParam(c.request, name), 10, 64)
  168. if err == nil {
  169. return v
  170. }
  171. }
  172. return def
  173. }
  174. // Param returns the named parameter derived from either the query string or the
  175. // defined URL parameter set. Parameters contained as members of the request URI
  176. // will be prioritized over those contained as part of the query string.
  177. // Parameters retrieved via this method are always unescaped or the empty string
  178. // is returned if the parameter cannot be unescaped for whatever reason.
  179. //
  180. // Note that unlike other parameter acquisition functions, no type conversions
  181. // are performed via this function.
  182. func (c *capstanContext) Param(name string) string {
  183. var err error
  184. s := chi.URLParam(c.request, name)
  185. if s != "" {
  186. if unescaped, err := url.PathUnescape(s); err == nil {
  187. return unescaped
  188. }
  189. return ""
  190. }
  191. if v, ok := c.request.URL.Query()[name]; ok && len(v) > 0 {
  192. s = v[0]
  193. }
  194. if s, err = url.QueryUnescape(s); err == nil {
  195. return s
  196. }
  197. return ""
  198. }
  199. // ParamDefault returns the URL parameter or query string variable defined by
  200. // `name` or the default value `def` if the parameter cannot otherwise be found.
  201. // Be aware that this method defines "missing" parameters as those that return
  202. // the empty string.
  203. //
  204. // Parameters retrieved via this method are always unescaped of the default
  205. // value is returned if the parameter value cannot be unescaped because of
  206. // invalid characters.
  207. func (c *capstanContext) ParamDefault(name, def string) string {
  208. var v string
  209. if v = c.Param(name); v == "" {
  210. return def
  211. }
  212. return v
  213. }
  214. // Params returns the named parameter as a slice of strings derived from either
  215. // the query string or the defined URL parameter set. Query parameters will
  216. // always be defined first. Multiple query parameters can be consumed and
  217. // returned. Parameters retrieved via this method are always unescaped or an
  218. // empty string is returned if the parameter cannot be unescaped for whatever
  219. // reason.
  220. //
  221. // Note that unlike other parameter acquisition functions, no type conversions
  222. // are performed via this function.
  223. func (c *capstanContext) Params(name string) []string {
  224. var ok bool
  225. var v []string
  226. if v, ok = c.request.URL.Query()[name]; !ok {
  227. v = make([]string, 0)
  228. }
  229. for i := 0; i < len(v); i++ {
  230. if unescaped, err := url.QueryUnescape(v[i]); err == nil {
  231. v[i] = unescaped
  232. } else {
  233. v[i] = ""
  234. }
  235. }
  236. if unescaped, err := url.PathUnescape(chi.URLParam(c.request, name)); err == nil {
  237. v = append(v, unescaped)
  238. } else {
  239. v = append(v, "")
  240. }
  241. return v
  242. }
  243. // String returns the URL parameter as its string representation or the error
  244. // ErrInvalidParam if its type cast cannot be found in the context parameter
  245. // types.
  246. //
  247. // No effort is made to escape parameters retrieved via this method.
  248. func (c *capstanContext) String(name string) (string, error) {
  249. _, ok := c.paramTypes[name]
  250. if !ok {
  251. return "", ErrInvalidParam
  252. }
  253. return chi.URLParam(c.request, name), nil
  254. }
  255. // StringDefault returns the URL parameter as its string representation or the
  256. // default value if it cannot be found in the parameter types map.
  257. //
  258. // No effort is made to escape parameters retrieved via this method.
  259. func (c *capstanContext) StringDefault(name string, def string) string {
  260. _, ok := c.paramTypes[name]
  261. if !ok {
  262. return def
  263. }
  264. return chi.URLParam(c.request, name)
  265. }
  266. // In transforms either a form submission or JSON-formatted request into `v`
  267. // which must be a tagged structure. For compatibility, struct tags should
  268. // follow the same declaration as one might use when parsing JSON.
  269. //
  270. // It's advisable to keep both the JSON and form field names identical.
  271. func (c *capstanContext) In(v interface{}) error {
  272. var err error
  273. if strings.HasPrefix(c.request.Header.
  274. Get("content-type"), "application/json") || strings.
  275. HasPrefix(c.request.Header.Get("content-type"), "text/json") {
  276. err = c.json(v)
  277. } else if strings.HasPrefix(c.request.Header.
  278. Get("content-type"), "multipart/form-data") || strings.
  279. HasPrefix(c.request.Header.Get("content-type"),
  280. "application/x-www-form-urlencoded") {
  281. c.request.ParseForm()
  282. err = utils.MapForm(c.request.Form, v)
  283. }
  284. if err != nil {
  285. c.SetError(err)
  286. // Bad request.
  287. c.code = 400
  288. return c
  289. }
  290. return nil
  291. }
  292. // Actually perform the JSON parsing since we have multiple methods that will
  293. // examine the headers.
  294. func (c *capstanContext) json(v interface{}) error {
  295. b, err := ioutil.ReadAll(c.request.Body)
  296. if err != nil {
  297. return err
  298. }
  299. if err := json.Unmarshal(b, v); err != nil {
  300. return ErrJSONDecoding.Do(err)
  301. }
  302. return nil
  303. }
  304. // JSON is a convenience method for reading the request body as JSON into the
  305. // interface specified. Note that this requires JSON struct tags and possesses
  306. // all of the drawbacks of the out-of-the-box Go JSON handling.
  307. //
  308. // Returns an unwrapped error that should be handled by the controller.
  309. func (c *capstanContext) JSON(v interface{}) error {
  310. if !strings.HasPrefix(c.request.Header.
  311. Get("content-type"), "application/json") || strings.
  312. HasPrefix(c.request.Header.Get("content-type"), "text/json") {
  313. c.code = 400
  314. c.SetError(ErrNotJSON)
  315. return c
  316. }
  317. if err := c.json(v); err != nil {
  318. c.SetError(err)
  319. // Bad request.
  320. c.code = 400
  321. return c
  322. }
  323. return nil
  324. }
  325. func (c *capstanContext) ParamTypes() map[string]string {
  326. return c.paramTypes
  327. }
  328. // Render the named template using the backend configured for this controller or
  329. // globally if none set. If no template renderer is configured, calls to this
  330. // method will be noops.
  331. //
  332. // Returns either wrapped or unwrapped errors dependent upon the backend.
  333. func (c *capstanContext) Render(name string, values interface{}) error {
  334. if v, ok := values.(render.MapValues); ok {
  335. v.Update(c.requestTplVars)
  336. }
  337. if c.contextRenderer != nil {
  338. if err := c.contextRenderer.Render(name, values, c); err != nil {
  339. c.SetError(err)
  340. c.code = 500
  341. return c
  342. }
  343. return nil
  344. }
  345. if c.renderer != nil {
  346. if err := c.renderer.Render(name, values, c); err != nil {
  347. c.SetError(err)
  348. c.code = 500
  349. return c
  350. }
  351. return nil
  352. }
  353. return ErrNoRendererConfigured
  354. }
  355. // Renderer returns a ContextRenderer bound to the template indicated by `name`.
  356. func (c *capstanContext) Renderer() (render.ContextRenderer, error) {
  357. if c.renderer == nil {
  358. return nil, ErrNoRendererConfigured
  359. }
  360. if c.contextRenderer = c.renderer.ContextRenderer(); c.contextRenderer != nil {
  361. return c.contextRenderer, nil
  362. }
  363. return nil, ErrNoRendererConfigured
  364. }
  365. func (c *capstanContext) SetRenderer(renderer render.Renderer) {
  366. c.renderer = renderer
  367. }
  368. func (c *capstanContext) Headers() http.Header {
  369. return c.request.Header
  370. }
  371. func (c *capstanContext) Request() *http.Request {
  372. return c.request
  373. }
  374. func (c *capstanContext) Response() http.ResponseWriter {
  375. return c.response
  376. }
  377. func (c *capstanContext) RequestContext(key string, value interface{}) {
  378. c.requestTplVars[key] = value
  379. }
  380. func (c *capstanContext) Route() *Route {
  381. return c.route
  382. }
  383. func (c *capstanContext) SetCookie(cookie *http.Cookie) {
  384. c.response.Header().Add("set-cookie", cookie.String())
  385. }
  386. func (c *capstanContext) WebSocket() *websocket.Conn {
  387. return c.wsConn
  388. }
  389. func (c *capstanContext) SetWebSocket(socket *websocket.Conn) {
  390. c.wsConn = socket
  391. }
  392. // Session returns the current configured session object or nil.
  393. //
  394. // Clients are advised to always check the value returned by this call for nil
  395. // as it indicates no session has been configured.
  396. //
  397. // TODO: Empty sessions render as empty cookies. This should probably render
  398. // *something* so as not to suggest whether a session is actually empty (or
  399. // not).
  400. //
  401. // TODO: Setting a session cookie should re-render that same cookie even if it
  402. // was unaltered so that it appears completely different with each request.
  403. //
  404. // TODO: Named session cookies or session cookies that can follow a set pattenr
  405. // with an additional name would be useful for things like a) separating
  406. // authentication concerns into a distinct cookie and b) adding everything else
  407. // into a "global" cookie.
  408. func (c *capstanContext) Session() session.Session {
  409. if c.sess != nil {
  410. return c.sess
  411. }
  412. c.sess = session.Load().NewSession(c.response, c.request)
  413. return c.sess
  414. }
  415. func (c *capstanContext) URLFor(controller string) *URLBuilder {
  416. return c.route.router.urls.For(controller)
  417. }
  418. // Write finalizes all header changes, sessions, cookies, etc., and proxies the
  419. // Write() call to c.Response.
  420. //
  421. // If you intend to use sessions, you MUST use this Write() method in preference
  422. // over c.Response.Write(), otherwise your session data will not be dispatched
  423. // to the client.
  424. func (c *capstanContext) Write(b []byte) (int, error) {
  425. if c.sess != nil {
  426. c.sess.Save()
  427. }
  428. n, err := c.response.Write(b)
  429. if err != nil {
  430. c.SetError(err)
  431. c.code = 500
  432. return n, c
  433. }
  434. return n, nil
  435. }
  436. // WriteHeader proxies the specified `code` to the underlying
  437. // http.ResponseWriter. Use of this function is recommended as it may perform
  438. // other duties in the future similarly to Context.Write() (e.g. session
  439. // saving).
  440. func (c *capstanContext) WriteHeader(code int) {
  441. switch c.contentType {
  442. case ApplicationJSON:
  443. c.response.Header().Set("content-type", string(ApplicationJSON))
  444. case TextPlain:
  445. c.response.Header().Set("content-type", string(TextPlain))
  446. default:
  447. c.response.Header().Set("content-type", string(TextHTML))
  448. }
  449. c.response.WriteHeader(code)
  450. }
  451. // WriteJSON is a convenience method for writing a JSON response. Content types
  452. // returned by this method will always be "application/json; charset=utf-8".
  453. //
  454. // Returns an unwrapped error that should be handled by the controller.
  455. func (c *capstanContext) WriteJSON(v interface{}) error {
  456. c.response.Header().Add("content-type", "application/json; charset=utf-8")
  457. b, err := json.Marshal(v)
  458. if err != nil {
  459. return err
  460. }
  461. c.response.Header().Add("content-length",
  462. strconv.FormatInt(int64(len(b)), 10))
  463. _, err = c.response.Write(b)
  464. if err != nil {
  465. c.SetError(err)
  466. c.code = 500
  467. return c
  468. }
  469. return nil
  470. }
  471. // ContentType forces the content type for the current request.
  472. //
  473. // If an error condition is raised, this will control the type of template
  474. // response returned, if error templates are enabled.
  475. func (c *capstanContext) ContentType(ct ContentType) {
  476. c.contentType = ct
  477. }
  478. // HasError returns the last error.
  479. func (c *capstanContext) HasError() error {
  480. return c.lastError
  481. }
  482. // Panic sets the last error state to the contents of the backtrace.
  483. func (c *capstanContext) Panic(trace string) {
  484. c.SetError(fmt.Errorf(trace))
  485. }
  486. // Fulfill the error and ErrorTracer interfaces.
  487. // Error implements the error interface and returns the value of the context's
  488. // lastError.
  489. //
  490. // Because this returns the error's string value, this cannot be checked for nil
  491. // (no pointer or interface type). Therefore, the context should not be returned
  492. // from an nil error state unless the caller intends to check whether the
  493. // returned value is of type Context.
  494. func (c *capstanContext) Error() string {
  495. if c.lastError == nil {
  496. return ""
  497. }
  498. return c.lastError.Error()
  499. }
  500. // SetError changes the value of lastError for this context.
  501. func (c *capstanContext) SetError(err error) {
  502. if _, ok := c.lastError.(Context); ok {
  503. panic("SetError(): context-as-error is invalid here")
  504. }
  505. c.lastError = err
  506. }
  507. // Code finalizes the implementation of ErrorTracer by allowing callers to read
  508. // the error code.
  509. //
  510. // In most cases, this will return an HTTP error status code in the event there
  511. // is an error condition triggered internally or by the caller. However, this
  512. // will also return an error code of 0 when no error has been triggered. When
  513. // converting this to an HTTP status, the zero value should be checked for and
  514. // converted appropriately into either a 1xx, 2xx, or 3xx value depending on the
  515. // response state.
  516. func (c *capstanContext) Code() int {
  517. return c.code
  518. }
  519. // SetCode may be used by callers to set the status code of this context.
  520. //
  521. // Note that the status code may change as the request evolves and the value set
  522. // by the caller isn't guaranteed to be the final value unless the endpoint
  523. // returns immediately after this call.
  524. func (c *capstanContext) SetCode(code int) {
  525. c.code = code
  526. }