Golang中如何设置带JSON响应头和正文的http.TimeoutHandler

Golang中如何设置带JSON响应头和正文的http.TimeoutHandler 你好,

有没有办法在使用 http.TimeoutHandler 时返回一个 JSON 响应体,并设置 application/json; charset=utf-8 内容类型头?

谢谢

2 回复

我不太了解 http.TimeoutHandler,但这里有一个使用 context 的想法:

func handlerWithTimeout(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	result := make(chan string)

	go func() {
		result <- slowOperation()
	}()

	select {
	case r := <-result:
		io.WriteString(w, r+"\n")
	case <-ctx.Done():
		w.Header().Add("content-type", "application/json")
		w.WriteHeader(http.StatusServiceUnavailable)

		d := map[string]string{"error": "timeout"}
		err := json.NewEncoder(w).Encode(d)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
		}
	}
}

func slowOperation() string {
	fmt.Println("Working on it...")

	time.Sleep(2 * time.Second)

	return "data"
}

更多关于Golang中如何设置带JSON响应头和正文的http.TimeoutHandler的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中,可以通过自定义超时响应处理器来实现带JSON响应头和正文的http.TimeoutHandler。以下是实现方法:

package main

import (
    "encoding/json"
    "net/http"
    "time"
)

// 自定义超时响应处理器
type jsonTimeoutHandler struct {
    handler http.Handler
    body    interface{}
    timeout time.Duration
}

func (h *jsonTimeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 创建带缓冲的ResponseWriter
    rw := &responseWriter{
        ResponseWriter: w,
        status:         http.StatusOK,
        body:           []byte{},
    }
    
    // 使用TimeoutHandler包装原始处理器
    timeoutHandler := http.TimeoutHandler(h.handler, h.timeout, "Request timeout")
    
    // 执行请求处理
    timeoutHandler.ServeHTTP(rw, r)
    
    // 如果发生超时(状态码503),返回自定义JSON响应
    if rw.status == http.StatusServiceUnavailable {
        w.Header().Set("Content-Type", "application/json; charset=utf-8")
        w.WriteHeader(http.StatusServiceUnavailable)
        
        json.NewEncoder(w).Encode(h.body)
        return
    }
    
    // 正常响应,写入原始响应
    for k, v := range rw.Header() {
        w.Header()[k] = v
    }
    w.WriteHeader(rw.status)
    w.Write(rw.body)
}

// 自定义ResponseWriter用于捕获响应
type responseWriter struct {
    http.ResponseWriter
    status int
    body   []byte
}

func (rw *responseWriter) WriteHeader(status int) {
    rw.status = status
    rw.ResponseWriter.WriteHeader(status)
}

func (rw *responseWriter) Write(b []byte) (int, error) {
    rw.body = append(rw.body, b...)
    return rw.ResponseWriter.Write(b)
}

// 使用示例
func main() {
    // 定义超时JSON响应体
    timeoutResponse := map[string]string{
        "error":   "Request timeout",
        "message": "The request took too long to process",
    }
    
    // 创建自定义超时处理器
    handler := &jsonTimeoutHandler{
        handler: http.HandlerFunc(yourHandler),
        body:    timeoutResponse,
        timeout: 5 * time.Second,
    }
    
    http.Handle("/", handler)
    http.ListenAndServe(":8080", nil)
}

func yourHandler(w http.ResponseWriter, r *http.Request) {
    // 模拟长时间处理
    time.Sleep(10 * time.Second)
    
    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    json.NewEncoder(w).Encode(map[string]string{
        "status": "success",
    })
}

更简洁的实现方式:

package main

import (
    "encoding/json"
    "net/http"
    "time"
)

func jsonTimeoutHandler(next http.Handler, timeout time.Duration, timeoutBody interface{}) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 创建通道用于检测超时
        done := make(chan bool, 1)
        
        go func() {
            next.ServeHTTP(w, r)
            done <- true
        }()
        
        select {
        case <-done:
            return
        case <-time.After(timeout):
            w.Header().Set("Content-Type", "application/json; charset=utf-8")
            w.WriteHeader(http.StatusServiceUnavailable)
            json.NewEncoder(w).Encode(timeoutBody)
        }
    })
}

// 使用示例
func main() {
    timeoutResponse := map[string]interface{}{
        "error":   "Request timeout",
        "code":    408,
        "timeout": 5,
    }
    
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(10 * time.Second)
        w.Header().Set("Content-Type", "application/json; charset=utf-8")
        json.NewEncoder(w).Encode(map[string]string{"result": "success"})
    })
    
    wrappedHandler := jsonTimeoutHandler(handler, 5*time.Second, timeoutResponse)
    
    http.Handle("/", wrappedHandler)
    http.ListenAndServe(":8080", nil)
}

直接包装http.TimeoutHandler的版本:

package main

import (
    "encoding/json"
    "net/http"
    "time"
)

func JSONTimeoutHandler(h http.Handler, dt time.Duration, msg string) http.Handler {
    timeoutResponse := map[string]string{
        "error": msg,
    }
    
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 使用标准TimeoutHandler
        timeoutHandler := http.TimeoutHandler(h, dt, msg)
        
        // 创建包装器来拦截响应
        rw := &jsonResponseWriter{
            ResponseWriter: w,
            originalWriter: w,
        }
        
        timeoutHandler.ServeHTTP(rw, r)
        
        // 如果是超时响应,返回JSON
        if rw.timeoutTriggered {
            w.Header().Set("Content-Type", "application/json; charset=utf-8")
            w.WriteHeader(http.StatusServiceUnavailable)
            json.NewEncoder(w).Encode(timeoutResponse)
        }
    })
}

type jsonResponseWriter struct {
    http.ResponseWriter
    originalWriter   http.ResponseWriter
    timeoutTriggered bool
}

func (w *jsonResponseWriter) WriteHeader(code int) {
    if code == http.StatusServiceUnavailable {
        w.timeoutTriggered = true
        return
    }
    w.originalWriter.WriteHeader(code)
}

func (w *jsonResponseWriter) Write(b []byte) (int, error) {
    if w.timeoutTriggered {
        return len(b), nil
    }
    return w.originalWriter.Write(b)
}

// 使用示例
func main() {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(10 * time.Second)
        w.Header().Set("Content-Type", "application/json; charset=utf-8")
        json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
    })
    
    wrappedHandler := JSONTimeoutHandler(handler, 5*time.Second, "Request timeout")
    
    http.Handle("/", wrappedHandler)
    http.ListenAndServe(":8080", nil)
}
回到顶部