cosmos-sdk/server/v2/api/rest/handler.go
2024-10-21 13:45:28 +00:00

100 lines
2.6 KiB
Go

package rest
import (
"encoding/json"
"fmt"
"io"
"net/http"
"reflect"
"strings"
"github.com/cosmos/gogoproto/jsonpb"
gogoproto "github.com/cosmos/gogoproto/proto"
"cosmossdk.io/core/transaction"
"cosmossdk.io/server/v2/appmanager"
)
const (
ContentTypeJSON = "application/json"
MaxBodySize = 1 << 20 // 1 MB
)
func NewDefaultHandler[T transaction.Tx](appManager appmanager.AppManager[T]) http.Handler {
return &DefaultHandler[T]{appManager: appManager}
}
type DefaultHandler[T transaction.Tx] struct {
appManager appmanager.AppManager[T]
}
func (h *DefaultHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := h.validateMethodIsPOST(r); err != nil {
http.Error(w, err.Error(), http.StatusMethodNotAllowed)
return
}
if err := h.validateContentTypeIsJSON(r); err != nil {
http.Error(w, err.Error(), http.StatusUnsupportedMediaType)
return
}
msg, err := h.createMessage(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
query, err := h.appManager.Query(r.Context(), 0, msg)
if err != nil {
http.Error(w, "Error querying", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", ContentTypeJSON)
if err := json.NewEncoder(w).Encode(query); err != nil {
http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError)
}
}
// validateMethodIsPOST validates that the request method is POST.
func (h *DefaultHandler[T]) validateMethodIsPOST(r *http.Request) error {
if r.Method != http.MethodPost {
return fmt.Errorf("method not allowed")
}
return nil
}
// validateContentTypeIsJSON validates that the request content type is JSON.
func (h *DefaultHandler[T]) validateContentTypeIsJSON(r *http.Request) error {
contentType := r.Header.Get("Content-Type")
if contentType != ContentTypeJSON {
return fmt.Errorf("unsupported content type, expected %s", ContentTypeJSON)
}
return nil
}
// createMessage creates the message by unmarshalling the request body.
func (h *DefaultHandler[T]) createMessage(r *http.Request) (gogoproto.Message, error) {
path := strings.TrimPrefix(r.URL.Path, "/")
requestType := gogoproto.MessageType(path)
if requestType == nil {
return nil, fmt.Errorf("unknown request type")
}
msg, ok := reflect.New(requestType.Elem()).Interface().(gogoproto.Message)
if !ok {
return nil, fmt.Errorf("failed to create message instance")
}
defer r.Body.Close()
limitedReader := io.LimitReader(r.Body, MaxBodySize)
err := jsonpb.Unmarshal(limitedReader, msg)
if err != nil {
return nil, fmt.Errorf("error parsing body: %w", err)
}
return msg, nil
}