Sederhanakan penulisan handler HTTP di Golang

Saat memproses permintaan HTTP yang masuk, sejumlah besar tindakan diperlukan, seperti:


  • Mencatat permintaan HTTP yang masuk
  • Validasi metode HTTP
  • Otentikasi (dasar, MS AD, ...)
  • Validasi token (jika perlu)
  • Membaca isi permintaan yang masuk
  • Membaca tajuk permintaan yang masuk
  • Sebenarnya memproses permintaan dan menghasilkan respons
  • Instal HSTS Strict-Transport-Security
  • Mengatur Konten-Jenis untuk respons keluar
  • Mencatat respons HTTP keluar
  • Rekam tajuk dari respons keluar
  • Rekam Badan Respons Keluar
  • Kesalahan dalam menangani dan mencatat
  • Tunda pemrosesan pemulihan untuk pemulihan setelah kemungkinan panik

Sebagian besar dari tindakan ini adalah tipikal dan tidak tergantung pada jenis permintaan dan prosesnya.
Untuk operasi yang produktif, untuk setiap tindakan perlu menyediakan penanganan kesalahan dan pencatatan.


Mengulangi semua ini di setiap penangan HTTP sangat tidak efisien.


Bahkan jika Anda memasukkan semua kode ke dalam subfungsi yang terpisah, Anda masih mendapatkan sekitar 80-100 baris kode untuk setiap pengendali HTTP tanpa memperhitungkan pemrosesan permintaan yang sebenarnya dan pembentukan respons .


Berikut ini menjelaskan pendekatan yang saya gunakan untuk menyederhanakan penulisan penangan HTTP di Golang tanpa menggunakan generator kode dan perpustakaan pihak ketiga.


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, error) - fungsi pemrosesan itu sendiri, ia menerima buffer permintaan masuk dan nomor permintaan HTTP unik (untuk tujuan logging), mengembalikan buffer respons keluar siap , tajuk respons keluar, status HTTP, dan kesalahan.

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