Simplifiez l'Ă©criture des gestionnaires HTTP dans Golang

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_handler



.


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)
                }
                //      HTTP
                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) {
    //    HTTP  
    _ = 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

    //    HTTP 
    reqID := GetNextRequestID()

    //   HTTP 
    if s.logger != nil {
        _ = s.logger.LogHTTPInRequest(s.tx, r, reqID) //   HTTP ,   ,    
        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
    }

    //       JWT ,       
    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)

        //    HTTP Basic Authentication
        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
        }
    }

    //   JWT -  
    if s.cfg.UseJWT {
        mylog.PrintfDebugMsg("JWT is on. Check JSON web token: reqID", reqID)

        //  token  requests cookies
        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
        }

        //  JWT  token
        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
    }

    // use HSTS Strict-Transport-Security
    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) //   HTTP ,   ,    
    }

    //   
    mylog.PrintfDebugMsg("Set HTTP response headers: reqID", reqID)
    if header != nil {
        for key, h := range header {
            w.Header().Set(key, h)
        }
    }

    //  HTTP  
    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
}

All Articles