go - zero 의 웹 프레임 워 크
13790 단어 golang
서비스 생 성
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 의 원리 절 차 는 다음 과 같다.
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))
중간 부품
웹 프레임 워 크 의 중간 부품 은 업무 와 비 업무 기능 의 결합 을 실현 하 는 방식 이다. 웹 프레임 워 크 에서 우 리 는 중간 부품 을 통 해 감 권, 제한 흐름, 융단 등 기능 을 실현 할 수 있다. 중간 부품 의 원리 절 차 는 다음 과 같다.
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 를 통 해 관련 지표 정 보 를 볼 수 있 습 니 다.
경로 원리
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
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
set containerThere is no built-in set container in Go How to implement Set struct{} => type struct{}{} => 0bytes How to create set :=...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.