go - zero 의 웹 프레임 워 크

13790 단어 golang
go - zero 는 각종 공정 실천 을 통합 한 웹 과 rpc 프레임 워 크 입 니 다. 그 중에서 rest 는 웹 프레임 워 크 모듈 입 니 다. Go 언어 원생 http 패 키 지 를 바탕 으로 구축 되 고 가 볍 고 고성능 이 며 기능 이 완전 하 며 간단 하고 사용 하기 쉬 운 웹 프레임 워 크 입 니 다.
서비스 생 성
go - zero 에서 http 서 비 스 를 만 드 는 것 은 매우 간단 합 니 다. 공식 적 으로 goctl 도 구 를 사용 하여 생 성 하 는 것 을 추천 합 니 다.프레젠테이션 을 편리 하 게 하기 위해 수 동 으로 서 비 스 를 만 듭 니 다. 코드 는 다음 과 같 습 니 다.
package main

import (
    "log"
    "net/http"

    "github.com/tal-tech/go-zero/core/logx"
    "github.com/tal-tech/go-zero/core/service"
    "github.com/tal-tech/go-zero/rest"
    "github.com/tal-tech/go-zero/rest/httpx"
)

func main() {
    srv, err := rest.NewServer(rest.RestConf{
        Port: 9090, //     
        ServiceConf: service.ServiceConf{
            Log: logx.LogConf{Path: "./logs"}, //     
        },
    })
    if err != nil {
        log.Fatal(err)
    }
    defer srv.Stop()
    //     
    srv.AddRoutes([]rest.Route{ 
        {
            Method:  http.MethodGet,
            Path:    "/user/info",
            Handler: userInfo,
        },
    })
    
    srv.Start() //     
}

type User struct {
    Name  string `json:"name"`
    Addr  string `json:"addr"`
    Level int    `json:"level"`
}

func userInfo(w http.ResponseWriter, r *http.Request) {
    var req struct {
        UserId int64 `form:"user_id"` //     
    }
    if err := httpx.Parse(r, &req); err != nil { //     
        httpx.Error(w, err)
        return
    }
    users := map[int64]*User{
        1: &User{"go-zero", "shanghai", 1},
        2: &User{"go-queue", "beijing", 2},
    }
    httpx.WriteJson(w, http.StatusOK, users[req.UserId]) //     
}

rest. NewServer 를 통 해 서 비 스 를 만 듭 니 다. 예제 에 서 는 포트 번호 와 로그 경 로 를 설정 하 였 습 니 다. 서비스 가 시 작 된 후 9090 포트 를 검색 하고 현재 디 렉 터 리 에 logs 디 렉 터 리 를 만 드 는 동시에 각 등급 로그 파일 을 만 듭 니 다.
그 다음 에 srv. addRoutes 를 통 해 경로 등록 을 하고 모든 경로 에서 이 경로 의 방법, Path 와 Handler 를 정의 해 야 합 니 다. 그 중에서 Handler 유형 은 http. Handler Func 입 니 다.
마지막 으로 srv. Start 를 통 해 서 비 스 를 시작 하고 서 비 스 를 시작 한 후 방문 http://localhost: 9090/user/info? userid = 1 되 돌아 오 는 결 과 를 볼 수 있 습 니 다.
{
    name: "go-zero",
    addr: "shanghai",
    level: 1
}

이 간단 한 http 서비스 가 만 들 어 졌 습 니 다. rest 를 사용 하여 http 서 비 스 를 만 드 는 것 은 매우 간단 합 니 다. 주로 세 가지 절차 로 나 눌 수 있 습 니 다. 서버 생 성, 등록 경로, 시작 서비스 입 니 다.
JWT 감 권
감 권 은 거의 모든 응용 에 필요 한 능력 이 고 감 권 의 방식 이 매우 많다. jwt 는 그 중에서 비교적 간단 하고 믿 을 수 있 는 방식 으로 rest 구조 에 jwt 감 권 기능 을 내장 했다. jwt 의 원리 절 차 는 다음 과 같다.
jwt
rest 프레임 워 크 에 서 는 rest. WithJwt (secret) 를 통 해 jwt 인증 권 을 사용 합 니 다. 그 중에서 secret 는 서버 의 비밀 키 로 누설 할 수 없습니다. secret 를 사용 하여 payload 가 변경 되 었 는 지 검증 해 야 하기 때 문 입 니 다. secret 유출 클 라 이언 트 가 스스로 token 을 서명 하면 해커 는 token 을 임의로 변경 할 수 있 습 니 다.우 리 는 위의 예 를 바탕 으로 개조 하여 rest 에서 jwt 감 권 을 어떻게 사용 하 는 지 검증 합 니 다.
jwt 가 져 오기
첫 번 째 클 라 이언 트 는 jwt 를 먼저 가 져 와 로그 인 인터페이스 에서 jwt 생 성 논 리 를 실현 해 야 합 니 다.
srv.AddRoute(rest.Route{
        Method:  http.MethodPost,
        Path:    "/user/login",
        Handler: userLogin,
})

