在框架里,我们需要有更强大的 Context,除了可以控制超时之外,常用的功能比如获取请求、返回结果、实现标准库的 Context 接口,也都要有。

    自己封装的 Context 最终需要提供四类功能函数:
    base 封装基本的函数功能,比如获取 http.Request 结构
    context 实现标准 Context 接口
    request 封装了 http.Request 的对外接口
    response 封装了 http.ResponseWriter 对外接口

    边界场景到这里,我们的超时逻辑设置就结束且生效了。但是,这样的代码逻辑只能算是及格,为什么这么说呢?因为它并没有覆盖所有的场景。我们的代码逻辑要再严谨一些,把边界场景也考虑进来。这里有两种可能:
    异常事件、超时事件触发时,需要往 responseWriter 中写入信息,这个时候如果有其他 Goroutine 也要操作 responseWriter,会不会导致 responseWriter 中的信息出现乱序?
    超时事件触发结束之后,已经往 responseWriter 中写入信息了,这个时候如果有其他 Goroutine 也要操作 responseWriter, 会不会导致 responseWriter 中的信息重复写入?

    image.png

    1. package basecontext
    2. import (
    3. "bytes"
    4. "context"
    5. "encoding/json"
    6. "errors"
    7. "io/ioutil"
    8. "net/http"
    9. "strconv"
    10. "sync"
    11. "time"
    12. )
    13. type Context struct {
    14. request *http.Request
    15. responseWriter http.ResponseWriter
    16. ctx context.Context
    17. handler ControllerHandler
    18. //是否超时标记
    19. hasTimeout bool
    20. //写保护机制
    21. writerMux *sync.Mutex
    22. }
    23. func NewContext(r *http.Request, w http.ResponseWriter) *Context{
    24. return &Context{
    25. request: r,
    26. responseWriter: w,
    27. ctx: r.Context(),
    28. handler: nil,
    29. hasTimeout: false,
    30. writerMux: &sync.Mutex{},
    31. }
    32. }
    33. // #region base function
    34. func (ctx *Context) WriterMux() *sync.Mutex {
    35. return ctx.writerMux
    36. }
    37. func (ctx *Context) GetRequest() *http.Request {
    38. return ctx.request
    39. }
    40. func (ctx *Context) GetResponse() http.ResponseWriter {
    41. return ctx.responseWriter
    42. }
    43. func (ctx *Context) SetHasTimeout() {
    44. ctx.hasTimeout = true
    45. }
    46. func (ctx *Context) HasTimeout() bool {
    47. return ctx.hasTimeout
    48. }
    49. // #endregion
    50. func (ctx *Context) BaseContext() context.Context {
    51. return ctx.request.Context()
    52. }
    53. // #region implement context.Context
    54. func (ctx *Context) Deadline() (deadline time.Time, ok bool) {
    55. return ctx.BaseContext().Deadline()
    56. }
    57. func (ctx *Context) Done() <-chan struct{} {
    58. return ctx.BaseContext().Done()
    59. }
    60. func (ctx *Context) Err() error {
    61. return ctx.BaseContext().Err()
    62. }
    63. func (ctx *Context) Value(key interface{}) interface{} {
    64. return ctx.BaseContext().Value(key)
    65. }
    66. // #endregion
    67. // #region query url
    68. func (ctx *Context) QueryInt(key string, def int) int {
    69. params := ctx.QueryAll();
    70. if vals, ok := params[key]; ok {
    71. len := len(vals)
    72. if len > 0 {
    73. intval, err := strconv.Atoi(vals[len-1])
    74. if err != nil{
    75. return def
    76. }
    77. return intval
    78. }
    79. }
    80. return def
    81. }
    82. func (ctx *Context) QueryString(key string, def string) string{
    83. params := ctx.QueryAll();
    84. if vals, ok := params[key]; ok {
    85. len := len(vals)
    86. if len > 0 {
    87. return vals[len - 1]
    88. }
    89. }
    90. return def
    91. }
    92. func (ctx *Context) QueryArray(key string, def []string) []string{
    93. params := ctx.QueryAll();
    94. if vals, ok := params[key]; ok {
    95. return vals
    96. }
    97. return def
    98. }
    99. func (ctx *Context) QueryAll() map[string][]string{
    100. if ctx.request != nil{
    101. return map[string][]string(ctx.request.URL.Query())
    102. }
    103. return map[string][]string{}
    104. }
    105. // #endregion
    106. // #region form post
    107. func (ctx *Context) FormInt(key string, def int) int {
    108. params := ctx.FormAll()
    109. if vals, ok := params[key]; ok {
    110. len := len(vals)
    111. if len > 0 {
    112. intval, err := strconv.Atoi(vals[len-1])
    113. if err != nil {
    114. return def
    115. }
    116. return intval
    117. }
    118. }
    119. return def
    120. }
    121. func (ctx *Context) FormString(key string, def string) string {
    122. params := ctx.FormAll()
    123. if vals, ok := params[key]; ok {
    124. len := len(vals)
    125. if len > 0 {
    126. return vals[len-1]
    127. }
    128. }
    129. return def
    130. }
    131. func (ctx *Context) FormArray(key string, def []string) []string{
    132. params := ctx.QueryAll();
    133. if vals, ok := params[key]; ok {
    134. return vals
    135. }
    136. return def
    137. }
    138. func (ctx *Context) FormAll() map[string][]string{
    139. if ctx.request != nil{
    140. return map[string][]string(ctx.request.PostForm)
    141. }
    142. return map[string][]string{}
    143. }
    144. // #endregion
    145. // #region application/json post
    146. func (ctx *Context) BindJson(obj interface{}) error {
    147. if ctx.request != nil {
    148. body, err := ioutil.ReadAll(ctx.request.Body)
    149. if err != nil {
    150. return err
    151. }
    152. ctx.request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
    153. err = json.Unmarshal(body, obj)
    154. if err != nil {
    155. return err
    156. }
    157. } else {
    158. return errors.New("ctx.request empty")
    159. }
    160. return nil
    161. }
    162. // #endregion
    163. // #region response
    164. func (ctx *Context) Json(status int, obj interface{}) error {
    165. if ctx.HasTimeout() {
    166. return nil
    167. }
    168. ctx.responseWriter.Header().Set("Content-Type", "application/json")
    169. ctx.responseWriter.WriteHeader(status)
    170. byt, err := json.Marshal(obj)
    171. if err != nil {
    172. ctx.responseWriter.WriteHeader(500)
    173. return err
    174. }
    175. ctx.responseWriter.Write(byt)
    176. return nil
    177. }
    178. func (ctx *Context) HTML(status int, obj interface{}, template string) error {
    179. return nil
    180. }
    181. func (ctx *Context) Text(status int, obj string) error {
    182. return nil
    183. }
    1. package basecontext
    2. import (
    3. "context"
    4. "encoding/json"
    5. "fmt"
    6. "log"
    7. "net/http"
    8. "strconv"
    9. "time"
    10. )
    11. type ControllerHandler func(c *Context) error
    12. func FooControllerHandler(c *Context) error {
    13. finish := make(chan struct{}, 1)
    14. panicChan := make(chan interface{}, 1)
    15. durationCtx, cancel := context.WithTimeout(c.BaseContext(), time.Duration(1*time.Second))
    16. defer cancel()
    17. // mu := sync.Mutex{}
    18. go func() {
    19. defer func() {
    20. if p := recover(); p != nil {
    21. panicChan <- p
    22. }
    23. }()
    24. // Do real action
    25. time.Sleep(10 * time.Second)
    26. c.Json(200, "ok")
    27. finish <- struct{}{}
    28. }()
    29. select {
    30. case p := <-panicChan:
    31. c.WriterMux().Lock()
    32. defer c.WriterMux().Unlock()
    33. log.Println(p)
    34. c.Json(500, "panic")
    35. case <-finish:
    36. fmt.Println("finish")
    37. case <-durationCtx.Done():
    38. c.WriterMux().Lock()
    39. defer c.WriterMux().Unlock()
    40. c.Json(500, "time out")
    41. c.SetHasTimeout()
    42. }
    43. return nil
    44. }
    45. func Foo(request *http.Request, response http.ResponseWriter) {
    46. obj := map[string]interface{}{
    47. "errno": 50001,
    48. "errmsg": "inner error",
    49. "data": nil,
    50. }
    51. response.Header().Set("Content-Type", "application/json")
    52. foo := request.PostFormValue("foo")
    53. if foo == "" {
    54. foo = "10"
    55. }
    56. fooInt, err := strconv.Atoi(foo)
    57. if err != nil {
    58. response.WriteHeader(500)
    59. return
    60. }
    61. obj["data"] = fooInt
    62. byt, err := json.Marshal(obj)
    63. if err != nil {
    64. response.WriteHeader(500)
    65. return
    66. }
    67. response.WriteHeader(200)
    68. response.Write(byt)
    69. return
    70. }
    71. // func Foo2(ctx *framework.Context) error {
    72. // obj := map[string]interface{}{
    73. // "errno": 50001,
    74. // "errmsg": "inner error",
    75. // "data": nil,
    76. // }
    77. // fooInt := ctx.FormInt("foo", 10)
    78. // obj["data"] = fooInt
    79. // return ctx.Json(http.StatusOK, obj)
    80. // }
    81. // func Foo3(ctx *framework.Context) error {
    82. // rdb := redis.NewClient(&redis.Options{
    83. // Addr: "localhost:6379",
    84. // Password: "", // no password set
    85. // DB: 0, // use default DB
    86. // })
    87. // return rdb.Set(ctx, "key", "value", 0).Err()
    88. // }
    1. package basecontext
    2. import (
    3. "log"
    4. "net/http"
    5. )
    6. type Core struct {
    7. router map[string]ControllerHandler
    8. }
    9. func NewCore() *Core {
    10. return &Core{router: map[string]ControllerHandler{}}
    11. }
    12. func (c *Core) Get(url string, handler ControllerHandler) {
    13. c.router[url] = handler
    14. }
    15. func (c *Core) ServeHTTP(response http.ResponseWriter, request *http.Request) {
    16. log.Panicln("core.serveHTTP")
    17. ctx := NewContext(request, response)
    18. // 一个简单的路由选择器,这里直接写死为测试路由foo
    19. router := c.router["foo"]
    20. if router == nil {
    21. return
    22. }
    23. log.Println("core.router")
    24. router(ctx)
    25. }
    1. package basecontext
    2. func RegisterRouter(core *Core) {
    3. // core.Get("foo", framework.TimeoutHandler(FooControllerHandler, time.Second*1))
    4. core.Get("foo", FooControllerHandler)
    5. }
    1. package example
    2. import (
    3. "github.com/programmerug/fibergorm/basecontext"
    4. "net/http"
    5. )
    6. func test() {
    7. core := basecontext.NewCore()
    8. basecontext.RegisterRouter(core)
    9. server := &http.Server{
    10. Handler: core,
    11. Addr: ":8888",
    12. }
    13. server.ListenAndServe()
    14. }