go-kratos / kratos

Your ultimate Go microservices framework for the cloud-native era.
https://go-kratos.dev
MIT License
23.31k stars 4.01k forks source link

[Question] How to return a custom response json? #3268

Closed kvii closed 7 months ago

kvii commented 7 months ago

How to return a custom response json?

kvii commented 7 months ago

answer reference

package server

import (
    v1 "code_msg_data/api/helloworld/v1"
    "code_msg_data/internal/conf"
    "code_msg_data/internal/service"
    sj "encoding/json"
    nt "net/http"
    "strings"

    "github.com/go-kratos/kratos/v2/encoding"
    "github.com/go-kratos/kratos/v2/encoding/json"
    "github.com/go-kratos/kratos/v2/errors"
    "github.com/go-kratos/kratos/v2/log"
    "github.com/go-kratos/kratos/v2/middleware/recovery"
    "github.com/go-kratos/kratos/v2/transport/http"
)

// 最终效果
// $ curl http://localhost:8000/helloworld/kvii
// {"code":0,"message":"success","data":{"message":"Hello kvii"}}

// $ curl http://localhost:8000/helloworld/err
// {"code":404,"message":"user not found"}

// NewHTTPServer new an HTTP server.
func NewHTTPServer(c *conf.Server, greeter *service.GreeterService, logger log.Logger) *http.Server {
    var opts = []http.ServerOption{
        http.Middleware(
            recovery.Recovery(),
        ),
        http.ErrorEncoder(DefaultErrorEncoder),       // <- 关键代码
        http.ResponseEncoder(DefaultResponseEncoder), // <- 关键代码
    }
    if c.Http.Network != "" {
        opts = append(opts, http.Network(c.Http.Network))
    }
    if c.Http.Addr != "" {
        opts = append(opts, http.Address(c.Http.Addr))
    }
    if c.Http.Timeout != nil {
        opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
    }
    srv := http.NewServer(opts...)
    v1.RegisterGreeterHTTPServer(srv, greeter)
    return srv
}

// DefaultResponseEncoder copy from http.DefaultResponseEncoder
func DefaultResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error {
    if v == nil {
        return nil
    }
    if rd, ok := v.(http.Redirector); ok {
        url, code := rd.Redirect()
        nt.Redirect(w, r, url, code)
        return nil
    }

    codec := encoding.GetCodec(json.Name) // ignore Accept Header
    data, err := codec.Marshal(v)
    if err != nil {
        return err
    }

    bs, _ := sj.Marshal(NewResponse(data))

    w.Header().Set("Content-Type", ContentType(codec.Name()))
    _, err = w.Write(bs)
    if err != nil {
        return err
    }
    return nil
}

// DefaultErrorEncoder copy from http.DefaultErrorEncoder.
func DefaultErrorEncoder(w http.ResponseWriter, r *http.Request, err error) {
    se := FromError(errors.FromError(err)) // change error to BaseResponse

    codec := encoding.GetCodec(json.Name) // ignore Accept header
    body, err := codec.Marshal(se)
    if err != nil {
        w.WriteHeader(nt.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Type", ContentType(codec.Name()))
    // w.WriteHeader(int(se.Code)) // ignore http status code
    _, _ = w.Write(body)
}

const (
    baseContentType = "application"
)

// ContentType returns the content-type with base prefix.
func ContentType(subtype string) string {
    return strings.Join([]string{baseContentType, subtype}, "/")
}

func NewResponse(data []byte) BaseResponse {
    return BaseResponse{
        Code:    0,
        Message: "success",
        Data:    sj.RawMessage(data),
    }
}

func FromError(e *errors.Error) *BaseResponse {
    if e == nil {
        return nil
    }
    return &BaseResponse{
        Code:    e.Code,
        Message: e.Message,
    }
}

type BaseResponse struct {
    Code    int32         `json:"code"`
    Message string        `json:"message"`
    Data    sj.RawMessage `json:"data,omitempty"`
}