프레젠테이션 의 편 의 를 위해 userLogin 의 논 리 는 매우 간단 합 니 다. 주로 정 보 를 얻 은 다음 에 jwt 를 생 성하 고 얻 은 정 보 를 jwt payload 에 저장 한 다음 에 jwt 로 돌아 갑 니 다.
func userLogin(w http.ResponseWriter, r *http.Request) {
    var req struct {
        UserName string `json:"user_name"`
        UserId   int    `json:"user_id"`
    }
    if err := httpx.Parse(r, &req); err != nil {
        httpx.Error(w, err)
        return
    }
    token, _ := genToken(accessSecret, map[string]interface{}{
        "user_id":   req.UserId,
        "user_name": req.UserName,
    }, accessExpire)

    httpx.WriteJson(w, http.StatusOK, struct {
        UserId   int    `json:"user_id"`
        UserName string `json:"user_name"`
        Token    string `json:"token"`
    }{
        UserId:   req.UserId,
        UserName: req.UserName,
        Token:    token,
    })
}

jwt 를 만 드 는 방법 은 다음 과 같다.
func genToken(secret string, payload map[string]interface{}, expire int64) (string, error) {
    now := time.Now().Unix()
    claims := make(jwt.MapClaims)
    claims["exp"] = now + expire
    claims["iat"] = now
    for k, v := range payload {
        claims[k] = v
    }
    token := jwt.New(jwt.SigningMethodHS256)
    token.Claims = claims
    return token.SignedString([]byte(secret))
}

서비스 시작 후 cURL 로 접근
curl -X "POST" "http://localhost:9090/user/login" \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "user_name": "gozero",
  "user_id": 666
}'

다음 과 같은 결 과 를 얻 을 수 있 습 니 다.
{
  "user_id": 666,
  "user_name": "gozero",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDYxMDgwNDcsImlhdCI6MTYwNTUwMzI0NywidXNlcl9pZCI6NjY2LCJ1c2VyX25hbWUiOiJnb3plcm8ifQ.hhMd5gc3F9xZwCUoiuFqAWH48xptqnNGph0AKVkTmqM"
}

헤더 추가
rest. WithJwt (accessSecret) 를 통 해 jwt 인증 을 사용 합 니 다.
srv.AddRoute(rest.Route{
        Method:  http.MethodGet,
        Path:    "/user/data",
        Handler: userData,
}, rest.WithJwt(accessSecret))

접근/user/data 인터페이스 401 Unauthorized 인증 이 통과 되 지 않 음 을 되 돌려 줍 니 다. Authorization Header 를 추가 하면 정상적으로 접근 할 수 있 습 니 다.
curl "http://localhost:9090/user/data?user_id=1" \
      -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDYxMDgwNDcsImlhdCI6MTYwNTUwMzI0NywidXNlcl9pZCI6NjY2LCJ1c2VyX25hbWUiOiJnb3plcm8ifQ.hhMd5gc3F9xZwCUoiuFqAWH48xptqnNGph0AKVkTmqM'

정보 획득
일반적으로 사용자 id 나 사용자 이름 을 jwt 의 payload 에 저장 한 다음 jwt 의 payload 에서 우리 가 미리 저장 한 정 보 를 분석 하면 이번 요청 에서 어떤 사용자 가 시 작 했 는 지 알 수 있 습 니 다.
func userData(w http.ResponseWriter, r *http.Request) {
    var jwt struct {
        UserId   int    `ctx:"user_id"`
        UserName string `ctx:"user_name"`
    }
    err := contextx.For(r.Context(), &jwt)
    if err != nil {
        httpx.Error(w, err)
    }
    httpx.WriteJson(w, http.StatusOK, struct {
        UserId   int    `json:"user_id"`
        UserName string `json:"user_name"`
    }{
        UserId:   jwt.UserId,
        UserName: jwt.UserName,
    })
}

