mirror of https://github.com/dapr/go-sdk.git
296 lines
9.5 KiB
Go
296 lines
9.5 KiB
Go
/*
|
|
Copyright 2021 The Dapr Authors
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package http
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"io/ioutil"
|
|
"net/http"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
actorErr "github.com/dapr/go-sdk/actor/error"
|
|
"github.com/dapr/go-sdk/actor/runtime"
|
|
"github.com/dapr/go-sdk/service/common"
|
|
"github.com/dapr/go-sdk/service/internal"
|
|
)
|
|
|
|
const (
|
|
// PubSubHandlerSuccessStatusCode is the successful ack code for pubsub event appcallback response.
|
|
PubSubHandlerSuccessStatusCode int = http.StatusOK
|
|
|
|
// PubSubHandlerRetryStatusCode is the error response code (nack) pubsub event appcallback response.
|
|
PubSubHandlerRetryStatusCode int = http.StatusInternalServerError
|
|
|
|
// PubSubHandlerDropStatusCode is the pubsub event appcallback response code indicating that Dapr should drop that message.
|
|
PubSubHandlerDropStatusCode int = http.StatusSeeOther
|
|
)
|
|
|
|
// topicEventJSON is identical to `common.TopicEvent`
|
|
// except for it treats `data` as a json.RawMessage so it can
|
|
// be used as bytes or interface{}.
|
|
type topicEventJSON struct {
|
|
// ID identifies the event.
|
|
ID string `json:"id"`
|
|
// The version of the CloudEvents specification.
|
|
SpecVersion string `json:"specversion"`
|
|
// The type of event related to the originating occurrence.
|
|
Type string `json:"type"`
|
|
// Source identifies the context in which an event happened.
|
|
Source string `json:"source"`
|
|
// The content type of data value.
|
|
DataContentType string `json:"datacontenttype"`
|
|
// The content of the event.
|
|
// Note, this is why the gRPC and HTTP implementations need separate structs for cloud events.
|
|
Data json.RawMessage `json:"data"`
|
|
// The base64 encoding content of the event.
|
|
// Note, this is processing rawPayload and binary content types.
|
|
DataBase64 string `json:"data_base64,omitempty"`
|
|
// Cloud event subject
|
|
Subject string `json:"subject"`
|
|
// The pubsub topic which publisher sent to.
|
|
Topic string `json:"topic"`
|
|
// PubsubName is name of the pub/sub this message came from
|
|
PubsubName string `json:"pubsubname"`
|
|
}
|
|
|
|
func (s *Server) registerBaseHandler() {
|
|
// register subscribe handler
|
|
f := func(w http.ResponseWriter, r *http.Request) {
|
|
subs := make([]*internal.TopicSubscription, 0, len(s.topicRegistrar))
|
|
for _, s := range s.topicRegistrar {
|
|
subs = append(subs, s.Subscription)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(subs); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
s.mux.HandleFunc("/dapr/subscribe", f)
|
|
|
|
// register health check handler
|
|
fHealth := func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
s.mux.HandleFunc("/healthz", fHealth).Methods(http.MethodGet)
|
|
|
|
// register actor config handler
|
|
fRegister := func(w http.ResponseWriter, r *http.Request) {
|
|
data, err := runtime.GetActorRuntimeInstance().GetJSONSerializedConfig()
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
if _, err = w.Write(data); err != nil {
|
|
return
|
|
}
|
|
}
|
|
s.mux.HandleFunc("/dapr/config", fRegister).Methods(http.MethodGet)
|
|
|
|
// register actor method invoke handler
|
|
fInvoke := func(w http.ResponseWriter, r *http.Request) {
|
|
varsMap := mux.Vars(r)
|
|
actorType := varsMap["actorType"]
|
|
actorID := varsMap["actorId"]
|
|
methodName := varsMap["methodName"]
|
|
reqData, _ := ioutil.ReadAll(r.Body)
|
|
rspData, err := runtime.GetActorRuntimeInstance().InvokeActorMethod(actorType, actorID, methodName, reqData)
|
|
if err == actorErr.ErrActorTypeNotFound {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
if err != actorErr.Success {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write(rspData)
|
|
}
|
|
s.mux.HandleFunc("/actors/{actorType}/{actorId}/method/{methodName}", fInvoke).Methods(http.MethodPut)
|
|
|
|
// register deactivate actor handler
|
|
fDelete := func(w http.ResponseWriter, r *http.Request) {
|
|
varsMap := mux.Vars(r)
|
|
actorType := varsMap["actorType"]
|
|
actorID := varsMap["actorId"]
|
|
err := runtime.GetActorRuntimeInstance().Deactivate(actorType, actorID)
|
|
if err == actorErr.ErrActorTypeNotFound || err == actorErr.ErrActorIDNotFound {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
if err != actorErr.Success {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
s.mux.HandleFunc("/actors/{actorType}/{actorId}", fDelete).Methods(http.MethodDelete)
|
|
|
|
// register actor reminder invoke handler
|
|
fReminder := func(w http.ResponseWriter, r *http.Request) {
|
|
varsMap := mux.Vars(r)
|
|
actorType := varsMap["actorType"]
|
|
actorID := varsMap["actorId"]
|
|
reminderName := varsMap["reminderName"]
|
|
reqData, _ := ioutil.ReadAll(r.Body)
|
|
err := runtime.GetActorRuntimeInstance().InvokeReminder(actorType, actorID, reminderName, reqData)
|
|
if err == actorErr.ErrActorTypeNotFound {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
if err != actorErr.Success {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
s.mux.HandleFunc("/actors/{actorType}/{actorId}/method/remind/{reminderName}", fReminder).Methods(http.MethodPut)
|
|
|
|
// register actor timer invoke handler
|
|
fTimer := func(w http.ResponseWriter, r *http.Request) {
|
|
varsMap := mux.Vars(r)
|
|
actorType := varsMap["actorType"]
|
|
actorID := varsMap["actorId"]
|
|
timerName := varsMap["timerName"]
|
|
reqData, _ := ioutil.ReadAll(r.Body)
|
|
err := runtime.GetActorRuntimeInstance().InvokeTimer(actorType, actorID, timerName, reqData)
|
|
if err == actorErr.ErrActorTypeNotFound {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
if err != actorErr.Success {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
s.mux.HandleFunc("/actors/{actorType}/{actorId}/method/timer/{timerName}", fTimer).Methods(http.MethodPut)
|
|
}
|
|
|
|
// AddTopicEventHandler appends provided event handler with it's name to the service.
|
|
func (s *Server) AddTopicEventHandler(sub *common.Subscription, fn common.TopicEventHandler) error {
|
|
if sub == nil {
|
|
return errors.New("subscription required")
|
|
}
|
|
// Route is only required for HTTP but should be specified for the
|
|
// app protocol to be interchangeable.
|
|
if sub.Route == "" {
|
|
return errors.New("handler route name")
|
|
}
|
|
if err := s.topicRegistrar.AddSubscription(sub, fn); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.mux.Handle(sub.Route, optionsHandler(http.HandlerFunc(
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
// check for post with no data
|
|
if r.ContentLength == 0 {
|
|
http.Error(w, "nil content", PubSubHandlerDropStatusCode)
|
|
return
|
|
}
|
|
|
|
// deserialize the event
|
|
var in topicEventJSON
|
|
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
|
http.Error(w, err.Error(), PubSubHandlerDropStatusCode)
|
|
return
|
|
}
|
|
|
|
if in.PubsubName == "" {
|
|
in.Topic = sub.PubsubName
|
|
}
|
|
if in.Topic == "" {
|
|
in.Topic = sub.Topic
|
|
}
|
|
|
|
var data interface{}
|
|
var rawData []byte
|
|
if len(in.Data) > 0 {
|
|
rawData = []byte(in.Data)
|
|
data = rawData
|
|
var v interface{}
|
|
// We can assume that rawData is valid JSON
|
|
// without checking in.DataContentType == "application/json".
|
|
if err := json.Unmarshal(rawData, &v); err == nil {
|
|
data = v
|
|
// Handling of JSON base64 encoded or escaped in a string.
|
|
if str, ok := v.(string); ok {
|
|
// This is the path that will most likely succeed.
|
|
var vString interface{}
|
|
if err := json.Unmarshal([]byte(str), &vString); err == nil {
|
|
data = vString
|
|
} else if decoded, err := base64.StdEncoding.DecodeString(str); err == nil {
|
|
// Decoded Base64 encoded JSON does not seem to be in the spec
|
|
// but it is in existing unit tests so this handles that case.
|
|
var vBase64 interface{}
|
|
if err := json.Unmarshal(decoded, &vBase64); err == nil {
|
|
data = vBase64
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if in.DataBase64 != "" {
|
|
var err error
|
|
rawData, err = base64.StdEncoding.DecodeString(in.DataBase64)
|
|
if err == nil {
|
|
data = rawData
|
|
if in.DataContentType == "application/json" {
|
|
var v interface{}
|
|
if err := json.Unmarshal(rawData, &v); err == nil {
|
|
data = v
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
te := common.TopicEvent{
|
|
ID: in.ID,
|
|
SpecVersion: in.SpecVersion,
|
|
Type: in.Type,
|
|
Source: in.Source,
|
|
DataContentType: in.DataContentType,
|
|
Data: data,
|
|
RawData: rawData,
|
|
DataBase64: in.DataBase64,
|
|
Subject: in.Subject,
|
|
PubsubName: in.PubsubName,
|
|
Topic: in.Topic,
|
|
}
|
|
|
|
w.Header().Add("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
// execute user handler
|
|
retry, err := fn(r.Context(), &te)
|
|
if err == nil {
|
|
writeStatus(w, common.SubscriptionResponseStatusSuccess)
|
|
return
|
|
}
|
|
|
|
if retry {
|
|
writeStatus(w, common.SubscriptionResponseStatusRetry)
|
|
return
|
|
}
|
|
|
|
writeStatus(w, common.SubscriptionResponseStatusDrop)
|
|
})))
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeStatus(w http.ResponseWriter, s string) {
|
|
status := &common.SubscriptionResponse{Status: s}
|
|
if err := json.NewEncoder(w).Encode(status); err != nil {
|
|
http.Error(w, err.Error(), PubSubHandlerRetryStatusCode)
|
|
}
|
|
}
|