@@ -23,6 +23,7 @@ import (
23
23
"io"
24
24
nethttp "net/http"
25
25
"net/url"
26
+ "path"
26
27
"strconv"
27
28
"strings"
28
29
"time"
@@ -57,6 +58,7 @@ import (
57
58
"github.com/dapr/dapr/pkg/runtime/compstore"
58
59
runtimePubsub "github.com/dapr/dapr/pkg/runtime/pubsub"
59
60
"github.com/dapr/dapr/utils"
61
+ "github.com/dapr/dapr/utils/responsewriter"
60
62
)
61
63
62
64
// API returns a list of HTTP endpoints for Dapr.
@@ -278,7 +280,7 @@ func (a *api) constructDirectMessagingEndpoints() []Endpoint {
278
280
IsFallback : true ,
279
281
Version : apiVersionV1 ,
280
282
KeepWildcardUnescaped : true ,
281
- FastHTTPHandler : a .onDirectMessage ,
283
+ Handler : a .onDirectMessage ,
282
284
},
283
285
}
284
286
}
@@ -1077,11 +1079,11 @@ func (a *api) getStateStoreName(reqCtx *fasthttp.RequestCtx) string {
1077
1079
1078
1080
type invokeError struct {
1079
1081
statusCode int
1080
- msg ErrorResponse
1082
+ msg [] byte
1081
1083
}
1082
1084
1083
1085
func (ie invokeError ) Error () string {
1084
- return fmt .Sprintf ("invokeError (statusCode='%d') msg.errorCode ='%s' msg.message='%s' " , ie .statusCode , ie .msg . ErrorCode , ie . msg . Message )
1086
+ return fmt .Sprintf ("invokeError (statusCode='%d') msg='%v' " , ie .statusCode , string ( ie .msg ) )
1085
1087
}
1086
1088
1087
1089
func (a * api ) isHTTPEndpoint (appID string ) bool {
@@ -1099,22 +1101,21 @@ func (a *api) getBaseURL(targetAppID string) string {
1099
1101
return ""
1100
1102
}
1101
1103
1102
- func (a * api ) onDirectMessage (reqCtx * fasthttp. RequestCtx ) {
1103
- targetID , invokeMethodName := findTargetIDAndMethod (string ( reqCtx . URI (). PathOriginal ()), reqCtx . Request . Header . Peek )
1104
+ func (a * api ) onDirectMessage (w nethttp. ResponseWriter , r * nethttp. Request ) {
1105
+ targetID , invokeMethodName := findTargetIDAndMethod (r . URL . String (), r . Header )
1104
1106
if targetID == "" {
1105
- msg := NewErrorResponse ("ERR_DIRECT_INVOKE" , messages .ErrDirectInvokeNoAppID )
1106
- fasthttpRespond (reqCtx , fasthttpResponseWithError (nethttp .StatusNotFound , msg ))
1107
+ respondWithError (w , messages .ErrDirectInvokeNoAppID )
1107
1108
return
1108
1109
}
1109
1110
1110
- // Store target and method as user values so they can be picked up by the tracing library
1111
- reqCtx .SetUserValue ("id" , targetID )
1112
- reqCtx .SetUserValue ("method" , invokeMethodName )
1111
+ // Store target and method as values in the context so they can be picked up by the tracing library
1112
+ rw := responsewriter .EnsureResponseWriter (w )
1113
+ rw .SetUserValue ("id" , targetID )
1114
+ rw .SetUserValue ("method" , invokeMethodName )
1113
1115
1114
- verb := strings .ToUpper (string ( reqCtx .Method ()) )
1116
+ verb := strings .ToUpper (r .Method )
1115
1117
if a .directMessaging == nil {
1116
- msg := NewErrorResponse ("ERR_DIRECT_INVOKE" , messages .ErrDirectInvokeNotReady )
1117
- fasthttpRespond (reqCtx , fasthttpResponseWithError (nethttp .StatusInternalServerError , msg ))
1118
+ respondWithError (w , messages .ErrDirectInvokeNotReady )
1118
1119
return
1119
1120
}
1120
1121
@@ -1134,18 +1135,18 @@ func (a *api) onDirectMessage(reqCtx *fasthttp.RequestCtx) {
1134
1135
}
1135
1136
1136
1137
req := invokev1 .NewInvokeMethodRequest (invokeMethodName ).
1137
- WithHTTPExtension (verb , reqCtx . QueryArgs (). String () ).
1138
- WithRawDataBytes ( reqCtx . Request . Body () ).
1139
- WithContentType (string ( reqCtx . Request . Header .ContentType () )).
1138
+ WithHTTPExtension (verb , r . URL . RawQuery ).
1139
+ WithRawData ( r . Body ).
1140
+ WithContentType (r . Header .Get ( "content-type" )).
1140
1141
// Save headers to internal metadata
1141
- WithFastHTTPHeaders ( & reqCtx . Request .Header )
1142
+ WithHTTPHeaders ( r .Header )
1142
1143
if policyDef != nil {
1143
1144
req .WithReplay (policyDef .HasRetries ())
1144
1145
}
1145
1146
defer req .Close ()
1146
1147
1147
1148
policyRunner := resiliency .NewRunnerWithOptions (
1148
- reqCtx , policyDef ,
1149
+ r . Context () , policyDef ,
1149
1150
resiliency.RunnerOpts [* invokev1.InvokeMethodResponse ]{
1150
1151
Disposer : resiliency .DisposerCloser [* invokev1 .InvokeMethodResponse ],
1151
1152
},
@@ -1156,9 +1157,10 @@ func (a *api) onDirectMessage(reqCtx *fasthttp.RequestCtx) {
1156
1157
if rErr != nil {
1157
1158
// Allowlist policies that are applied on the callee side can return a Permission Denied error.
1158
1159
// For everything else, treat it as a gRPC transport error
1160
+ apiErr := messages .ErrDirectInvoke .WithFormat (targetID , rErr )
1159
1161
invokeErr := invokeError {
1160
- statusCode : nethttp . StatusInternalServerError ,
1161
- msg : NewErrorResponse ( "ERR_DIRECT_INVOKE" , fmt . Sprintf ( messages . ErrDirectInvoke , targetID , rErr ) ),
1162
+ statusCode : apiErr . HTTPCode () ,
1163
+ msg : apiErr . JSONErrorValue ( ),
1162
1164
}
1163
1165
1164
1166
if status .Code (rErr ) == codes .PermissionDenied {
@@ -1181,7 +1183,7 @@ func (a *api) onDirectMessage(reqCtx *fasthttp.RequestCtx) {
1181
1183
if rErr != nil {
1182
1184
return rResp , invokeError {
1183
1185
statusCode : nethttp .StatusInternalServerError ,
1184
- msg : NewErrorResponse ("ERR_MALFORMED_RESPONSE" , rErr .Error ()),
1186
+ msg : NewErrorResponse ("ERR_MALFORMED_RESPONSE" , rErr .Error ()). JSONErrorValue () ,
1185
1187
}
1186
1188
}
1187
1189
} else {
@@ -1197,67 +1199,70 @@ func (a *api) onDirectMessage(reqCtx *fasthttp.RequestCtx) {
1197
1199
1198
1200
// Special case for timeouts/circuit breakers since they won't go through the rest of the logic.
1199
1201
if errors .Is (err , context .DeadlineExceeded ) || breaker .IsErrorPermanent (err ) {
1200
- fasthttpRespond ( reqCtx , fasthttpResponseWithError ( nethttp . StatusInternalServerError , NewErrorResponse ( "ERR_DIRECT_INVOKE" , err . Error ()) ))
1202
+ respondWithError ( w , messages . ErrDirectInvoke . WithFormat ( targetID , err ))
1201
1203
return
1202
1204
}
1203
1205
1204
1206
if resp != nil {
1205
1207
headers := resp .Headers ()
1206
1208
if len (headers ) > 0 {
1207
- invokev1 .InternalMetadataToHTTPHeader (reqCtx , headers , reqCtx . Response . Header .Add )
1209
+ invokev1 .InternalMetadataToHTTPHeader (r . Context () , headers , w . Header () .Add )
1208
1210
}
1209
1211
}
1210
1212
1211
1213
invokeErr := invokeError {}
1212
1214
if errors .As (err , & invokeErr ) {
1213
- fasthttpRespond ( reqCtx , fasthttpResponseWithError ( invokeErr .statusCode , invokeErr .msg ) )
1215
+ respondWithData ( w , invokeErr .statusCode , invokeErr .msg )
1214
1216
if resp != nil {
1215
1217
_ = resp .Close ()
1216
1218
}
1217
1219
return
1218
1220
}
1219
1221
1220
1222
if resp == nil {
1221
- fasthttpRespond ( reqCtx , fasthttpResponseWithError ( nethttp . StatusInternalServerError , NewErrorResponse ( "ERR_DIRECT_INVOKE" , fmt . Sprintf ( messages .ErrDirectInvoke , targetID , "response object is nil" )) ))
1223
+ respondWithError ( w , messages .ErrDirectInvoke . WithFormat ( targetID , "response object is nil" ))
1222
1224
return
1223
1225
}
1224
1226
defer resp .Close ()
1225
1227
1226
1228
statusCode := int (resp .Status ().Code )
1227
1229
1228
- body , err := resp .RawDataFull ()
1230
+ if ct := resp .ContentType (); ct != "" {
1231
+ w .Header ().Set ("content-type" , ct )
1232
+ }
1233
+
1234
+ w .WriteHeader (statusCode )
1235
+
1236
+ _ , err = io .Copy (w , resp .RawData ())
1229
1237
if err != nil {
1230
- fasthttpRespond ( reqCtx , fasthttpResponseWithError ( nethttp . StatusInternalServerError , NewErrorResponse ( "ERR_DIRECT_INVOKE" , fmt . Sprintf ( messages .ErrDirectInvoke , targetID , err )) ))
1238
+ respondWithError ( w , messages .ErrDirectInvoke . WithFormat ( targetID , err ))
1231
1239
return
1232
1240
}
1233
-
1234
- reqCtx .Response .Header .SetContentType (resp .ContentType ())
1235
- fasthttpRespond (reqCtx , fasthttpResponseWith (statusCode , body ))
1236
1241
}
1237
1242
1238
1243
// findTargetIDAndMethod finds ID of the target service and method from the following three places:
1239
1244
// 1. HTTP header 'dapr-app-id' (path is method)
1240
1245
// 2. Basic auth header: `http://dapr-app-id:<service-id>@localhost:3500/<method>`
1241
1246
// 3. URL parameter: `http://localhost:3500/v1.0/invoke/<app-id>/method/<method>`
1242
- func findTargetIDAndMethod (path string , peekHeader func ( string ) [] byte ) (targetID string , method string ) {
1243
- if appID := peekHeader (daprAppID ); len ( appID ) != 0 {
1244
- return string ( appID ) , strings .TrimPrefix (path , "/" )
1247
+ func findTargetIDAndMethod (reqPath string , headers nethttp. Header ) (targetID string , method string ) {
1248
+ if appID := headers . Get (daprAppID ); appID != "" {
1249
+ return appID , strings .TrimPrefix (path . Clean ( reqPath ) , "/" )
1245
1250
}
1246
1251
1247
- if auth := string ( peekHeader ( fasthttp .HeaderAuthorization ) ); strings .HasPrefix (auth , "Basic " ) {
1252
+ if auth := headers . Get ( fasthttp .HeaderAuthorization ); strings .HasPrefix (auth , "Basic " ) {
1248
1253
if s , err := base64 .StdEncoding .DecodeString (strings .TrimPrefix (auth , "Basic " )); err == nil {
1249
1254
pair := strings .Split (string (s ), ":" )
1250
1255
if len (pair ) == 2 && pair [0 ] == daprAppID {
1251
- return pair [1 ], strings .TrimPrefix (path , "/" )
1256
+ return pair [1 ], strings .TrimPrefix (path . Clean ( reqPath ) , "/" )
1252
1257
}
1253
1258
}
1254
1259
}
1255
1260
1256
1261
// If we're here, the handler was probably invoked with /v1.0/invoke/ (or the invocation is invalid, missing the app id provided as header or Basic auth)
1257
1262
// However, we are not relying on wildcardParam because the URL may have been sanitized to remove `//``, so `http://` would have been turned into `http:/`
1258
1263
// First, check to make sure that the path has the prefix
1259
- if idx := pathHasPrefix (path , apiVersionV1 , "invoke" ); idx > 0 {
1260
- path = path [idx :]
1264
+ if idx := pathHasPrefix (reqPath , apiVersionV1 , "invoke" ); idx > 0 {
1265
+ reqPath = reqPath [idx :]
1261
1266
1262
1267
// Scan to find app ID and method
1263
1268
// Matches `<appid>/method/<method>`.
@@ -1266,9 +1271,9 @@ func findTargetIDAndMethod(path string, peekHeader func(string) []byte) (targetI
1266
1271
// - `http://example.com/method/mymethod`
1267
1272
// - `https://example.com/method/mymethod`
1268
1273
// - `http%3A%2F%2Fexample.com/method/mymethod`
1269
- if idx = strings .Index (path , "/method/" ); idx > 0 {
1270
- targetID = path [:idx ]
1271
- method = path [(idx + len ("/method/" )):]
1274
+ if idx = strings .Index (reqPath , "/method/" ); idx > 0 {
1275
+ targetID = reqPath [:idx ]
1276
+ method = reqPath [(idx + len ("/method/" )):]
1272
1277
if t , _ := url .QueryUnescape (targetID ); t != "" {
1273
1278
targetID = t
1274
1279
}
0 commit comments