실현 원리
jwt 감 권 의 실현 은 authhandler. go 에서 실현 원리 도 비교적 간단 하 다. 먼저 secret 에 따라 jwt token 을 분석 하고 token 이 효과 가 있 는 지 검증 하 며 무효 또는 검증 오류 가 발생 하면 401 Unauthorized 로 돌아간다.
func unauthorized(w http.ResponseWriter, r *http.Request, err error, callback UnauthorizedCallback) {
    writer := newGuardedResponseWriter(w)

    if err != nil {
        detailAuthLog(r, err.Error())
    } else {
        detailAuthLog(r, noDetailReason)
    }
    if callback != nil {
        callback(writer, r, err)
    }

    writer.WriteHeader(http.StatusUnauthorized)
}

인증 통과 후 payload 의 정 보 를 http request 의 context 에 저장 합 니 다.
ctx := r.Context()
for k, v := range claims {
  switch k {
    case jwtAudience, jwtExpire, jwtId, jwtIssueAt, jwtIssuer, jwtNotBefore, jwtSubject:
    // ignore the standard claims
    default:
    ctx = context.WithValue(ctx, k, v)
  }
}

next.ServeHTTP(w, r.WithContext(ctx))

중간 부품
웹 프레임 워 크 의 중간 부품 은 업무 와 비 업무 기능 의 결합 을 실현 하 는 방식 이다. 웹 프레임 워 크 에서 우 리 는 중간 부품 을 통 해 감 권, 제한 흐름, 융단 등 기능 을 실현 할 수 있다. 중간 부품 의 원리 절 차 는 다음 과 같다.
go-zero之web框架_第1张图片
rest 프레임 워 크 에 매우 풍부 한 미들웨어 가 내장 되 어 있 습 니 다. rest/handler 경로 에서 alice 도 구 를 통 해 모든 미들웨어 를 연결 합 니 다. 요청 을 할 때 모든 미들웨어 를 순서대로 통과 하고 모든 조건 을 만족 시 킨 후에 최종 요청 이 진정한 업무 Handler 의 업무 논 리 를 수행 할 수 있 습 니 다. 위 에서 소개 한 jwt 감 권 은 authHandler 를 통 해 이 루어 집 니 다.내 장 된 미들웨어 가 비교적 많 기 때문에 일일이 소개 할 수 없고 관심 이 있 는 파트너 는 스스로 공부 할 수 있 습 니 다. 여기 서 prometheus 지표 가 수집 한 미들웨어 Promethous Handler 를 소개 합 니 다. 코드 는 다음 과 같 습 니 다.
func PromethousHandler(path string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            startTime := timex.Now() //     
            cw := &security.WithCodeResponseWriter{Writer: w}
            defer func() {
        //   
                metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), path)
        // code 
                metricServerReqCodeTotal.Inc(path, strconv.Itoa(cw.Code))
            }()
            
            next.ServeHTTP(cw, r)
        })
    }
}

이 미들웨어 에 서 는 요청 이 시 작 될 때 시작 시간 을 기록 하고 요청 이 끝 난 후 defer 에서 prometheus 의 Histogram 과 Counter 데이터 형식 을 통 해 현재 요청 path 의 시간 소모 와 되 돌아 오 는 code 코드 를 기록 합 니 다. 이때 저 희 는 방문 http://127.0.0.1: 9101/metrics 를 통 해 관련 지표 정 보 를 볼 수 있 습 니 다.
go-zero之web框架_第2张图片
경로 원리
rest 프레임 워 크 에서 AddRoutes 방법 을 통 해 루트 를 등록 합 니 다. 루트 마다 Method, Path, Handler 세 가지 속성 이 있 습 니 다. Handler 유형 은 http. Handler Func 이 고 추 가 된 루트 는 featuredRoutes 로 다음 과 같이 정 의 됩 니 다.
featuredRoutes struct {
        priority  bool //      
        jwt       jwtSetting  // jwt  
        signature signatureSetting //     
        routes    []Route  //   AddRoutes     
    }

featuredRoutes 는 engine 의 AddRoutes 를 통 해 engine 의 routes 속성 에 추가 합 니 다.
func (s *engine) AddRoutes(r featuredRoutes) {
    s.routes = append(s.routes, r)
}

