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.

252 lines
5.2KB

  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. "encoding/json"
  7. "encoding/xml"
  8. "net/http"
  9. "reflect"
  10. )
  11. // StatusResult describes http response
  12. type StatusResult struct {
  13. Code int
  14. Result interface{}
  15. }
  16. // enumerate all the return response types
  17. const (
  18. autoResponse = iota
  19. jsonResponse
  20. xmlResponse
  21. )
  22. // ResponseTyper describes reponse type
  23. type ResponseTyper interface {
  24. ResponseType() int
  25. }
  26. // Json describes return JSON type
  27. // Deprecated: use JSON instead
  28. type Json struct{}
  29. // ResponseType implementes ResponseTyper
  30. func (Json) ResponseType() int {
  31. return jsonResponse
  32. }
  33. // JSON describes return JSON type
  34. type JSON struct{}
  35. // ResponseType implementes ResponseTyper
  36. func (JSON) ResponseType() int {
  37. return jsonResponse
  38. }
  39. // Xml descirbes return XML type
  40. // Deprecated: use XML instead
  41. type Xml struct{}
  42. // ResponseType implementes ResponseTyper
  43. func (Xml) ResponseType() int {
  44. return xmlResponse
  45. }
  46. // XML descirbes return XML type
  47. type XML struct{}
  48. // ResponseType implementes ResponseTyper
  49. func (XML) ResponseType() int {
  50. return xmlResponse
  51. }
  52. func isNil(a interface{}) bool {
  53. if a == nil {
  54. return true
  55. }
  56. aa := reflect.ValueOf(a)
  57. return !aa.IsValid() || (aa.Type().Kind() == reflect.Ptr && aa.IsNil())
  58. }
  59. // XMLError describes return xml error
  60. type XMLError struct {
  61. XMLName xml.Name `xml:"err"`
  62. Content string `xml:"content"`
  63. }
  64. // XMLString describes return xml string
  65. type XMLString struct {
  66. XMLName xml.Name `xml:"string"`
  67. Content string `xml:"content"`
  68. }
  69. // Return returns a tango middleware to handler return values
  70. func Return() HandlerFunc {
  71. return func(ctx *Context) {
  72. var rt int
  73. action := ctx.Action()
  74. if action != nil {
  75. if i, ok := action.(ResponseTyper); ok {
  76. rt = i.ResponseType()
  77. }
  78. }
  79. ctx.Next()
  80. // if no route match or has been write, then return
  81. if action == nil || ctx.Written() {
  82. return
  83. }
  84. // if there is no return value or return nil
  85. if isNil(ctx.Result) {
  86. // then we return blank page
  87. ctx.Result = ""
  88. }
  89. var result = ctx.Result
  90. var statusCode = 0
  91. if res, ok := ctx.Result.(*StatusResult); ok {
  92. statusCode = res.Code
  93. result = res.Result
  94. }
  95. if rt == jsonResponse {
  96. encoder := json.NewEncoder(ctx)
  97. if len(ctx.Header().Get("Content-Type")) <= 0 {
  98. ctx.Header().Set("Content-Type", "application/json; charset=UTF-8")
  99. }
  100. switch res := result.(type) {
  101. case AbortError:
  102. if statusCode == 0 {
  103. statusCode = res.Code()
  104. }
  105. ctx.WriteHeader(statusCode)
  106. encoder.Encode(map[string]string{
  107. "err": res.Error(),
  108. })
  109. case ErrorWithCode:
  110. if statusCode == 0 {
  111. statusCode = http.StatusOK
  112. }
  113. ctx.WriteHeader(statusCode)
  114. encoder.Encode(map[string]interface{}{
  115. "err": res.Error(),
  116. "err_code": res.ErrorCode(),
  117. })
  118. case error:
  119. if statusCode == 0 {
  120. statusCode = http.StatusOK
  121. }
  122. ctx.WriteHeader(statusCode)
  123. encoder.Encode(map[string]string{
  124. "err": res.Error(),
  125. })
  126. case string:
  127. if statusCode == 0 {
  128. statusCode = http.StatusOK
  129. }
  130. ctx.WriteHeader(statusCode)
  131. encoder.Encode(map[string]string{
  132. "content": res,
  133. })
  134. case []byte:
  135. if statusCode == 0 {
  136. statusCode = http.StatusOK
  137. }
  138. ctx.WriteHeader(statusCode)
  139. encoder.Encode(map[string]string{
  140. "content": string(res),
  141. })
  142. default:
  143. if statusCode == 0 {
  144. statusCode = http.StatusOK
  145. }
  146. ctx.WriteHeader(statusCode)
  147. err := encoder.Encode(result)
  148. if err != nil {
  149. ctx.Result = err
  150. encoder.Encode(map[string]string{
  151. "err": err.Error(),
  152. })
  153. }
  154. }
  155. return
  156. } else if rt == xmlResponse {
  157. encoder := xml.NewEncoder(ctx)
  158. if len(ctx.Header().Get("Content-Type")) <= 0 {
  159. ctx.Header().Set("Content-Type", "application/xml; charset=UTF-8")
  160. }
  161. switch res := result.(type) {
  162. case AbortError:
  163. if statusCode == 0 {
  164. statusCode = res.Code()
  165. }
  166. ctx.WriteHeader(statusCode)
  167. encoder.Encode(XMLError{
  168. Content: res.Error(),
  169. })
  170. case error:
  171. if statusCode == 0 {
  172. statusCode = http.StatusOK
  173. }
  174. ctx.WriteHeader(statusCode)
  175. encoder.Encode(XMLError{
  176. Content: res.Error(),
  177. })
  178. case string:
  179. if statusCode == 0 {
  180. statusCode = http.StatusOK
  181. }
  182. ctx.WriteHeader(statusCode)
  183. encoder.Encode(XMLString{
  184. Content: res,
  185. })
  186. case []byte:
  187. if statusCode == 0 {
  188. statusCode = http.StatusOK
  189. }
  190. ctx.WriteHeader(statusCode)
  191. encoder.Encode(XMLString{
  192. Content: string(res),
  193. })
  194. default:
  195. if statusCode == 0 {
  196. statusCode = http.StatusOK
  197. }
  198. ctx.WriteHeader(statusCode)
  199. err := encoder.Encode(result)
  200. if err != nil {
  201. ctx.Result = err
  202. encoder.Encode(XMLError{
  203. Content: err.Error(),
  204. })
  205. }
  206. }
  207. return
  208. }
  209. switch res := result.(type) {
  210. case AbortError, error:
  211. ctx.HandleError()
  212. case []byte:
  213. if statusCode == 0 {
  214. statusCode = http.StatusOK
  215. }
  216. ctx.WriteHeader(statusCode)
  217. ctx.Write(res)
  218. case string:
  219. if statusCode == 0 {
  220. statusCode = http.StatusOK
  221. }
  222. ctx.WriteHeader(statusCode)
  223. ctx.WriteString(res)
  224. }
  225. }
  226. }