عند معالجة طلب HTTP الوارد ، يلزم عدد كبير من الإجراءات ، مثل:
- تسجيل طلب HTTP وارد
- التحقق من صحة طريقة HTTP
- المصادقة (أساسية ، MS AD ، ...)
- التحقق من الرمز (إذا لزم الأمر)
- قراءة نص الطلب الوارد
- قراءة رأس الطلب الوارد
- في الواقع معالجة الطلب وتوليد رد
- قم بتثبيت HSTS Strict-Transport-Security
- تعيين نوع المحتوى للاستجابة الصادرة
- تسجيل استجابة HTTP الصادرة
- رأس سجل الاستجابة الصادرة
- سجل هيئة الاستجابة الصادرة
- معالجة الخطأ وتسجيل
- تأجيل معالجة الاسترداد للتعافي بعد الذعر المحتمل
معظم هذه الإجراءات نموذجية ولا تعتمد على نوع الطلب ومعالجته.
للتشغيل المنتج ، من الضروري لكل معالجة توفير معالجة الأخطاء وتسجيل الدخول.
تكرار كل هذا في كل معالج HTTP غير فعال للغاية.
حتى إذا قمت بوضع جميع التعليمات البرمجية في وظائف فرعية منفصلة ، فلا يزال بإمكانك الحصول على حوالي 80-100 سطر من التعليمات البرمجية لكل معالج HTTP دون مراعاة المعالجة الفعلية للطلب وتشكيل الاستجابة .
فيما يلي وصف للطريقة التي أستخدمها لتبسيط كتابة معالجات HTTP في Golang بدون استخدام مولدات التعليمات البرمجية ومكتبات الجهات الخارجية.
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 [] byte، reqID uint64) ([] byte، Header، int، error) - وظيفة المعالجة نفسها ، تتلقى مخزنًا مؤقتًا للطلبات الواردة ورقم طلب HTTP فريدًا (لأغراض التسجيل) ، تُرجع مخزنًا مؤقتًا للاستجابة الصادرة ، رأس الاستجابة الصادرة ، حالة HTTP والخطأ.
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
}