Start 방법 으로 서 비 스 를 시작 하면 engine 의 Start 방법 을 호출 하고 Start WithRouter 방법 을 호출 합 니 다. 이 방법 은 bindRoutes 로 연결 되 어 있 습 니 다.
func (s *engine) bindRoutes(router httpx.Router) error {
    metrics := s.createMetrics()

    for _, fr := range s.routes { 
        if err := s.bindFeaturedRoutes(router, fr, metrics); err != nil { //     
            return err
        }
    }

    return nil
}

최종 적 으로 patRouter 의 Handle 방법 을 호출 하여 바 인 딩 을 하고 patRouter 는 Router 인 터 페 이 스 를 실현 합 니 다.
type Router interface {
    http.Handler
    Handle(method string, path string, handler http.Handler) error
    SetNotFoundHandler(handler http.Handler)
    SetNotAllowedHandler(handler http.Handler)
}

patRouter 에서 모든 요청 방법 은 하나의 트 리 구조 에 대응 합 니 다. 모든 트 리 노드 는 path 에 대응 하 는 handler 이 고 children 은 경로 매개 변수 와 경로 매개 변수 가 없 는 트 리 노드 로 다음 과 같이 정의 합 니 다.
node struct {
  item     interface{}
  children [2]map[string]*node
}

Tree struct {
  root *node
}

Tree 의 Add 방법 을 통 해 서로 다른 path 와 대응 하 는 handler 를 이 트 리 에 등록 합 니 다. 우 리 는 그림 을 통 해 이 트 리 의 저장 구 조 를 보 여 줍 니 다. 예 를 들 어 우리 가 정의 하 는 길 은 다음 과 같 습 니 다.
{
  Method:  http.MethodGet,
  Path:    "/user",
  Handler: userHander,
},
{
  Method:  http.MethodGet,
  Path:    "/user/infos",
  Handler: infosHandler,
},
{
  Method:  http.MethodGet,
  Path:    "/user/info/:id",
  Handler: infoHandler,
},

루트 에 저 장 된 트 리 구 조 는 다음 과 같다.

요청 이 올 때 patRouter 의 ServeHTTP 방법 을 호출 합 니 다. 이 방법 에서 tree. Search 방법 을 통 해 해당 하 는 handler 를 찾 아 실행 합 니 다. 그렇지 않 으 면 notFound 나 notAllow 의 논 리 를 실행 합 니 다.
func (pr *patRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    reqPath := path.Clean(r.URL.Path)
    if tree, ok := pr.trees[r.Method]; ok {
        if result, ok := tree.Search(reqPath); ok { //         handler
            if len(result.Params) > 0 {
                r = context.WithPathVars(r, result.Params)
            }
            result.Item.(http.Handler).ServeHTTP(w, r)
            return
        }
    }

    allow, ok := pr.methodNotAllowed(r.Method, reqPath)
    if !ok {
        pr.handleNotFound(w, r)
        return
    }

    if pr.notAllowed != nil {
        pr.notAllowed.ServeHTTP(w, r)
    } else {
        w.Header().Set(allowHeader, allow)
        w.WriteHeader(http.StatusMethodNotAllowed)
    }
}

총결산
본 고 는 전체적으로 rest 를 소개 했다. 이 글 을 통 해 rest 의 디자인 과 주요 기능 을 대체적으로 이해 할 수 있다. 그 중에서 중간 부분 은 중심 이 고 그 안에 각종 서비스 관리 와 관련 된 기능 을 통합 시 켰 으 며 자동 으로 통합 되 어 우리 가 어떠한 설정 도 할 필요 가 없다. 다른 기능, 예 를 들 어 파라미터 자동 효과 등 기능 은 지면 이 제한 되 어 있 기 때문에 여기 서 소개 하지 않 는 다.관심 있 는 친 구 는 스스로 공식 문 서 를 보고 공부 할 수 있다.go - zero 에 서 는 http 프로 토 콜 뿐만 아니 라 rpc 프로 토 콜 과 각종 성능 향상 과 개발 효율 을 향상 시 키 는 도 구 를 제공 하여 우리 가 깊이 공부 하고 연구 할 만 한 구조 입 니 다.
항목 주소
https://github.com/tal-tech/go-zero
글 이 좋다 면, 스타 를 주문 하 세 요.
항목 주소:
github

좋은 웹페이지 즐겨찾기