Micro & pluggable web framework for Go
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.

482 lines
11KB

  1. // Copyright 2015 The Tango Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package tango
  5. import (
  6. "bytes"
  7. "encoding/json"
  8. "encoding/xml"
  9. "fmt"
  10. "io"
  11. "io/ioutil"
  12. "net/http"
  13. "os"
  14. "path/filepath"
  15. "reflect"
  16. "strings"
  17. )
  18. // Handler defines middleware interface
  19. type Handler interface {
  20. Handle(*Context)
  21. }
  22. // Context defines request and response context
  23. type Context struct {
  24. tan *Tango
  25. Logger
  26. idx int
  27. req *http.Request
  28. ResponseWriter
  29. route *Route
  30. params Params
  31. callArgs []reflect.Value
  32. matched bool
  33. stage byte
  34. action interface{}
  35. Result interface{}
  36. }
  37. func (ctx *Context) reset(req *http.Request, resp ResponseWriter) {
  38. ctx.req = req
  39. ctx.ResponseWriter = resp
  40. ctx.idx = 0
  41. ctx.stage = 0
  42. ctx.route = nil
  43. ctx.params = nil
  44. ctx.callArgs = nil
  45. ctx.matched = false
  46. ctx.action = nil
  47. ctx.Result = nil
  48. }
  49. // HandleError handles errors
  50. func (ctx *Context) HandleError() {
  51. ctx.tan.ErrHandler.Handle(ctx)
  52. }
  53. // Req returns current HTTP Request information
  54. func (ctx *Context) Req() *http.Request {
  55. return ctx.req
  56. }
  57. // SetRequest sets the request to context
  58. func (ctx *Context) SetRequest(req *http.Request) {
  59. ctx.req = req
  60. }
  61. // IsAjax returns if the request is an ajax request
  62. func (ctx *Context) IsAjax() bool {
  63. return ctx.Req().Header.Get("X-Requested-With") == "XMLHttpRequest"
  64. }
  65. // SecureCookies generates a secret cookie
  66. func (ctx *Context) SecureCookies(secret string) Cookies {
  67. return &secureCookies{
  68. (*cookies)(ctx),
  69. secret,
  70. }
  71. }
  72. // Cookies returns the cookies
  73. func (ctx *Context) Cookies() Cookies {
  74. return (*cookies)(ctx)
  75. }
  76. // Forms returns the query/body names and values
  77. func (ctx *Context) Forms() *Forms {
  78. ctx.req.ParseForm()
  79. return (*Forms)(ctx.req)
  80. }
  81. // Queries returns the query names and values
  82. func (ctx *Context) Queries() *Queries {
  83. return (*Queries)(ctx.req)
  84. }
  85. // Route returns route
  86. func (ctx *Context) Route() *Route {
  87. ctx.newAction()
  88. return ctx.route
  89. }
  90. // Params returns the URL params
  91. func (ctx *Context) Params() *Params {
  92. ctx.newAction()
  93. return &ctx.params
  94. }
  95. // IP returns remote IP
  96. func (ctx *Context) IP() string {
  97. proxy := []string{}
  98. if ips := ctx.Req().Header.Get("X-Forwarded-For"); ips != "" {
  99. proxy = strings.Split(ips, ",")
  100. }
  101. if len(proxy) > 0 && proxy[0] != "" {
  102. return proxy[0]
  103. }
  104. ip := strings.Split(ctx.Req().RemoteAddr, ":")
  105. if len(ip) > 0 {
  106. if ip[0] != "[" {
  107. return ip[0]
  108. }
  109. }
  110. return "127.0.0.1"
  111. }
  112. // Action returns action
  113. func (ctx *Context) Action() interface{} {
  114. ctx.newAction()
  115. return ctx.action
  116. }
  117. // ActionValue returns action value
  118. func (ctx *Context) ActionValue() reflect.Value {
  119. ctx.newAction()
  120. return ctx.callArgs[0]
  121. }
  122. // ActionTag returns field tag on action struct
  123. // TODO: cache the name
  124. func (ctx *Context) ActionTag(fieldName string) string {
  125. ctx.newAction()
  126. if ctx.route.routeType == StructPtrRoute || ctx.route.routeType == StructRoute {
  127. tp := ctx.callArgs[0].Type()
  128. if tp.Kind() == reflect.Ptr {
  129. tp = tp.Elem()
  130. }
  131. field, ok := tp.FieldByName(fieldName)
  132. if !ok {
  133. return ""
  134. }
  135. return string(field.Tag)
  136. }
  137. return ""
  138. }
  139. // WriteString writes a string to response write
  140. func (ctx *Context) WriteString(content string) (int, error) {
  141. return io.WriteString(ctx.ResponseWriter, content)
  142. }
  143. func (ctx *Context) newAction() {
  144. if !ctx.matched {
  145. reqPath := removeStick(ctx.Req().URL.Path)
  146. ctx.route, ctx.params = ctx.tan.Match(reqPath, ctx.Req().Method)
  147. if ctx.route != nil {
  148. vc := ctx.route.newAction()
  149. ctx.action = vc.Interface()
  150. switch ctx.route.routeType {
  151. case StructPtrRoute:
  152. ctx.callArgs = []reflect.Value{vc.Elem()}
  153. case StructRoute:
  154. ctx.callArgs = []reflect.Value{vc}
  155. case FuncRoute:
  156. ctx.callArgs = []reflect.Value{}
  157. case FuncHTTPRoute:
  158. ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx.ResponseWriter),
  159. reflect.ValueOf(ctx.Req())}
  160. case FuncReqRoute:
  161. ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx.Req())}
  162. case FuncResponseRoute:
  163. ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx.ResponseWriter)}
  164. case FuncCtxRoute:
  165. ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx)}
  166. default:
  167. panic("routeType error")
  168. }
  169. }
  170. ctx.matched = true
  171. }
  172. }
  173. // Next call next middleware or action
  174. // WARNING: don't invoke this method on action
  175. func (ctx *Context) Next() {
  176. ctx.idx++
  177. ctx.invoke()
  178. }
  179. func (ctx *Context) execute() {
  180. ctx.newAction()
  181. // route is matched
  182. if ctx.action != nil {
  183. if len(ctx.route.handlers) > 0 && ctx.stage == 0 {
  184. ctx.idx = 0
  185. ctx.stage = 1
  186. ctx.invoke()
  187. return
  188. }
  189. var ret []reflect.Value
  190. switch fn := ctx.route.raw.(type) {
  191. case func(*Context):
  192. fn(ctx)
  193. case func(http.ResponseWriter, *http.Request):
  194. fn(ctx.ResponseWriter, ctx.req)
  195. case func():
  196. fn()
  197. case func(*http.Request):
  198. fn(ctx.req)
  199. case func(http.ResponseWriter):
  200. fn(ctx.ResponseWriter)
  201. default:
  202. ret = ctx.route.method.Call(ctx.callArgs)
  203. }
  204. if len(ret) == 1 {
  205. ctx.Result = ret[0].Interface()
  206. } else if len(ret) == 2 {
  207. if code, ok := ret[0].Interface().(int); ok {
  208. ctx.Result = &StatusResult{code, ret[1].Interface()}
  209. }
  210. }
  211. // not route matched
  212. } else {
  213. if !ctx.Written() {
  214. ctx.NotFound()
  215. }
  216. }
  217. }
  218. func (ctx *Context) invoke() {
  219. if ctx.stage == 0 {
  220. if ctx.idx < len(ctx.tan.handlers) {
  221. ctx.tan.handlers[ctx.idx].Handle(ctx)
  222. } else {
  223. ctx.execute()
  224. }
  225. } else if ctx.stage == 1 {
  226. if ctx.idx < len(ctx.route.handlers) {
  227. ctx.route.handlers[ctx.idx].Handle(ctx)
  228. } else {
  229. ctx.execute()
  230. }
  231. }
  232. }
  233. func toHTTPError(err error) (msg string, httpStatus int) {
  234. if os.IsNotExist(err) {
  235. return "404 page not found", http.StatusNotFound
  236. }
  237. if os.IsPermission(err) {
  238. return "403 Forbidden", http.StatusForbidden
  239. }
  240. // Default:
  241. return "500 Internal Server Error", http.StatusInternalServerError
  242. }
  243. // ServeFile serves a file
  244. func (ctx *Context) ServeFile(path string) error {
  245. dir, file := filepath.Split(path)
  246. return ctx.ServeContent(file, http.Dir(dir))
  247. }
  248. // ServeContent serve content
  249. func (ctx *Context) ServeContent(path string, fileSystem http.FileSystem) error {
  250. f, err := fileSystem.Open(path)
  251. if err != nil {
  252. msg, code := toHTTPError(err)
  253. http.Error(ctx, msg, code)
  254. return nil
  255. }
  256. defer f.Close()
  257. d, err := f.Stat()
  258. if err != nil {
  259. msg, code := toHTTPError(err)
  260. http.Error(ctx, msg, code)
  261. return nil
  262. }
  263. if d.IsDir() {
  264. http.Error(ctx, http.StatusText(http.StatusForbidden), http.StatusForbidden)
  265. return nil
  266. }
  267. http.ServeContent(ctx, ctx.Req(), d.Name(), d.ModTime(), f)
  268. return nil
  269. }
  270. // ServeXml serves marshaled XML content from obj
  271. // Deprecated: use ServeXML instead
  272. func (ctx *Context) ServeXml(obj interface{}) error {
  273. return ctx.ServeXML(obj)
  274. }
  275. // ServeXML serves marshaled XML content from obj
  276. func (ctx *Context) ServeXML(obj interface{}) error {
  277. encoder := xml.NewEncoder(ctx)
  278. ctx.Header().Set("Content-Type", "application/xml; charset=UTF-8")
  279. err := encoder.Encode(obj)
  280. if err != nil {
  281. ctx.Header().Del("Content-Type")
  282. }
  283. return err
  284. }
  285. // ServeJson serves marshaled JSON content from obj
  286. // Deprecated: use ServeJSON instead
  287. func (ctx *Context) ServeJson(obj interface{}) error {
  288. return ctx.ServeJSON(obj)
  289. }
  290. // ServeJSON serves marshaled JSON content from obj
  291. func (ctx *Context) ServeJSON(obj interface{}) error {
  292. encoder := json.NewEncoder(ctx)
  293. ctx.Header().Set("Content-Type", "application/json; charset=UTF-8")
  294. err := encoder.Encode(obj)
  295. if err != nil {
  296. ctx.Header().Del("Content-Type")
  297. }
  298. return err
  299. }
  300. // Body returns body's content
  301. func (ctx *Context) Body() ([]byte, error) {
  302. if ctx.req.Body == nil {
  303. return []byte{}, nil
  304. }
  305. body, err := ioutil.ReadAll(ctx.req.Body)
  306. if err != nil {
  307. return nil, err
  308. }
  309. ctx.req.Body.Close()
  310. ctx.req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
  311. return body, nil
  312. }
  313. // DecodeJson decodes body as JSON format to obj
  314. // Deprecated: use DecodeJSON instead
  315. func (ctx *Context) DecodeJson(obj interface{}) error {
  316. return ctx.DecodeJSON(obj)
  317. }
  318. // DecodeJSON decodes body as JSON format to obj
  319. func (ctx *Context) DecodeJSON(obj interface{}) error {
  320. body, err := ctx.Body()
  321. if err != nil {
  322. return err
  323. }
  324. return json.Unmarshal(body, obj)
  325. }
  326. // DecodeXml decodes body as XML format to obj
  327. // Deprecated: use DecodeXML instead
  328. func (ctx *Context) DecodeXml(obj interface{}) error {
  329. return ctx.DecodeXML(obj)
  330. }
  331. // DecodeXML decodes body as XML format to obj
  332. func (ctx *Context) DecodeXML(obj interface{}) error {
  333. body, err := ctx.Body()
  334. if err != nil {
  335. return err
  336. }
  337. return xml.Unmarshal(body, obj)
  338. }
  339. // Download provides a locale file to http client
  340. func (ctx *Context) Download(fpath string) error {
  341. f, err := os.Open(fpath)
  342. if err != nil {
  343. return err
  344. }
  345. defer f.Close()
  346. fName := filepath.Base(fpath)
  347. ctx.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%v\"", fName))
  348. _, err = io.Copy(ctx, f)
  349. return err
  350. }
  351. // SaveToFile saves the HTTP post file form to local file path
  352. func (ctx *Context) SaveToFile(formName, savePath string) error {
  353. file, _, err := ctx.Req().FormFile(formName)
  354. if err != nil {
  355. return err
  356. }
  357. defer file.Close()
  358. f, err := os.OpenFile(savePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  359. if err != nil {
  360. return err
  361. }
  362. defer f.Close()
  363. _, err = io.Copy(f, file)
  364. return err
  365. }
  366. // Redirect redirects the request to another URL
  367. func (ctx *Context) Redirect(url string, status ...int) {
  368. s := http.StatusFound
  369. if len(status) > 0 {
  370. s = status[0]
  371. }
  372. http.Redirect(ctx.ResponseWriter, ctx.Req(), url, s)
  373. }
  374. // NotModified writes a 304 HTTP response
  375. func (ctx *Context) NotModified() {
  376. ctx.WriteHeader(http.StatusNotModified)
  377. }
  378. // Unauthorized writes a 401 HTTP response
  379. func (ctx *Context) Unauthorized() {
  380. ctx.Abort(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
  381. }
  382. // NotFound writes a 404 HTTP response
  383. func (ctx *Context) NotFound(message ...string) {
  384. if len(message) == 0 {
  385. ctx.Abort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
  386. return
  387. }
  388. ctx.Abort(http.StatusNotFound, message[0])
  389. }
  390. // Abort is a helper method that sends an HTTP header and an optional
  391. // body. It is useful for returning 4xx or 5xx errors.
  392. // Once it has been called, any return value from the handler will
  393. // not be written to the response.
  394. func (ctx *Context) Abort(status int, body ...string) {
  395. ctx.Result = Abort(status, body...)
  396. ctx.HandleError()
  397. }
  398. // Contexter describes an interface to set *Context
  399. type Contexter interface {
  400. SetContext(*Context)
  401. }
  402. // Ctx implements Contexter
  403. type Ctx struct {
  404. *Context
  405. }
  406. // SetContext set *Context to action struct
  407. func (c *Ctx) SetContext(ctx *Context) {
  408. c.Context = ctx
  409. }
  410. // Contexts returns a middleware to inject Context to action struct
  411. func Contexts() HandlerFunc {
  412. return func(ctx *Context) {
  413. if action := ctx.Action(); action != nil {
  414. if a, ok := action.(Contexter); ok {
  415. a.SetContext(ctx)
  416. }
  417. }
  418. ctx.Next()
  419. }
  420. }