SoFunction
Updated on 2025-03-05

Analysis of the implementation principle of Golang HTTP service timeout control

Introduction

becausePrevious articleAsked, every time a request comes, one will be issuedgoroutinueThe result may be a tree-shaped request graph. If a timeout occurs during execution of the node below, coroutines will accumulate, so timeout control is necessary. Generally, the implementation is designed by a top-level Context for top-down transmission, so it can avoid multiple execution exceptions from one place. I will not explain the too many details of Context here. If necessary, I will introduce a separate introduction to Context. Let's take a look at how the source code is designed:

Context

// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
  // When the Context is cancelled or reaches deadline, return a closed channel  Done() <-chan struct{}
}
// Function handletype CancelFunc func()

The two most important points of design intention are how to actively end the downstream, and the other is how to notify the upstream to end the downstream.

The former usesCancelFuncThe latter usesDone, the latter needs to be monitored continuously, so the channel's return value is used for monitoring

//Create Exit Contextfunc WithCancel(parent Context)(ctx Context,cancel CancelFunc){}
//Create a Context with a timeout timefunc WithTimeout(parent Context,timeout )(Context,CancelFunc){}
//Create a Context with a deadlinefunc WithDeadline(parent Context,d )(Context,CancelFunc){}

WithCancel/WithTimeout/WithDeadlineThe end notification is automatically triggered through a timer, which means that aDonechild node and return the child node'sCancelFuncFunction handle.

Encapsulate custom Context

You can define your own Context, which first has the most basic request and response parameters. Finally, because you think about the writer that concurrently writes resposne, you need to add lock member variables and timeout flags to prevent repeated writes.

package framework
import (
	"context"
	"encoding/json"
	"net/http"
	"sync"
)
type Context struct {
	Request        *
	ResponseWriter 
	hasTimeOut     bool // Whether to timeout flag bit	writerMux      *
}
func NewContext()*Context{
	return &Context{}
}
func (ctx *Context) BaseContext()  {
	return ()
}
func (ctx *Context) Done() <-chan struct{} {
	return ().Done()
}
func (ctx *Context)SetHasTimeOut(){
	=true
}
func (ctx *Context)HasTimeOut()bool{
	return 
}
// Method to encapsulate a Json by yourselffunc (ctx *Context) Json(status int, obj interface{}) (err error) {
	if (){
		return nil
	}
	bytes, err := (obj)
	(status)
	_, err = (bytes)
	return
}
// Expose the lock to the outsidefunc (ctx *Context) WriterMux() * {
	return 
}
// Unified processor Controller methodtype ControllerHandler func(c *Context) error

The business method uses the Context encapsulated by itself, which takes into account timeout control, concurrent read and write, and processing panic

package main
import (
	"context"
	"fmt"
	"testdemo1/coredemo/framework"
	"time"
)
func FooController(ctx *) error {
	durationCtx, cancel := ((), )
	defer cancel()
	finish := make(chan struct{}, 1)
	panicChan := make(chan interface{}, 1)
	go func() {
		defer func() {
			if p := recover(); p != nil {
				panicChan <- p
			}
		}()
		( * 10)
		finish <- struct{}{}
	}()
	select {
	case p := <-panicChan: // panic
	("panic:",p)
	    ().Lock()  // Prevent the messaging of multiple coroutines from being out of order		defer ().Unlock()
		(500, "panic")
	case <-finish: // Exit normally		(200, "ok")
		("finish")
	case <-(): // Timeout event		().Lock()
		defer ().Unlock()
		(500, "timed out")
		()  // Prevent multiple coroutines from repeatedly writing timeout logs	}
	return nil
}

The serverHandler class can handle the request logic first, and you can register the corresponding mapper and method

package framework
import (
	"net/http"
)
type Core struct {
	RouterMap map[string]ControllerHandler
}
func (c Core) ServeHTTP(writer , request *) {
	(writer, request)
}
func NewCore() *Core {
	return &Core{
		RouterMap:make(map[string]ControllerHandler,0),
	}
}
// Register Get methodfunc (c *Core) Get(pattern string, handler ControllerHandler) {
	["get"+"-"+pattern]=handler
}
// Register Post methodfunc (c *Core) Post(pattern string, handler ControllerHandler) {
	["post"+"-"+pattern]=handler
}

Register the router unified management and register it into our corresponding http method into our request logic class

package main
import "testdemo1/coredemo/framework"
func registerRouter(core *){
	// Set up the controller	("foo",FooController)
}

Finally, the main program executes http service listening and calls to register the initial router! Pass it into our customizedContext

package main
import (
	"log"
	"net/http"
	"testdemo1/coredemo/framework"
)
func main() {
	server:=&{Addr: ":8080",Handler: ()}
	// Register router	registerRouter(())
	err := ()
    if err!=nil{
    	(err)
	}
}

This article ends here! You can implement it yourself and experience it. The actual source code packaging is similar to gin~

usNext articlegoodbye

This is the article about the analysis of the implementation principle of Golang HTTP service timeout control. For more related content on Golang HTTP service timeout control, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!