1
1
package io .scalecube .services ;
2
2
3
+ import static io .scalecube .services .MaskUtil .mask ;
3
4
import static io .scalecube .services .api .ServiceMessage .HEADER_QUALIFIER ;
4
5
import static io .scalecube .services .api .ServiceMessage .HEADER_REQUEST_METHOD ;
5
6
import static io .scalecube .services .api .ServiceMessage .HEADER_UPLOAD_FILENAME ;
10
11
import io .scalecube .services .methods .MethodInfo ;
11
12
import java .math .BigDecimal ;
12
13
import java .math .BigInteger ;
14
+ import java .util .Collections ;
13
15
import java .util .Map ;
14
16
import java .util .Map .Entry ;
15
17
import java .util .Objects ;
18
+ import java .util .StringJoiner ;
16
19
import java .util .stream .Stream ;
17
20
import org .slf4j .Logger ;
18
21
import org .slf4j .LoggerFactory ;
19
22
import reactor .core .publisher .Mono ;
20
23
import reactor .util .context .Context ;
21
24
22
25
/**
23
- * Represents request-specific context that holds metadata related to the current request. It acts
24
- * as a wrapper around an existing {@link Context} and provides additional methods for accessing and
25
- * managing request-specific data such as headers, principal, method information, and path
26
- * variables.
26
+ * Request-scoped context that wraps Reactor {@link Context} and provides typed accessors for
27
+ * request metadata (headers, principal, method info, etc.).
27
28
*/
28
29
public class RequestContext implements Context {
29
30
30
31
private static final Logger LOGGER = LoggerFactory .getLogger (RequestContext .class );
31
32
33
+ private static final Object HEADERS_KEY = new Object ();
34
+ private static final Object REQUEST_KEY = new Object ();
35
+ private static final Object PRINCIPAL_KEY = new Object ();
36
+ private static final Object METHOD_INFO_KEY = new Object ();
37
+ private static final Object PATH_VARS_KEY = new Object ();
38
+
32
39
private final Context source ;
33
40
34
- /** Creates new {@code RequestContext} with an empty base context. */
41
+ /** Creates new {@code RequestContext} with empty base context. */
35
42
public RequestContext () {
36
43
this (Context .empty ());
37
44
}
38
45
39
46
/**
40
- * Creates new {@code RequestContext} based on an existing context.
47
+ * Creates new {@code RequestContext} based on existing context.
41
48
*
42
- * @param source the source context to wrap
49
+ * @param source source context to wrap
43
50
*/
44
51
public RequestContext (Context source ) {
45
52
this .source = Objects .requireNonNull (source , "source" );
@@ -98,7 +105,7 @@ public Context delete(Object key) {
98
105
* @return headers, or {@code null} if not set
99
106
*/
100
107
public Map <String , String > headers () {
101
- return source .getOrDefault ("headers" , null );
108
+ return source .getOrDefault (HEADERS_KEY , Collections . emptyMap () );
102
109
}
103
110
104
111
/**
@@ -108,7 +115,7 @@ public Map<String, String> headers() {
108
115
* @return new {@code RequestContext} instance with updated headers
109
116
*/
110
117
public RequestContext headers (Map <String , String > headers ) {
111
- return put ("headers" , headers );
118
+ return put (HEADERS_KEY , headers );
112
119
}
113
120
114
121
/**
@@ -117,7 +124,7 @@ public RequestContext headers(Map<String, String> headers) {
117
124
* @return request, or {@code null} if not set
118
125
*/
119
126
public Object request () {
120
- return source .getOrDefault ("request" , null );
127
+ return source .getOrDefault (REQUEST_KEY , null );
121
128
}
122
129
123
130
/**
@@ -127,7 +134,7 @@ public Object request() {
127
134
* @return new {@code RequestContext} instance with updated request
128
135
*/
129
136
public RequestContext request (Object request ) {
130
- return put ("request" , request );
137
+ return put (REQUEST_KEY , request );
131
138
}
132
139
133
140
/**
@@ -137,8 +144,7 @@ public RequestContext request(Object request) {
137
144
* @return header value, or {@code null} if not found
138
145
*/
139
146
public String header (String name ) {
140
- final var headers = headers ();
141
- return headers != null ? headers .get (name ) : null ;
147
+ return headers ().get (name );
142
148
}
143
149
144
150
/**
@@ -174,7 +180,7 @@ public String uploadFilename() {
174
180
* @return {@link Principal} associated with the request, or {@code null} if not set
175
181
*/
176
182
public Principal principal () {
177
- return getOrDefault ("principal" , null );
183
+ return getOrDefault (PRINCIPAL_KEY , null );
178
184
}
179
185
180
186
/**
@@ -184,7 +190,7 @@ public Principal principal() {
184
190
* @return new {@code RequestContext} instance with the updated principal
185
191
*/
186
192
public RequestContext principal (Principal principal ) {
187
- return put ("principal" , principal );
193
+ return put (PRINCIPAL_KEY , principal );
188
194
}
189
195
190
196
/**
@@ -204,7 +210,7 @@ public boolean hasPrincipal() {
204
210
* @return {@link MethodInfo} associated with the request, or {@code null} if not set
205
211
*/
206
212
public MethodInfo methodInfo () {
207
- return getOrDefault ("methodInfo" , null );
213
+ return getOrDefault (METHOD_INFO_KEY , null );
208
214
}
209
215
210
216
/**
@@ -214,7 +220,7 @@ public MethodInfo methodInfo() {
214
220
* @return new {@code RequestContext} instance with the updated {@link MethodInfo}
215
221
*/
216
222
public RequestContext methodInfo (MethodInfo methodInfo ) {
217
- return put ("methodInfo" , methodInfo );
223
+ return put (METHOD_INFO_KEY , methodInfo );
218
224
}
219
225
220
226
/**
@@ -223,7 +229,7 @@ public RequestContext methodInfo(MethodInfo methodInfo) {
223
229
* @return path variables, or {@code null} if not set
224
230
*/
225
231
public Map <String , String > pathVars () {
226
- return get ( "pathVars" );
232
+ return source . getOrDefault ( PATH_VARS_KEY , Collections . emptyMap () );
227
233
}
228
234
229
235
/**
@@ -232,7 +238,7 @@ public Map<String, String> pathVars() {
232
238
* @return path variables, or {@code null} if not set
233
239
*/
234
240
public RequestContext pathVars (Map <String , String > pathVars ) {
235
- return put ("pathVars" , pathVars );
241
+ return put (PATH_VARS_KEY , pathVars );
236
242
}
237
243
238
244
/**
@@ -242,8 +248,7 @@ public RequestContext pathVars(Map<String, String> pathVars) {
242
248
* @return path variable value, or {@code null} if not found
243
249
*/
244
250
public String pathVar (String name ) {
245
- final var pathVars = pathVars ();
246
- return pathVars != null ? pathVars .get (name ) : null ;
251
+ return pathVars ().get (name );
247
252
}
248
253
249
254
/**
@@ -281,13 +286,12 @@ public <T> T pathVar(String name, Class<T> type) {
281
286
return (T ) new BigInteger (s );
282
287
}
283
288
284
- throw new IllegalArgumentException ("Wrong pathVar type: " + type );
289
+ throw new IllegalArgumentException ("Unsupported pathVar type: " + type );
285
290
}
286
291
287
292
/**
288
- * Retrieves {@link RequestContext} from the reactor context in deferred manner.
289
- *
290
- * @return {@link Mono} emitting the {@code RequestContext}, or error - if it is missing
293
+ * Retrieves {@link RequestContext} from the reactor context, wrapping the existing context if
294
+ * necessary.
291
295
*/
292
296
public static Mono <RequestContext > deferContextual () {
293
297
return Mono .deferContextual (context -> Mono .just (context .get (RequestContext .class )));
@@ -318,9 +322,8 @@ public static Mono<RequestContext> deferSecured() {
318
322
context -> {
319
323
if (!context .hasPrincipal ()) {
320
324
LOGGER .warn (
321
- "Insufficient permissions for secured method ({}) -- "
322
- + "request context ({}) does not have principal" ,
323
- context .methodInfo (),
325
+ "Insufficient permissions -- "
326
+ + "request context does not have principal, request context: {}" ,
324
327
context );
325
328
throw new ForbiddenException ("Insufficient permissions" );
326
329
}
@@ -333,11 +336,10 @@ public static Mono<RequestContext> deferSecured() {
333
336
&& !allowedRoles .isEmpty ()
334
337
&& !allowedRoles .contains (principal .role ())) {
335
338
LOGGER .warn (
336
- "Insufficient permissions for secured method ({}) -- "
337
- + "principal role '{}' is not allowed (principal: {})" ,
338
- context .methodInfo (),
339
+ "Insufficient permissions -- "
340
+ + "principal role '{}' is not allowed, request context: {}" ,
339
341
principal .role (),
340
- principal );
342
+ context );
341
343
throw new ForbiddenException ("Insufficient permissions" );
342
344
}
343
345
@@ -346,15 +348,25 @@ public static Mono<RequestContext> deferSecured() {
346
348
for (var allowedPermission : allowedPermissions ) {
347
349
if (!principal .hasPermission (allowedPermission )) {
348
350
LOGGER .warn (
349
- "Insufficient permissions for secured method ({}) -- "
350
- + "allowed permission '{}' is missing (principal: {})" ,
351
- context .methodInfo (),
351
+ "Insufficient permissions -- "
352
+ + "allowed permission '{}' is missing, request context: {}" ,
352
353
allowedPermission ,
353
- principal );
354
+ context );
354
355
throw new ForbiddenException ("Insufficient permissions" );
355
356
}
356
357
}
357
358
}
358
359
});
359
360
}
361
+
362
+ @ Override
363
+ public String toString () {
364
+ return new StringJoiner (", " , RequestContext .class .getSimpleName () + "[" , "]" )
365
+ .add ("principal=" + principal ())
366
+ .add ("methodInfo=" + methodInfo ())
367
+ .add ("headers=" + mask (headers ()))
368
+ .add ("pathVars=" + mask (pathVars ()))
369
+ .add ("sourceKeys=" + source .stream ().map (Entry ::getKey ).toList ())
370
+ .toString ();
371
+ }
360
372
}
0 commit comments