一个使用Golang的HTTP JSON响应包的例子

90 阅读1分钟

有很多方法来处理HTTP响应,但我将向你展示我喜欢的方法。我个人更喜欢/在我的项目中使用第一个版本,因为所有的选项都是可选的。如果你在你的Go应用程序中使用某种JSON方式处理响应,它是很有用的。你也可以设置头文件。

选项1

package response

import (
	"encoding/json"
	"errors"
	"net/http"
)

type response struct {
	code    int
	headers map[string]string
	Action  string            `json:"action,omitempty"`
	Data    interface{}       `json:"data,omitempty"`
	Meta    interface{}       `json:"meta,omitempty"`
	Message string            `json:"message,omitempty"`
	Errors  map[string]string `json:"errors,omitempty"`
}

// Write sends the response to the client. The `response` fields can be
// overridden by passing variadic `opts` ("Functional Options") arguments. If
// no options are given, an empty `200` response is used.
func Write(w http.ResponseWriter, opts ...Option) error {
	if len(opts) == 0 {
		w.WriteHeader(http.StatusOK)
		return nil
	}

	r := &response{code: http.StatusOK}

	for _, opt := range opts {
		opt(r)
	}

	if r.code == 0 {
		return errors.New("0 is not a valid code")
	}

	for k, v := range r.headers {
		w.Header().Add(k, v)
	}

	if !isBodyAllowed(r.code) {
		w.WriteHeader(r.code)
		return nil
	}

	body, err := json.Marshal(r)
	if err != nil {
		return err
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(r.code)

	if _, err := w.Write(body); err != nil {
		return err
	}

	return nil
}

// isBodyAllowed reports whether a given response status code permits a body.
// See RFC 7230, section 3.3.
func isBodyAllowed(status int) bool {
	if (status >= 100 && status <= 199) || status == 204 || status == 304 {
		return false
	}

	return true
}

// -----------------------------------------------------------------------------

// Option helps overriding/adding response options to the current response.
type Option func(*response)

// Code sets status code.
func Code(code int) Option {
	return func(r *response) {
		r.code = code
	}
}

// Headers adds headers.
func Headers(headers map[string]string) Option {
	return func(r *response) {
		for k, v := range headers {
			r.headers[k] = v
		}
	}
}

// Success represents "successful" response.
func Success(action string, data interface{}, meta interface{}) Option {
	return func(r *response) {
		r.Action = action
		r.Data = data
		r.Meta = meta
		r.Message = ""
		r.Errors = nil
	}
}

// Error represents "failure" response.
func Error(action string, message string, errors map[string]string) Option {
	return func(r *response) {
		r.Action = action
		r.Message = message
		r.Errors = errors
		r.Data = nil
		r.Meta = nil
	}
}

使用方法

response.Write(w)

response.Write(w, response.Code(http.StatusNoContent))

response.Write(w,
	response.Code(http.StatusBadRequest),
	response.Error("users", "Invalid request", map[string]string{"name":"Required field"}),
)

response.Write(w,
	response.Code(http.StatusInternalServerError),
	response.Error("users", "Internal error occurred", nil),
)

选项2

package response

import (
	"encoding/json"
	"errors"
	"net/http"
)

type Response struct {
	// Shared common fields.
	Code    int               `json:"-"`
	Headers map[string]string `json:"-"`
	Action  string            `json:"action,omitempty"`

	// Success specific fields.
	Data    interface{}       `json:"data,omitempty"`
	Meta    interface{}       `json:"meta,omitempty"`

	// Failure specific fields.
	Message string            `json:"message,omitempty"`
	Errors  map[string]string `json:"errors,omitempty"`
}

func Write(w http.ResponseWriter, r Response) error {
	if r.Code == 0 {
		return errors.New("0 is not a valid code")
	}

	for k, v := range r.Headers {
		w.Header().Add(k, v)
	}

	if !isBodyAllowed(r.Code) {
		w.WriteHeader(r.Code)
		return nil
	}

	body, err := json.Marshal(r)
	if err != nil {
		return err
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(r.Code)

	if _, err := w.Write(body); err != nil {
		return err
	}

	return nil
}

// See RFC 7230, section 3.3.
func isBodyAllowed(status int) bool {
	if (status >= 100 && status <= 199) || status == 204 || status == 304 {
		return false
	}

	return true
}

使用方法

response.Write(w, response.Response{
    Code:    http.StatusInternalServerError,
    Action:  "users",
    Message: "An internal error has occurred.",
})

response.Write(w, response.Response{
    Code:    http.StatusCreated,
    Action:  "users",
    Data:    something,
})

response.Write(w, response.Response{Code: http.StatusNoContent})

选项3

package response

import (
	"encoding/json"
	"net/http"
)

type Success struct {
	Action string      `json:"action,omitempty"`
	Data   interface{} `json:"data,omitempty"`
	Meta   interface{} `json:"meta,omitempty"`
}

type Error struct {
	Action  string            `json:"action,omitempty"`
	Message string            `json:"message,omitempty"`
	Errors  map[string]string `json:"errors,omitempty"`
}

// NewSuccess returns `*Success` instance.
func NewSuccess() *Success {
	return &Success{}
}

// NewError returns `*Error` instance.
func NewError() *Error {
	return &Error{}
}

// Write calls `write` function to write success response.
func (s *Success) Write(w http.ResponseWriter, code int, headers map[string]string) {
	write(w, s, code, headers)
}

// Write calls `write` function to write error response.
func (e *Error) Write(w http.ResponseWriter, code int, headers map[string]string) {
	write(w, e, code, headers)
}

// Write writes final response.
func write(w http.ResponseWriter, d interface{}, c int, h map[string]string) error {
	if c == 0 {
		return errors.New("0 is not a valid code")
	}

	for k, v := range h {
		w.Header().Set(k, v)
	}

	if !bodyAllowed(c) {
		w.WriteHeader(c)
		return nil
	}

	b, e := json.Marshal(d)
	if e != nil {
		return e
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(c)

	if _, e := w.Write(b); e != nil {
		return e
	}

	return nil
}

// bodyAllowed reports whether a given response status code permits a body. See
// RFC 7230, section 3.3.
func bodyAllowed(status int) bool {
	if (status >= 100 && status <= 199) || status == 204 || status == 304 {
		return false
	}

	return true
}

使用情况

response.NewSuccess().Write(w, http.StatusNoContent, nil)

res := response.NewError()
res.Action = "users"
res.Message = "Invalid request"
res.Errors = map[string]string{"name":"Required field"}
res.Write(w, http.StatusBadRequest, nil)

res := response.NewError()
res.Action = "users"
res.Write(w, http.StatusInternalServerError, nil)

响应示例

500 Internal Server Error
{
    "action": "users",
    "message": "An internal error has occurred."
}

400 Bad Request
{
    "action": "users",
    "message": "An invalid request was provided.",
    "errors": {
        "address": "Invalid value.",
        "name": "Invalid value."
    }
}

200 OK
{
    "action": "users",
    "data": {
        "uuid": "ac6ae584-7820-4104-ab1d-06bafb07cc65",
        "name": "Name",
        "address": "Address"
    }
}