Lors du traitement d'une demande HTTP entrante, un grand nombre d'actions sont requises, telles que:
- Consignation d'une requĂȘte HTTP entrante
- Validation de la méthode HTTP
- Authentification (basique, MS AD, ...)
- Validation de jeton (si nécessaire)
- Lecture du corps d'une demande entrante
- Lecture de l'en-tĂȘte de la demande entrante
- Traitement effectif de la demande et génération d'une réponse
- Installer HSTS Strict-Transport-Security
- Définition du type de contenu pour la réponse sortante
- Consignation de la réponse HTTP sortante
- Enregistrer l'en-tĂȘte de la rĂ©ponse sortante
- Enregistrer le corps de réponse sortant
- Gestion des erreurs et journalisation
- Différer le traitement de récupération pour récupération aprÚs une éventuelle panique
La plupart de ces actions sont typiques et ne dépendent pas du type de demande et de son traitement.
Pour un fonctionnement productif, pour chaque action, il est nécessaire de prévoir la gestion et la journalisation des erreurs.
RĂ©pĂ©ter tout cela dans chaque gestionnaire HTTP est extrĂȘmement inefficace.
MĂȘme si vous mettez tout le code dans des sous-fonctions distinctes, vous obtenez toujours environ 80 Ă 100 lignes de code pour chaque gestionnaire HTTP sans prendre en compte le traitement rĂ©el de la demande et la formation de la rĂ©ponse .
Ce qui suit décrit l'approche que j'utilise pour simplifier l'écriture de gestionnaires HTTP dans Golang sans utiliser de générateurs de code et de bibliothÚques tierces.
backend Golang
.
, , â , - .
HTTP
UML HTTP EchoHandler.
, , :
- defer . UML â RecoverWrap.func1, .
- . HTTP handler. UML Process â .
- HTTP handler. UML EchoHandler.func1 â .

.
HTTP EchoHandler, "" .
, EchoHandler, ( RecoverWrap), EchoHandler.
router.HandleFunc("/echo", service.RecoverWrap(http.HandlerFunc(service.EchoHandler))).Methods("GET")
RecoverWrap .
defer func() EchoHandler.
func (s *Service) RecoverWrap(handlerFunc http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
var myerr error
r := recover()
if r != nil {
msg := "HTTP Handler recover from panic"
switch t := r.(type) {
case string:
myerr = myerror.New("8888", msg, t)
case error:
myerr = myerror.WithCause("8888", msg, t)
default:
myerr = myerror.New("8888", msg)
}
s.processError(myerr, w, http.StatusInternalServerError, 0)
}
}()
if handlerFunc != nil {
handlerFunc(w, r)
}
})
}
, EchoHandler. , HTTP Process .
func (s *Service) EchoHandler(w http.ResponseWriter, r *http.Request) {
_ = s.Process("POST", w, r, func(requestBuf []byte, reqID uint64) ([]byte, Header, int, error) {
header := Header{}
for key := range r.Header {
header[key] = r.Header.Get(key)
}
return requestBuf, header, http.StatusOK, nil
})
}
HTTP Process. :
- method string â HTTP HTTP
- w http.ResponseWriter, r *http.Request â
- fn func (requestBuf [] octet, reqID uint64) ([] octet, en-tĂȘte, int, erreur) - la fonction de traitement elle-mĂȘme, elle reçoit un tampon de demande entrant et un numĂ©ro de demande HTTP unique (Ă des fins de journalisation), renvoie un tampon de rĂ©ponse sortant prĂ©parĂ© , en-tĂȘte de la rĂ©ponse sortante, Ă©tat HTTP et erreur.
func (s *Service) Process(method string, w http.ResponseWriter, r *http.Request, fn func(requestBuf []byte, reqID uint64) ([]byte, Header, int, error)) error {
var myerr error
reqID := GetNextRequestID()
if s.logger != nil {
_ = s.logger.LogHTTPInRequest(s.tx, r, reqID)
mylog.PrintfDebugMsg("Logging HTTP in request: reqID", reqID)
}
mylog.PrintfDebugMsg("Check allowed HTTP method: reqID, request.Method, method", reqID, r.Method, method)
if r.Method != method {
myerr = myerror.New("8000", "HTTP method is not allowed: reqID, request.Method, method", reqID, r.Method, method)
mylog.PrintfErrorInfo(myerr)
return myerr
}
mylog.PrintfDebugMsg("Check authentication method: reqID, AuthType", reqID, s.cfg.AuthType)
if (s.cfg.AuthType == "INTERNAL" || s.cfg.AuthType == "MSAD") && !s.cfg.UseJWT {
mylog.PrintfDebugMsg("JWT is of. Need Authentication: reqID", reqID)
username, password, ok := r.BasicAuth()
if !ok {
myerr := myerror.New("8004", "Header 'Authorization' is not set")
mylog.PrintfErrorInfo(myerr)
return myerr
}
mylog.PrintfDebugMsg("Get Authorization header: username", username)
if myerr = s.checkAuthentication(username, password); myerr != nil {
mylog.PrintfErrorInfo(myerr)
return myerr
}
}
if s.cfg.UseJWT {
mylog.PrintfDebugMsg("JWT is on. Check JSON web token: reqID", reqID)
cookie, err := r.Cookie("token")
if err != nil {
myerr := myerror.WithCause("8005", "JWT token does not present in Cookie. You have to authorize first.", err)
mylog.PrintfErrorInfo(myerr)
return myerr
}
if myerr = myjwt.CheckJWTFromCookie(cookie, s.cfg.JwtKey); myerr != nil {
mylog.PrintfErrorInfo(myerr)
return myerr
}
}
mylog.PrintfDebugMsg("Reading request body: reqID", reqID)
requestBuf, err := ioutil.ReadAll(r.Body)
if err != nil {
myerr = myerror.WithCause("8001", "Failed to read HTTP body: reqID", err, reqID)
mylog.PrintfErrorInfo(myerr)
return myerr
}
mylog.PrintfDebugMsg("Read request body: reqID, len(body)", reqID, len(requestBuf))
mylog.PrintfDebugMsg("Calling external function handler: reqID, function", reqID, fn)
responseBuf, header, status, myerr := fn(requestBuf, reqID)
if myerr != nil {
mylog.PrintfErrorInfo(myerr)
return myerr
}
if s.cfg.UseHSTS {
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
}
if s.logger != nil {
mylog.PrintfDebugMsg("Logging HTTP out response: reqID", reqID)
_ = s.logger.LogHTTPOutResponse(s.tx, header, responseBuf, status, reqID)
}
mylog.PrintfDebugMsg("Set HTTP response headers: reqID", reqID)
if header != nil {
for key, h := range header {
w.Header().Set(key, h)
}
}
mylog.PrintfDebugMsg("Set HTTP response status: reqID, Status", reqID, http.StatusText(status))
w.WriteHeader(status)
if responseBuf != nil && len(responseBuf) > 0 {
mylog.PrintfDebugMsg("Writing HTTP response body: reqID, len(body)", reqID, len(responseBuf))
respWrittenLen, err := w.Write(responseBuf)
if err != nil {
myerr = myerror.WithCause("8002", "Failed to write HTTP repsonse: reqID", err)
mylog.PrintfErrorInfo(myerr)
return myerr
}
mylog.PrintfDebugMsg("Written HTTP response: reqID, len(body)", reqID, respWrittenLen)
}
return nil
}