Vereinfachen Sie das Schreiben von HTTP-Handlern in Golang

Bei der Verarbeitung einer eingehenden HTTP-Anforderung ist eine große Anzahl von Aktionen erforderlich, z.


  • Protokollierung einer eingehenden HTTP-Anforderung
  • Validierung der HTTP-Methode
  • Authentifizierung (Basic, MS AD, ...)
  • Token-Validierung (falls erforderlich)
  • Lesen des Textes einer eingehenden Anfrage
  • Lesen des Headers der eingehenden Anfrage
  • Die Anfrage tatsächlich verarbeiten und eine Antwort generieren
  • Installieren Sie HSTS Strict-Transport-Security
  • Festlegen des Inhaltstyps für die ausgehende Antwort
  • Protokollierung der ausgehenden HTTP-Antwort
  • Kopfzeile der ausgehenden Antwort aufzeichnen
  • Ausgehenden Antworttext aufzeichnen
  • Fehlerbehandlung und Protokollierung
  • Verschieben Sie die Wiederherstellungsverarbeitung für die Wiederherstellung nach einer möglichen Panik

Die meisten dieser Aktionen sind typisch und hängen nicht von der Art der Anforderung und ihrer Verarbeitung ab.
Für einen produktiven Betrieb ist es erforderlich, für jede Aktion eine Fehlerbehandlung und -protokollierung bereitzustellen.


Das alles in jedem HTTP-Handler zu wiederholen, ist äußerst ineffizient.


Selbst wenn Sie den gesamten Code in separate Unterfunktionen einfügen, erhalten Sie für jeden HTTP-Handler etwa 80 bis 100 Codezeilen, ohne die tatsächliche Verarbeitung der Anforderung und die Bildung der Antwort zu berücksichtigen .


Im Folgenden wird der Ansatz beschrieben, mit dem ich das Schreiben von HTTP-Handlern in Golang ohne die Verwendung von Codegeneratoren und Bibliotheken von Drittanbietern vereinfache.


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 [] Byte, reqID uint64) ([] Byte, Header, int, Fehler) - Die Verarbeitungsfunktion selbst empfängt einen eingehenden Anforderungspuffer und eine eindeutige HTTP-Anforderungsnummer (für Protokollierungszwecke) und gibt einen vorbereiteten ausgehenden Antwortpuffer zurück , Header der ausgehenden Antwort, HTTP-Status und Fehler.

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